feat(kugetsu): implement issue-driven session management
- Add kugetsu init to create base session via TUI - Add kugetsu start/continue for issue-based task handling - Add kugetsu list/prune/destroy for session lifecycle - Implement directory files + index.json storage pattern - Use issue ref format: instance/user/repo#number - Fork from base session enables headless operation Solves: opencode headless CLI limitation discovered in issue #14
This commit is contained in:
@@ -1,16 +1,16 @@
|
|||||||
---
|
---
|
||||||
name: kugetsu
|
name: kugetsu
|
||||||
description: Session manager for opencode CLI. Use when managing long-running opencode sessions, resuming interrupted work, or tracking session state across disconnects. Features state tracking (used/idle/left), auto-fill last message on resume, and safe locking via confirmation prompts.
|
description: Issue-driven session manager for opencode CLI. Manages base sessions and per-issue forked sessions with automatic indexing for headless orchestration.
|
||||||
license: MIT
|
license: MIT
|
||||||
compatibility: Requires opencode CLI, bash, and filesystem access for session state.
|
compatibility: Requires opencode CLI, bash, python3, and filesystem access.
|
||||||
metadata:
|
metadata:
|
||||||
author: shoko
|
author: shoko
|
||||||
version: "1.1"
|
version: "2.0"
|
||||||
---
|
---
|
||||||
|
|
||||||
# kugetsu - OpenCode Session Manager
|
# kugetsu - OpenCode Session Manager (Issue-Driven)
|
||||||
|
|
||||||
Manages opencode CLI sessions with state tracking and safe resume.
|
Manages opencode sessions with a base session + forked session pattern optimized for headless orchestration.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -27,122 +27,184 @@ cp skills/kugetsu/scripts/kugetsu ~/.local/bin/kugetsu
|
|||||||
chmod +x ~/.local/bin/kugetsu
|
chmod +x ~/.local/bin/kugetsu
|
||||||
```
|
```
|
||||||
|
|
||||||
Or source directly when needed:
|
## Architecture
|
||||||
```bash
|
|
||||||
. skills/kugetsu/scripts/kugetsu
|
### Session Pattern
|
||||||
|
- **Base Session**: Created once via TUI, used for forking
|
||||||
|
- **Forked Sessions**: One per issue, branched from base via `opencode run --fork --session <base>`
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
```
|
||||||
|
~/.kugetsu/
|
||||||
|
├── sessions/
|
||||||
|
│ ├── base.json # Base session metadata
|
||||||
|
│ └── github.com-shoko-kugetsu-14.json # Forked session per issue
|
||||||
|
└── index.json # Maps issue refs to session files
|
||||||
```
|
```
|
||||||
|
|
||||||
## Session State
|
### Index File
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"base": "ses_abc123",
|
||||||
|
"issues": {
|
||||||
|
"github.com/shoko/kugetsu#14": "github.com-shoko-kugetsu-14.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
| State | Meaning | Resumable? |
|
### Session File
|
||||||
|-------|---------|------------|
|
```json
|
||||||
| `used` | Session is active (process running) | Yes (with confirmation) |
|
{
|
||||||
| `idle` | Session ended gracefully | No |
|
"type": "base|forked",
|
||||||
| `left` | Session interrupted/crashed | Yes |
|
"issue_ref": "github.com/shoko/kugetsu#14",
|
||||||
| `invalid` | Session data missing/corrupt | No |
|
"opencode_session_id": "ses_xyz789",
|
||||||
|
"created_at": "2026-03-29T18:16:10+02:00",
|
||||||
|
"state": "idle"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Session Directory
|
## Issue Ref Format
|
||||||
|
|
||||||
Sessions are stored in `~/.kugetsu/sessions/<session_id>/`:
|
All issue references use the format: `instance/user/repo#number`
|
||||||
- `state` - current state (used/idle/left/invalid)
|
|
||||||
- `message` - last user message (for auto-fill)
|
Examples:
|
||||||
- `pid` - active process PID (when used)
|
- `github.com/shoko/kugetsu#14`
|
||||||
|
- `gitlab.com/username/project#42`
|
||||||
|
- `codeberg.org/user/repo#100`
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
### kugetsu start `<session_id>` `<message>`
|
### kugetsu init [--force]
|
||||||
Start a new session:
|
|
||||||
|
Initialize base session via TUI:
|
||||||
```bash
|
```bash
|
||||||
kugetsu start mytask "fix bug #1"
|
kugetsu init
|
||||||
```
|
```
|
||||||
- Creates session directory
|
|
||||||
- Sets state to `used`
|
|
||||||
- Stores PID and message
|
|
||||||
- Runs: `opencode run --session <session_id> <message>`
|
|
||||||
|
|
||||||
### kugetsu list [--all]
|
- Requires a terminal (TTY) to spawn the opencode TUI
|
||||||
List sessions:
|
- Creates base session once; subsequent runs error unless `--force` is used
|
||||||
|
- Stores base session ID in `index.json`
|
||||||
|
|
||||||
|
### kugetsu start `<issue-ref>` `<message>` [--debug]
|
||||||
|
|
||||||
|
Start task for an issue by forking from base session:
|
||||||
```bash
|
```bash
|
||||||
kugetsu list # Shows only `left` (resumable)
|
kugetsu start github.com/shoko/kugetsu#14 "fix authentication bug"
|
||||||
kugetsu list --all # Shows all states
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### kugetsu resume `<session_id>` [message]
|
- Forks new session from base
|
||||||
Resume an interrupted session:
|
- Stores mapping in `index.json`
|
||||||
|
- Uses `opencode run --fork --session <base-session-id> "<message>"`
|
||||||
|
|
||||||
|
### kugetsu continue `<issue-ref>` `<message>` [--debug]
|
||||||
|
|
||||||
|
Continue work on an existing issue session:
|
||||||
```bash
|
```bash
|
||||||
kugetsu resume mytask # Auto-fills last message
|
kugetsu continue github.com/shoko/kugetsu#14 "add unit tests"
|
||||||
kugetsu resume mytask "continue" # Uses provided message
|
|
||||||
```
|
```
|
||||||
- If state is `used`: prompts for confirmation (someone else might be using)
|
|
||||||
- If state is `idle`: errors (not resumable)
|
|
||||||
- If state is `left`: proceeds with message
|
|
||||||
|
|
||||||
### kugetsu stop `<session_id>`
|
- Looks up session file from index
|
||||||
Stop a session gracefully:
|
- Uses `opencode run --continue --session <opencode-session-id> "<message>"`
|
||||||
|
|
||||||
|
### kugetsu list
|
||||||
|
|
||||||
|
List all tracked sessions:
|
||||||
```bash
|
```bash
|
||||||
kugetsu stop mytask
|
kugetsu list
|
||||||
```
|
```
|
||||||
- Sends SIGTERM to process
|
|
||||||
- Sets state to `idle`
|
|
||||||
|
|
||||||
### kugetsu destroy `<session_id>` [-y]
|
Output:
|
||||||
Delete a session:
|
```
|
||||||
|
ISSUE_REF TYPE SESSION_ID CREATED
|
||||||
|
──────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
(base) base ses_abc123 N/A
|
||||||
|
github.com/shoko/kugetsu#14 forked ses_xyz789 2026-03-29T18:16:10+02:00
|
||||||
|
```
|
||||||
|
|
||||||
|
### kugetsu prune [--force]
|
||||||
|
|
||||||
|
Remove orphaned sessions (files not in index):
|
||||||
```bash
|
```bash
|
||||||
kugetsu destroy mytask # Prompts for confirmation (default: N)
|
kugetsu prune # Shows what would be deleted
|
||||||
kugetsu destroy mytask -y # Skips confirmation
|
kugetsu prune --force # Deletes orphaned sessions
|
||||||
```
|
```
|
||||||
- Errors if session is `used` (use `stop` first)
|
|
||||||
- Errors if session not found
|
|
||||||
|
|
||||||
### kugetsu destroy --all [-y]
|
- Orphaned = session files in `sessions/` but not in `index.json`
|
||||||
Delete all sessions:
|
- Always keeps `base.json`
|
||||||
|
- Useful after opencode session cleanup
|
||||||
|
|
||||||
|
### kugetsu destroy `<issue-ref>` [-y]
|
||||||
|
|
||||||
|
Delete session for specific issue:
|
||||||
```bash
|
```bash
|
||||||
kugetsu destroy --all # Prompts for confirmation (default: N)
|
kugetsu destroy github.com/shoko/kugetsu#14 # Prompts for confirmation
|
||||||
kugetsu destroy --all -y # Skips confirmation
|
kugetsu destroy github.com/shoko/kugetsu#14 -y # Skips confirmation
|
||||||
```
|
|
||||||
- Useful for fresh start
|
|
||||||
|
|
||||||
### kugetsu help
|
|
||||||
Show usage help.
|
|
||||||
|
|
||||||
## State Transitions
|
|
||||||
|
|
||||||
```
|
|
||||||
start ──────────────► used ──────► idle (stop/SIGTERM)
|
|
||||||
│
|
|
||||||
└──────► left (kill/SIGINT/crash)
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
destroy (delete)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example Workflow
|
### kugetsu destroy --base [-y]
|
||||||
|
|
||||||
|
Delete base session (requires explicit `--base`):
|
||||||
|
```bash
|
||||||
|
kugetsu destroy --base -y
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow Example
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start a long-running task
|
# First-time setup (requires TTY)
|
||||||
kugetsu start issue42 "implement feature X"
|
kugetsu init
|
||||||
|
|
||||||
# ... time passes, connection drops ...
|
# Start work on issue
|
||||||
|
kugetsu start github.com/shoko/kugetsu#14 "implement feature X"
|
||||||
|
|
||||||
# Check what sessions are resumable
|
# Continue later
|
||||||
|
kugetsu continue github.com/shoko/kugetsu#14 "add tests"
|
||||||
|
|
||||||
|
# Continue again
|
||||||
|
kugetsu continue github.com/shoko/kugetsu#14 "fix failing test"
|
||||||
|
|
||||||
|
# List all sessions
|
||||||
kugetsu list
|
kugetsu list
|
||||||
|
|
||||||
# Resume with auto-filled message
|
# Clean up orphaned sessions
|
||||||
kugetsu resume issue42
|
kugetsu prune --force
|
||||||
|
|
||||||
# Later, when done
|
# Delete session when done
|
||||||
kugetsu stop issue42
|
kugetsu destroy github.com/shoko/kugetsu#14
|
||||||
|
|
||||||
# When you want a fresh start
|
|
||||||
kugetsu destroy --all
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Headless Operation
|
||||||
|
|
||||||
|
This design solves the headless CLI limitation discovered in Issue #14:
|
||||||
|
|
||||||
|
1. **Problem**: `opencode run --session <new>` doesn't work headlessly (SSE stream terminates)
|
||||||
|
2. **Solution**: Fork from existing base session, which works headlessly
|
||||||
|
|
||||||
|
The pattern:
|
||||||
|
- Base session created once via TUI (interactive)
|
||||||
|
- All subsequent work uses `--fork --session <base>` or `--continue --session <forked>`
|
||||||
|
|
||||||
|
## Recovery
|
||||||
|
|
||||||
|
If opencode sessions become out of sync:
|
||||||
|
|
||||||
|
1. `kugetsu list` shows tracked sessions
|
||||||
|
2. `kugetsu prune` removes orphaned files
|
||||||
|
3. For full reset: `kugetsu destroy --base -y && kugetsu init`
|
||||||
|
|
||||||
## Without kugetsu
|
## Without kugetsu
|
||||||
|
|
||||||
If kugetsu is not installed, use opencode directly:
|
If kugetsu is not available, use opencode directly:
|
||||||
```bash
|
```bash
|
||||||
opencode run --session mytask "task description"
|
# Create base session (requires TTY)
|
||||||
opencode run --continue --session mytask "continue"
|
opencode
|
||||||
opencode session list
|
# Note the session ID from: opencode session list
|
||||||
|
|
||||||
|
# Fork for issue
|
||||||
|
opencode run --fork --session <base-session-id> "task"
|
||||||
|
|
||||||
|
# Continue
|
||||||
|
opencode run --continue --session <forked-session-id> "continue"
|
||||||
```
|
```
|
||||||
Tradeoff: No state tracking, no auto-fill, no filtered list, no confirmation prompts.
|
|
||||||
|
Tradeoff: No issue mapping, no index, manual session tracking.
|
||||||
@@ -3,69 +3,137 @@ set -euo pipefail
|
|||||||
|
|
||||||
KUGETSU_DIR="${KUGETSU_DIR:-$HOME/.kugetsu}"
|
KUGETSU_DIR="${KUGETSU_DIR:-$HOME/.kugetsu}"
|
||||||
SESSIONS_DIR="$KUGETSU_DIR/sessions"
|
SESSIONS_DIR="$KUGETSU_DIR/sessions"
|
||||||
BIN_DIR="$KUGETSU_DIR/bin"
|
INDEX_FILE="$KUGETSU_DIR/index.json"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat << 'EOF'
|
cat << 'EOF'
|
||||||
kugetsu - OpenCode Session Manager
|
kugetsu - OpenCode Session Manager (Issue-Driven)
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
kugetsu start <session_id> <message> [--debug] Start a new session
|
kugetsu init [--force] Initialize base session (requires TTY)
|
||||||
kugetsu list [--all] List sessions (default: left only)
|
kugetsu start <issue-ref> <message> [--debug] Start task for issue (forks base session)
|
||||||
kugetsu resume <session_id> [message] [--debug] Resume a session
|
kugetsu continue <issue-ref> [message] [--debug] Continue existing task for issue
|
||||||
kugetsu stop <session_id> [--debug] Stop a session gracefully
|
kugetsu list List all tracked sessions
|
||||||
kugetsu destroy <session_id> [-y] [--debug] Delete a session (prompts confirmation)
|
kugetsu prune [--force] Remove orphaned sessions (keeps base)
|
||||||
kugetsu destroy --all [-y] Delete all sessions (prompts confirmation)
|
kugetsu destroy <issue-ref> [-y] Delete session for issue
|
||||||
kugetsu help Show this help
|
kugetsu destroy --base [-y] Delete base session
|
||||||
|
kugetsu help Show this help
|
||||||
|
|
||||||
States:
|
Issue Ref Format:
|
||||||
used - Session is active (process running)
|
instance/user/repo#number
|
||||||
idle - Session ended gracefully (not resumable)
|
Example: github.com/shoko/kugetsu#14
|
||||||
left - Session interrupted/crashed (resumable)
|
|
||||||
invalid - Session data missing/corrupt
|
Commands:
|
||||||
|
init Create base session via TUI. Requires terminal access.
|
||||||
|
Use --force to reinitialize if base session exists.
|
||||||
|
start Fork new session from base for specific issue.
|
||||||
|
continue Continue work on existing issue session.
|
||||||
|
list Show all sessions (base + forked issues).
|
||||||
|
prune Remove sessions not in index (orphaned from opencode).
|
||||||
|
Use --force to skip confirmation.
|
||||||
|
destroy Delete specific issue session or base session.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--debug Show real-time debug output and capture to debug.log
|
--debug Show real-time debug output and capture to debug.log
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
kugetsu start mytask "fix bug #1"
|
kugetsu init
|
||||||
kugetsu start mytask "fix bug #1" --debug
|
kugetsu start github.com/shoko/kugetsu#14 "fix bug"
|
||||||
|
kugetsu continue github.com/shoko/kugetsu#14 "add tests"
|
||||||
kugetsu list
|
kugetsu list
|
||||||
kugetsu list --all
|
kugetsu prune
|
||||||
kugetsu resume mytask
|
kugetsu prune --force
|
||||||
kugetsu resume mytask "continue working" --debug
|
kugetsu destroy github.com/shoko/kugetsu#14
|
||||||
kugetsu stop mytask --debug
|
|
||||||
kugetsu destroy mytask --debug
|
|
||||||
kugetsu destroy mytask -y
|
|
||||||
kugetsu destroy --all
|
|
||||||
kugetsu destroy --all -y
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_dirs() {
|
ensure_dirs() {
|
||||||
mkdir -p "$SESSIONS_DIR" "$BIN_DIR"
|
mkdir -p "$SESSIONS_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_session_id() {
|
issue_ref_to_filename() {
|
||||||
local session_id="$1"
|
local issue_ref="$1"
|
||||||
if [ -z "$session_id" ]; then
|
echo "$issue_ref" | sed 's/[\/:]/-/g' | sed 's/#/-/'
|
||||||
echo "Error: session_id cannot be empty" >&2
|
}
|
||||||
|
|
||||||
|
filename_to_issue_ref() {
|
||||||
|
local filename="$1"
|
||||||
|
local name="${filename%.json}"
|
||||||
|
echo "$name" | sed 's/-\([0-9]*\)$/#\1' | sed 's/-/\//g'
|
||||||
|
}
|
||||||
|
|
||||||
|
read_index() {
|
||||||
|
if [ -f "$INDEX_FILE" ]; then
|
||||||
|
cat "$INDEX_FILE"
|
||||||
|
else
|
||||||
|
echo '{"base": null, "issues": {}}'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
write_index() {
|
||||||
|
local base="$1"
|
||||||
|
local issues_json="$2"
|
||||||
|
local temp_file="$INDEX_FILE.tmp.$$"
|
||||||
|
printf '{"base": %s, "issues": %s}\n' "$base" "$issues_json" > "$temp_file"
|
||||||
|
mv "$temp_file" "$INDEX_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_base_session_id() {
|
||||||
|
local index=$(read_index)
|
||||||
|
echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('base') or '')"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_session_for_issue() {
|
||||||
|
local issue_ref="$1"
|
||||||
|
local index=$(read_index)
|
||||||
|
echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d['issues'].get('$issue_ref') or '')"
|
||||||
|
}
|
||||||
|
|
||||||
|
set_base_in_index() {
|
||||||
|
local base_session_id="$1"
|
||||||
|
local issues_json=$(read_index | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d['issues']))")
|
||||||
|
write_index "\"$base_session_id\"" "$issues_json"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_issue_to_index() {
|
||||||
|
local issue_ref="$1"
|
||||||
|
local session_file="$2"
|
||||||
|
local index=$(read_index)
|
||||||
|
local base=$(get_base_session_id)
|
||||||
|
local issues=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d['issues']))")
|
||||||
|
local new_issues=$(echo "$issues" | python3 -c "import sys, json; d=json.load(sys.stdin); d['$issue_ref']='$session_file'; print(json.dumps(d))")
|
||||||
|
if [ "$base" = "null" ] || [ -z "$base" ]; then
|
||||||
|
write_index "null" "$new_issues"
|
||||||
|
else
|
||||||
|
write_index "\"$base\"" "$new_issues"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_issue_from_index() {
|
||||||
|
local issue_ref="$1"
|
||||||
|
local index=$(read_index)
|
||||||
|
local base=$(get_base_session_id)
|
||||||
|
local new_issues=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); d['issues'].pop('$issue_ref', None); print(json.dumps(d['issues']))")
|
||||||
|
if [ "$base" = "null" ] || [ -z "$base" ]; then
|
||||||
|
write_index "null" "$new_issues"
|
||||||
|
else
|
||||||
|
write_index "\"$base\"" "$new_issues"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_issue_ref() {
|
||||||
|
local issue_ref="$1"
|
||||||
|
if [[ ! "$issue_ref" =~ ^[^/]+/[^/]+/[^#]+#[0-9]+$ ]]; then
|
||||||
|
echo "Error: invalid issue ref format" >&2
|
||||||
|
echo "Expected: instance/user/repo#number" >&2
|
||||||
|
echo "Example: github.com/shoko/kugetsu#14" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
get_session_dir() {
|
check_opencode_session_exists() {
|
||||||
local session_id="$1"
|
local session_id="$1"
|
||||||
echo "$SESSIONS_DIR/$session_id"
|
opencode session list 2>/dev/null | grep -q "^$session_id"
|
||||||
}
|
|
||||||
|
|
||||||
get_state() {
|
|
||||||
local session_dir="$1"
|
|
||||||
if [ -f "$session_dir/state" ]; then
|
|
||||||
cat "$session_dir/state"
|
|
||||||
else
|
|
||||||
echo "invalid"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_MODE=false
|
DEBUG_MODE=false
|
||||||
@@ -87,416 +155,315 @@ set_debug_mode() {
|
|||||||
echo "${filtered_args[@]}"
|
echo "${filtered_args[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
show_debug_log() {
|
cmd_init() {
|
||||||
local session_dir="$1"
|
local force=false
|
||||||
local debug_log="$session_dir/debug.log"
|
|
||||||
if [ -f "$debug_log" ]; then
|
|
||||||
echo "=== Debug Log ==="
|
|
||||||
cat "$debug_log"
|
|
||||||
echo "=== End Debug Log ==="
|
|
||||||
else
|
|
||||||
echo "No debug log found"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
set_state() {
|
while [ $# -gt 0 ]; do
|
||||||
local session_dir="$1"
|
case "$1" in
|
||||||
local state="$2"
|
--force)
|
||||||
echo "$state" > "$session_dir/state"
|
force=true
|
||||||
}
|
;;
|
||||||
|
*)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
is_process_running() {
|
ensure_dirs
|
||||||
local pid="$1"
|
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_and_update_state() {
|
local existing_base=$(get_base_session_id)
|
||||||
local session_dir="$1"
|
if [ -n "$existing_base" ] && [ "$existing_base" != "null" ]; then
|
||||||
local state=$(get_state "$session_dir")
|
if [ "$force" = true ]; then
|
||||||
|
echo "Warning: Reinitializing base session (force mode)" >&2
|
||||||
if [ "$state" = "used" ]; then
|
|
||||||
local pid_file="$session_dir/pid"
|
|
||||||
if [ -f "$pid_file" ]; then
|
|
||||||
local pid=$(cat "$pid_file")
|
|
||||||
if ! is_process_running "$pid"; then
|
|
||||||
set_state "$session_dir" "left"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
set_state "$session_dir" "left"
|
echo "Error: Base session already exists: $existing_base" >&2
|
||||||
return 1
|
echo "Use --force to reinitialize" >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
return 0
|
|
||||||
|
if ! test -t 0; then
|
||||||
|
echo "Error: init requires a terminal (TTY)" >&2
|
||||||
|
echo "Please run this command in an interactive shell" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting TUI to create base session..."
|
||||||
|
echo "Press Ctrl+C to cancel or wait for session to be created"
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
opencode
|
||||||
|
|
||||||
|
local session_ids=$(opencode session list 2>/dev/null | grep -E '^ses_' | awk '{print $1}' | tail -1)
|
||||||
|
if [ -z "$session_ids" ]; then
|
||||||
|
echo "Error: Could not find newly created session" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local new_session_id=$(echo "$session_ids" | tail -1)
|
||||||
|
local session_file="base.json"
|
||||||
|
|
||||||
|
printf '{"type": "base", "opencode_session_id": "%s", "created_at": "%s", "state": "idle"}\n' \
|
||||||
|
"$new_session_id" "$(date -Iseconds)" > "$SESSIONS_DIR/$session_file"
|
||||||
|
|
||||||
|
set_base_in_index "$new_session_id"
|
||||||
|
|
||||||
|
echo "Base session initialized: $new_session_id"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_start() {
|
cmd_start() {
|
||||||
local session_id=""
|
local issue_ref=""
|
||||||
local message=""
|
local message=""
|
||||||
|
local args=("$@")
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case "$1" in
|
args=$(set_debug_mode "${args[@]}")
|
||||||
--debug)
|
|
||||||
DEBUG_MODE=true
|
for arg in $args; do
|
||||||
;;
|
if [ -z "$issue_ref" ]; then
|
||||||
*)
|
issue_ref="$arg"
|
||||||
if [ -z "$session_id" ]; then
|
elif [ -z "$message" ]; then
|
||||||
session_id="$1"
|
message="$arg"
|
||||||
elif [ -z "$message" ]; then
|
fi
|
||||||
message="$1"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -z "$session_id" ] || [ -z "$message" ]; then
|
if [ -z "$issue_ref" ] || [ -z "$message" ]; then
|
||||||
echo "Error: start requires <session_id> and <message>" >&2
|
echo "Error: start requires <issue-ref> and <message>" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
validate_session_id "$session_id"
|
validate_issue_ref "$issue_ref"
|
||||||
local session_dir=$(get_session_dir "$session_id")
|
|
||||||
|
|
||||||
ensure_dirs
|
ensure_dirs
|
||||||
|
|
||||||
if [ -d "$session_dir" ]; then
|
local base_session_id=$(get_base_session_id)
|
||||||
local state=$(get_state "$session_dir")
|
if [ -z "$base_session_id" ] || [ "$base_session_id" = "null" ]; then
|
||||||
check_and_update_state "$session_dir"
|
echo "Error: No base session. Run 'kugetsu init' first." >&2
|
||||||
state=$(get_state "$session_dir")
|
exit 1
|
||||||
|
|
||||||
if [ "$state" = "used" ]; then
|
|
||||||
echo "Error: session '$session_id' is already in use (state=used)" >&2
|
|
||||||
echo "Use 'kugetsu list' to see all sessions, or 'kugetsu resume $session_id' to resume" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$state" = "left" ]; then
|
|
||||||
echo "Warning: session '$session_id' was left interrupted" >&2
|
|
||||||
echo "Resuming instead of starting new..." >&2
|
|
||||||
cmd_resume "$session_id" "$message"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$session_dir"
|
local existing_session=$(get_session_for_issue "$issue_ref")
|
||||||
set_state "$session_dir" "used"
|
if [ -n "$existing_session" ] && [ "$existing_session" != "null" ]; then
|
||||||
echo "$$" > "$session_dir/pid"
|
echo "Error: Session for '$issue_ref' already exists" >&2
|
||||||
echo "$message" > "$session_dir/message"
|
echo "Use 'kugetsu continue $issue_ref <message>' instead" >&2
|
||||||
|
exit 1
|
||||||
echo "Starting session '$session_id'..."
|
fi
|
||||||
|
|
||||||
|
local session_file="${issue_ref_to_filename "$issue_ref"}.json"
|
||||||
|
|
||||||
|
echo "Forking session for '$issue_ref'..."
|
||||||
if [ "$DEBUG_MODE" = true ]; then
|
if [ "$DEBUG_MODE" = true ]; then
|
||||||
stdbuf -oL opencode run --print-logs --log-level DEBUG --session "$session_id" "$message" 2>&1 | tee "$session_dir/debug.log"
|
opencode run --fork --session "$base_session_id" "$message" 2>&1 | tee "$SESSIONS_DIR/$session_file.debug.log"
|
||||||
else
|
else
|
||||||
opencode run --session "$session_id" "$message"
|
opencode run --fork --session "$base_session_id" "$message"
|
||||||
fi
|
fi
|
||||||
local exit_code=$?
|
|
||||||
|
local new_session_ids=$(opencode session list 2>/dev/null | grep -E '^ses_' | awk '{print $1}' | tail -1)
|
||||||
if [ $exit_code -eq 0 ]; then
|
local new_session_id=$(echo "$new_session_ids" | tail -1)
|
||||||
set_state "$session_dir" "idle"
|
|
||||||
|
printf '{"type": "forked", "issue_ref": "%s", "opencode_session_id": "%s", "created_at": "%s", "state": "idle"}\n' \
|
||||||
|
"$issue_ref" "$new_session_id" "$(date -Iseconds)" > "$SESSIONS_DIR/$session_file"
|
||||||
|
|
||||||
|
add_issue_to_index "$issue_ref" "$session_file"
|
||||||
|
|
||||||
|
echo "Session started for '$issue_ref': $new_session_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_continue() {
|
||||||
|
local issue_ref=""
|
||||||
|
local message=""
|
||||||
|
local args=("$@")
|
||||||
|
|
||||||
|
args=$(set_debug_mode "${args[@]}")
|
||||||
|
|
||||||
|
for arg in $args; do
|
||||||
|
if [ -z "$issue_ref" ]; then
|
||||||
|
issue_ref="$arg"
|
||||||
|
elif [ -z "$message" ]; then
|
||||||
|
message="$arg"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$issue_ref" ]; then
|
||||||
|
echo "Error: continue requires <issue-ref>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$message" ]; then
|
||||||
|
echo "Error: continue requires <message>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
validate_issue_ref "$issue_ref"
|
||||||
|
|
||||||
|
local session_file=$(get_session_for_issue "$issue_ref")
|
||||||
|
if [ -z "$session_file" ] || [ "$session_file" = "null" ]; then
|
||||||
|
echo "Error: No session found for '$issue_ref'" >&2
|
||||||
|
echo "Use 'kugetsu start $issue_ref <message>' to create one" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local session_path="$SESSIONS_DIR/$session_file"
|
||||||
|
if [ ! -f "$session_path" ]; then
|
||||||
|
echo "Error: Session file missing: $session_path" >&2
|
||||||
|
echo "Run 'kugetsu start $issue_ref <message>' to recreate" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local opencode_session_id=$(python3 -c "import json; print(json.load(open('$session_path'))['opencode_session_id'])")
|
||||||
|
|
||||||
|
if ! check_opencode_session_exists "$opencode_session_id"; then
|
||||||
|
echo "Warning: Session may have expired in opencode" >&2
|
||||||
|
echo "Attempting to continue anyway..." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Continuing session for '$issue_ref'..."
|
||||||
|
if [ "$DEBUG_MODE" = true ]; then
|
||||||
|
opencode run --continue --session "$opencode_session_id" "$message" 2>&1 | tee "$session_path.debug.log"
|
||||||
else
|
else
|
||||||
set_state "$session_dir" "left"
|
opencode run --continue --session "$opencode_session_id" "$message"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$session_dir/pid"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_list() {
|
cmd_list() {
|
||||||
local show_all=false
|
|
||||||
if [ $# -ge 1 ] && [ "$1" = "--all" ]; then
|
|
||||||
show_all=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
ensure_dirs
|
ensure_dirs
|
||||||
|
|
||||||
printf "%-20s %-10s %-s\n" "SESSION_ID" "STATE" "LAST_MESSAGE"
|
printf "%-50s %-10s %-25s %s\n" "ISSUE_REF" "TYPE" "SESSION_ID" "CREATED"
|
||||||
printf "%-20s %-10s %-s\n" "──────────" "─────" "───────────"
|
printf "%-50s %-10s %-25s %s\n" "─────────" "─────" "──────────" "───────"
|
||||||
|
|
||||||
for session_dir in "$SESSIONS_DIR"/*; do
|
local base_session_id=$(get_base_session_id)
|
||||||
if [ -d "$session_dir" ]; then
|
if [ -n "$base_session_id" ] && [ "$base_session_id" != "null" ]; then
|
||||||
local session_id=$(basename "$session_dir")
|
printf "%-50s %-10s %-25s %s\n" "(base)" "base" "$base_session_id" "N/A"
|
||||||
check_and_update_state "$session_dir"
|
fi
|
||||||
local state=$(get_state "$session_dir")
|
|
||||||
|
local index=$(read_index)
|
||||||
if [ "$show_all" = false ] && [ "$state" != "left" ]; then
|
local issue_refs=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print('\n'.join(d['issues'].keys()))" 2>/dev/null || true)
|
||||||
|
|
||||||
|
for session_file in "$SESSIONS_DIR"/*.json; do
|
||||||
|
if [ -f "$session_file" ]; then
|
||||||
|
local filename=$(basename "$session_file" .json)
|
||||||
|
if [ "$filename" = "base" ]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local message=""
|
local issue_ref=$(python3 -c "import json; print(json.load(open('$session_file'))['issue_ref'])" 2>/dev/null || echo "$filename")
|
||||||
if [ -f "$session_dir/message" ]; then
|
local sess_id=$(python3 -c "import json; print(json.load(open('$session_file'))['opencode_session_id'])" 2>/dev/null || echo "unknown")
|
||||||
message=$(cat "$session_dir/message")
|
local created=$(python3 -c "import json; print(json.load(open('$session_file'))['created_at'])" 2>/dev/null || echo "unknown")
|
||||||
if [ ${#message} -gt 40 ]; then
|
|
||||||
message="${message:0:37}..."
|
printf "%-50s %-10s %-25s %s\n" "$issue_ref" "forked" "$sess_id" "$created"
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "%-20s %-10s %-s\n" "$session_id" "$state" "$message"
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_resume() {
|
cmd_prune() {
|
||||||
local session_id=""
|
local force=false
|
||||||
local message=""
|
|
||||||
local args=("$@")
|
|
||||||
|
|
||||||
for arg in "${args[@]}"; do
|
|
||||||
case "$arg" in
|
|
||||||
--debug)
|
|
||||||
DEBUG_MODE=true
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
if [ -z "$session_id" ]; then
|
|
||||||
session_id="$arg"
|
|
||||||
elif [ -z "$message" ]; then
|
|
||||||
message="$arg"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "$session_id" ]; then
|
|
||||||
echo "Error: resume requires <session_id>" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
validate_session_id "$session_id"
|
|
||||||
|
|
||||||
local session_dir=$(get_session_dir "$session_id")
|
|
||||||
|
|
||||||
if [ ! -d "$session_dir" ]; then
|
|
||||||
echo "Error: session '$session_id' not found" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
check_and_update_state "$session_dir"
|
|
||||||
local state=$(get_state "$session_dir")
|
|
||||||
|
|
||||||
if [ "$state" = "used" ]; then
|
|
||||||
echo "Warning: session '$session_id' is marked as used" >&2
|
|
||||||
local pid=""
|
|
||||||
if [ -f "$session_dir/pid" ]; then
|
|
||||||
pid=$(cat "$session_dir/pid")
|
|
||||||
fi
|
|
||||||
if [ -n "$pid" ] && is_process_running "$pid"; then
|
|
||||||
echo "Error: process $pid is still running for this session" >&2
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
set_state "$session_dir" "left"
|
|
||||||
state="left"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$state" = "idle" ]; then
|
|
||||||
echo "Error: session '$session_id' ended gracefully (state=idle)" >&2
|
|
||||||
echo "This session cannot be resumed. Start a new session instead." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$state" = "invalid" ]; then
|
|
||||||
echo "Error: session '$session_id' is invalid (state=invalid)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$message" ]; then
|
|
||||||
if [ -f "$session_dir/message" ]; then
|
|
||||||
message=$(cat "$session_dir/message")
|
|
||||||
echo "Auto-filled message: $message"
|
|
||||||
else
|
|
||||||
echo "Error: no message stored for session '$session_id'" >&2
|
|
||||||
echo "Provide a message as second argument: kugetsu resume $session_id <message>" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Using provided message: $message"
|
|
||||||
fi
|
|
||||||
|
|
||||||
set_state "$session_dir" "used"
|
|
||||||
echo "$$" > "$session_dir/pid"
|
|
||||||
echo "$message" > "$session_dir/message"
|
|
||||||
|
|
||||||
echo "Resuming session '$session_id'..."
|
|
||||||
if [ "$DEBUG_MODE" = true ]; then
|
|
||||||
stdbuf -oL opencode run --print-logs --log-level DEBUG --continue --session "$session_id" "$message" 2>&1 | tee "$session_dir/debug.log"
|
|
||||||
else
|
|
||||||
opencode run --continue --session "$session_id" "$message"
|
|
||||||
fi
|
|
||||||
local exit_code=$?
|
|
||||||
|
|
||||||
if [ $exit_code -eq 0 ]; then
|
|
||||||
set_state "$session_dir" "idle"
|
|
||||||
else
|
|
||||||
set_state "$session_dir" "left"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f "$session_dir/pid"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_stop() {
|
|
||||||
local session_id=""
|
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--debug)
|
--force)
|
||||||
DEBUG_MODE=true
|
force=true
|
||||||
;;
|
|
||||||
*)
|
|
||||||
if [ -z "$session_id" ]; then
|
|
||||||
session_id="$1"
|
|
||||||
fi
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -z "$session_id" ]; then
|
ensure_dirs
|
||||||
echo "Error: stop requires <session_id>" >&2
|
|
||||||
exit 1
|
local index=$(read_index)
|
||||||
fi
|
local index_session_files=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); sessions=set(d['issues'].values()); sessions.add('base.json'); print('\n'.join(sessions))" 2>/dev/null || echo "base.json")
|
||||||
|
|
||||||
validate_session_id "$session_id"
|
local orphaned=()
|
||||||
local session_dir=$(get_session_dir "$session_id")
|
for session_file in "$SESSIONS_DIR"/*.json; do
|
||||||
|
if [ -f "$session_file" ]; then
|
||||||
if [ ! -d "$session_dir" ]; then
|
local filename=$(basename "$session_file")
|
||||||
echo "Error: session '$session_id' not found" >&2
|
if ! echo "$index_session_files" | grep -q "^$filename$"; then
|
||||||
exit 1
|
orphaned+=("$session_file")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$DEBUG_MODE" = true ]; then
|
|
||||||
show_debug_log "$session_dir"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local state=$(get_state "$session_dir")
|
|
||||||
|
|
||||||
if [ "$state" != "used" ]; then
|
|
||||||
echo "Error: session '$session_id' is not in use (state=$state)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local pid=""
|
|
||||||
if [ -f "$session_dir/pid" ]; then
|
|
||||||
pid=$(cat "$session_dir/pid")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$pid" ] && is_process_running "$pid"; then
|
|
||||||
echo "Sending SIGTERM to process $pid..."
|
|
||||||
kill -TERM "$pid" 2>/dev/null || true
|
|
||||||
|
|
||||||
local count=0
|
|
||||||
while is_process_running "$pid" && [ $count -lt 10 ]; do
|
|
||||||
sleep 0.5
|
|
||||||
count=$((count + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
if is_process_running "$pid"; then
|
|
||||||
echo "Process still running, sending SIGKILL..." >&2
|
|
||||||
kill -KILL "$pid" 2>/dev/null || true
|
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#orphaned[@]} -eq 0 ]; then
|
||||||
|
echo "No orphaned sessions found"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found ${#orphaned[@]} orphaned session(s):"
|
||||||
|
for f in "${orphaned[@]}"; do
|
||||||
|
echo " - $(basename "$f")"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$force" = true ]; then
|
||||||
|
echo "Removing orphaned sessions (force mode)..."
|
||||||
|
for f in "${orphaned[@]}"; do
|
||||||
|
rm -f "$f"
|
||||||
|
echo "Removed: $(basename "$f")"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "Run with --force to remove"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
set_state "$session_dir" "idle"
|
|
||||||
rm -f "$session_dir/pid"
|
|
||||||
|
|
||||||
echo "Session '$session_id' stopped"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_destroy() {
|
cmd_destroy() {
|
||||||
local session_id=""
|
local target=""
|
||||||
local destroy_all=false
|
|
||||||
local force=false
|
local force=false
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--all)
|
--base)
|
||||||
destroy_all=true
|
target="base"
|
||||||
;;
|
|
||||||
--debug)
|
|
||||||
DEBUG_MODE=true
|
|
||||||
;;
|
;;
|
||||||
-y|--yes)
|
-y|--yes)
|
||||||
force=true
|
force=true
|
||||||
;;
|
;;
|
||||||
-*)
|
|
||||||
echo "Error: unknown option '$1'" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
*)
|
*)
|
||||||
if [ -n "$session_id" ]; then
|
if [ -z "$target" ]; then
|
||||||
echo "Error: too many arguments" >&2
|
target="$1"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
session_id="$1"
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ "$destroy_all" = true ]; then
|
if [ -z "$target" ]; then
|
||||||
if [ -n "$session_id" ]; then
|
echo "Error: destroy requires <issue-ref> or --base" >&2
|
||||||
echo "Error: cannot specify session_id with --all" >&2
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$target" = "base" ]; then
|
||||||
|
if [ "$force" = true ]; then
|
||||||
|
rm -f "$SESSIONS_DIR/base.json"
|
||||||
|
echo '{"base": null, "issues": {}}' > "$INDEX_FILE"
|
||||||
|
echo "Base session destroyed"
|
||||||
|
else
|
||||||
|
echo "Error: destroying base session requires --base -y" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
return
|
||||||
if [ "$force" = true ]; then
|
fi
|
||||||
rm -rf "$SESSIONS_DIR"/*
|
|
||||||
echo "All sessions deleted"
|
validate_issue_ref "$target"
|
||||||
return
|
|
||||||
fi
|
local session_file=$(get_session_for_issue "$target")
|
||||||
|
if [ -z "$session_file" ] || [ "$session_file" = "null" ]; then
|
||||||
echo "Delete ALL sessions? [y/N] "
|
echo "Error: No session found for '$target'" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local session_path="$SESSIONS_DIR/$session_file"
|
||||||
|
|
||||||
|
if [ "$force" = true ]; then
|
||||||
|
rm -f "$session_path"
|
||||||
|
remove_issue_from_index "$target"
|
||||||
|
echo "Session for '$target' destroyed"
|
||||||
|
else
|
||||||
|
echo "Delete session for '$target'? [y/N] "
|
||||||
local reply
|
local reply
|
||||||
read reply
|
read reply
|
||||||
if [ "$reply" = "y" ] || [ "$reply" = "Y" ]; then
|
if [ "$reply" = "y" ] || [ "$reply" = "Y" ]; then
|
||||||
rm -rf "$SESSIONS_DIR"/*
|
rm -f "$session_path"
|
||||||
echo "All sessions deleted"
|
remove_issue_from_index "$target"
|
||||||
|
echo "Session for '$target' destroyed"
|
||||||
else
|
else
|
||||||
echo "Aborted"
|
echo "Aborted"
|
||||||
fi
|
fi
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$session_id" ]; then
|
|
||||||
echo "Error: destroy requires <session_id> or --all" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
validate_session_id "$session_id"
|
|
||||||
local session_dir=$(get_session_dir "$session_id")
|
|
||||||
|
|
||||||
if [ ! -d "$session_dir" ]; then
|
|
||||||
echo "Error: session '$session_id' not found" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$DEBUG_MODE" = true ]; then
|
|
||||||
show_debug_log "$session_dir"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local state=$(get_state "$session_dir")
|
|
||||||
if [ "$state" = "used" ]; then
|
|
||||||
echo "Error: session '$session_id' is in use (state=used)" >&2
|
|
||||||
echo "Use 'kugetsu stop $session_id' first" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$force" = true ]; then
|
|
||||||
rm -rf "$session_dir"
|
|
||||||
echo "Session '$session_id' deleted"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Delete session '$session_id'? [y/N] "
|
|
||||||
local reply
|
|
||||||
read reply
|
|
||||||
if [ "$reply" = "y" ] || [ "$reply" = "Y" ]; then
|
|
||||||
rm -rf "$session_dir"
|
|
||||||
echo "Session '$session_id' deleted"
|
|
||||||
else
|
|
||||||
echo "Aborted"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,25 +472,28 @@ main() {
|
|||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local command="$1"
|
local command="$1"
|
||||||
shift
|
shift
|
||||||
|
|
||||||
case "$command" in
|
case "$command" in
|
||||||
help|--help|-h)
|
help|--help|-h)
|
||||||
usage
|
usage
|
||||||
;;
|
;;
|
||||||
|
init)
|
||||||
|
cmd_init "$@"
|
||||||
|
;;
|
||||||
start)
|
start)
|
||||||
cmd_start "$@"
|
cmd_start "$@"
|
||||||
;;
|
;;
|
||||||
|
continue)
|
||||||
|
cmd_continue "$@"
|
||||||
|
;;
|
||||||
list)
|
list)
|
||||||
cmd_list "$@"
|
cmd_list "$@"
|
||||||
;;
|
;;
|
||||||
resume)
|
prune)
|
||||||
cmd_resume "$@"
|
cmd_prune "$@"
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
cmd_stop "$@"
|
|
||||||
;;
|
;;
|
||||||
destroy)
|
destroy)
|
||||||
cmd_destroy "$@"
|
cmd_destroy "$@"
|
||||||
@@ -536,4 +506,4 @@ main() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
Reference in New Issue
Block a user