opencode-sesh 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,129 @@
1
+ # AGENTS.md — sesh
2
+
3
+ Instructions for AI coding agents working in this repository. The map, not the
4
+ territory: read this first, then the specific file you need.
5
+
6
+ ## What this is
7
+
8
+ `sesh` is an opencode-native session browser in three parts:
9
+
10
+ 1. **`bin/`** — a fullscreen [fzf](https://github.com/junegunn/fzf) picker for
11
+ any terminal. Lists **all** sessions across **all** project directories,
12
+ full-text searchable, with transcript preview and resume/fork/delete.
13
+ 2. **`tui/sesh-panel.tsx`** — a SolidJS/OpenTUI plugin for the opencode TUI: a
14
+ recent-sessions section in the sidebar plus a `ctrl+o` / `/sesh` picker.
15
+ 3. **`opencode/tools/sesh-list.ts`** — the `sesh-list` custom tool, so the agent
16
+ can list sessions across every directory (the native `opencode session list`
17
+ is project-scoped).
18
+
19
+ No iTerm2, no AppleScript, no panes, no window management — **never add any.**
20
+ opencode has no persistent-sidebar API, so the "sidebar" is a `sidebar_content`
21
+ slot section plus an xlarge modal picker; the terminal UI is fullscreen fzf.
22
+
23
+ ## Start here
24
+
25
+ ```bash
26
+ npm install # dev deps (TypeScript, opencode/OpenTUI types)
27
+ npm test # fixture-DB regression suite — must stay green
28
+ npm run typecheck # tsc over tui/ and opencode/
29
+ npm run lint:sh # bash -n on every script
30
+ HOME=/tmp/fakehome bash install.sh # installer smoke test (never touches ~/.config)
31
+ ```
32
+
33
+ ## Layout
34
+
35
+ - `bin/` — flat; no subdirs. Scripts resolve siblings from their own location.
36
+ Never hardcode install paths.
37
+ - `tui/sesh-panel.tsx` — SolidJS panel, `/** @jsxImportSource @opentui/solid */`.
38
+ - `opencode/tools/sesh-list.ts` — the agent-facing `sesh-list` tool.
39
+ - `themes/`, `tests/`, `install.sh`, `README.md`.
40
+
41
+ The **filename becomes the tool name** in opencode, so `sesh-list.ts` is the
42
+ `sesh-list` tool. Do not rename it without updating the docs.
43
+
44
+ ## Data flow (fzf picker)
45
+
46
+ `bin/sesh-list.sh` is the only place that knows the session store (opencode
47
+ SQLite DB: `session` / `message` / `part` tables). Keystrokes never touch the
48
+ database:
49
+
50
+ - `--refresh` (expensive): bulk-reads session metadata and per-session text
51
+ parts, validates the persistent extraction cache
52
+ (`$SESH_CACHE_DIR/extractions/<ses_*.json>`) against `time_updated` + part
53
+ count, re-extracts only changed sessions, prunes vanished ones, and atomically
54
+ publishes `STATE_DIR/snapshot.jsonl` with `status = fresh`. Scope
55
+ (`--cwd`/toggle) and `--limit` apply at assembly, so metadata always covers
56
+ the whole DB and pruning is always safe.
57
+ - render (cheap, pure `jq`): reads the snapshot only. Filters on `searchText`
58
+ (title + `fulltextLower`, both lowercased at extraction), groups by `cwd`,
59
+ sorts groups/items by recency, renders the TSV.
60
+
61
+ **Output contract** (TSV, 6 fields):
62
+ `agentId sessionId liveState display cwd trackingId`. `display` is the only
63
+ rendered column; `trackingId` is fzf's `--id-nth`. Headers/notices carry an
64
+ empty `sessionId` and are a selection no-op — keep that guard.
65
+
66
+ One worker (`sesh-refresh-worker.sh`, per picker, TERMed on exit) serializes
67
+ scans; fzf's `start`/`every(3)`/`change` bindings render only. Selection requests
68
+ a fresh poll (`request`/`wait`, ≤12 s) before dispatch. opencode has no
69
+ live-agent API, so `liveState` is always `unknown` and rows render gray;
70
+ dispatch is always `opencode --session <id>` (`--fork` with Ctrl-F/Enter).
71
+ Resume happens in place (`exec` after `cd` to the session's cwd).
72
+
73
+ ## Conventions
74
+
75
+ - Default scope is **all** sessions, every directory. `--cwd`/Ctrl-G narrows.
76
+ Archived sessions and fork children are hidden unless `--archived`.
77
+ - **Text parts only** in search/preview; reasoning and tool payloads are
78
+ excluded (asserted in tests).
79
+ - Validate session ids (`^ses_[A-Za-z0-9]+$`) before SQL interpolation — never
80
+ interpolate unvalidated input.
81
+ - Prefer direct `sqlite3` over `opencode db` (startup latency). Never parse
82
+ `sqlite3 -json` in `-R` mode; it pretty-prints — parse as JSON.
83
+ - `jq -R` output stays JSON-quoted: use `-Rr` when a shell variable needs the
84
+ raw string.
85
+ - fzf: stock only, `>= 0.52` (`transform:`, `every():`). `{q}` in binds is
86
+ shell-quoted by fzf — don't add quoting. Ctrl-G is bind-only (stays open);
87
+ Ctrl-F is `--expect` (accepts with fork).
88
+ - The installer prefers the local opencode version for `@opencode-ai/*` and
89
+ merges (never clobbers) the config `package.json`; opencode runs `bun install`
90
+ at startup.
91
+
92
+ ## TUI panel (`tui/sesh-panel.tsx`)
93
+
94
+ - **Sidebar:** `api.slots.register` on `sidebar_content` (append mode — native
95
+ sidebar content stays). Renders the most recent unarchived sessions with the
96
+ current one highlighted, polled every 15 s. Display-only; the picker does
97
+ actions.
98
+ - **Picker:** a custom dialog (not `DialogSelect`) grouped by
99
+ project/directory, recency-ordered, resume via
100
+ `api.route.navigate("session", …)`. `ctrl+o` and the command palette open it;
101
+ Ctrl-X deletes via the opencode CLI and removes the row only after the server
102
+ confirms the deletion.
103
+
104
+ ### Hard-won rules — do not regress
105
+
106
+ - **No `DialogSelect`.** Call `api.ui.dialog.setSize("xlarge")` **after**
107
+ `dialog.replace(...)`; `replace` resets the size to medium.
108
+ - Use the `backgroundColor` **prop** on boxes, never `style={{backgroundColor}}`.
109
+ - No percentage heights/widths in the picker; size the list window explicitly.
110
+ - An inline `<input>` in a slot does **not** receive keyboard focus. Sidebar and
111
+ home search are keymap-captured queries (priority-20 base layer, letters /
112
+ space / backspace, Esc exits); click the box to activate, click elsewhere to
113
+ leave.
114
+ - **`/sesh` is the plugin's slash**, registered with
115
+ `slash: { name: "sesh" }` on the `api.command.register` entry — that is the
116
+ only way to open the dialog. A markdown command under `commands/` cannot drive
117
+ the picker (it just prompts the model), so never ship one; the installer
118
+ removes any leftover `commands/sesh.md`.
119
+ - Do not bind `<leader>l` (native `session_list`) or shadow the native
120
+ `/sessions` command.
121
+
122
+ ## Verifying
123
+
124
+ - `npm test` — fixture-DB regression suite, hermetic via `SESH_*` overrides.
125
+ - `npm run typecheck` — `tsc` over `tui/` and `opencode/`.
126
+ - `npm run lint:sh` — `bash -n` on every script.
127
+ - `HOME=/tmp/fakehome bash install.sh` — installer smoke test.
128
+ - Keep `tui/sesh-panel.tsx` and any installed copy byte-identical when testing
129
+ the panel locally.
package/CHANGELOG.md ADDED
@@ -0,0 +1,55 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.3] - 2026-09-12
11
+
12
+ ### Added
13
+
14
+ - npm distribution: `npm install -g opencode-sesh`, with `sesh install` /
15
+ `sesh uninstall` wrappers and a tag-triggered publish workflow.
16
+
17
+ ## [0.1.2] - 2026-09-12
18
+
19
+ ### Fixed
20
+
21
+ - The home-screen recent-sessions list had no transcript preview binding, so
22
+ pressing space while hovering a session typed into the prompt instead. It now
23
+ opens the same preview as the sidebar.
24
+
25
+ ## [0.1.1] - 2026-09-12
26
+
27
+ ### Changed
28
+
29
+ - Transcript previews (terminal picker and TUI panel) now show the newest
30
+ messages first instead of the oldest.
31
+
32
+ ## [0.1.0] - 2026-09-12
33
+
34
+ ### Added
35
+
36
+ - Fullscreen `fzf` terminal picker (`sesh`) listing every session across every
37
+ project directory, grouped by directory and sorted by recency.
38
+ - Full-text search over session titles and text-only conversation parts, with a
39
+ persistent, incrementally refreshed extraction cache.
40
+ - Transcript preview, rendered as Markdown via `glow` when available.
41
+ - Resume in place (`opencode --session <id>`) and fork (`--fork`) actions.
42
+ - Session deletion from the picker.
43
+ - `sesh-list` opencode tool so agents can list sessions across all directories.
44
+ - OpenTUI sidebar panel and `ctrl+o` / `/sesh` picker plugin, themed by the
45
+ active opencode theme.
46
+ - `install.sh` / `--uninstall` installer that links the picker and merges the
47
+ TUI plugin into the opencode config.
48
+ - Fixture-database regression suite, TypeScript typechecking, and ShellCheck in
49
+ CI.
50
+
51
+ [Unreleased]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.1.3...HEAD
52
+ [0.1.3]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.1.2...v0.1.3
53
+ [0.1.2]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.1.1...v0.1.2
54
+ [0.1.1]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.1.0...v0.1.1
55
+ [0.1.0]: https://github.com/ozandogrultan/opencode-sesh/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ozan Dogrultan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # sesh
2
+
3
+ **A session browser for [opencode](https://opencode.ai).** Search every session
4
+ across every project, preview the transcript, and resume in place — from your
5
+ terminal or from inside the TUI.
6
+
7
+ `opencode` keeps a growing pile of sessions, and its native lists (`<leader>l`,
8
+ `/sessions`) are scoped to the current project. `sesh` shows all of them —
9
+ grouped by directory, full-text searchable, and previewable from anywhere.
10
+
11
+ [![CI](https://github.com/ozandogrultan/opencode-sesh/actions/workflows/ci.yml/badge.svg)](https://github.com/ozandogrultan/opencode-sesh/actions/workflows/ci.yml)
12
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
13
+ [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](#contributing)
14
+ [![GitHub stars](https://img.shields.io/github/stars/ozandogrultan/opencode-sesh?style=social)](https://github.com/ozandogrultan/opencode-sesh/stargazers)
15
+
16
+ ```text
17
+ ❯ auth
18
+ ~/code/api (8)
19
+ ├ Retry the OAuth token refresh 2h
20
+ ├ Login rate limiting notes 3d
21
+ └ auth middleware cleanup 11d
22
+ ~/code/web (3)
23
+ ├ Fix session cookie on Safari 1d
24
+ └ Redirect loop after logout 6d
25
+ ```
26
+
27
+ ## Why sesh
28
+
29
+ - **Every session, everywhere.** Sessions are grouped by project directory and
30
+ sorted by recency — across *all* your projects, not just the current one.
31
+ - **Full-text search.** Type to match against session titles *and* the text of
32
+ the conversation itself. Reasoning and tool output are excluded, so the index
33
+ stays clean.
34
+ - **Transcript preview.** Space shows the most recent messages first, rendered as
35
+ Markdown (with `glow` if you have it).
36
+ - **Resume in place.** Enter `exec`s `opencode --session <id>` in the session's
37
+ own directory. Ctrl-F forks instead. Your terminal becomes the session — no
38
+ tabs, no panes, no window management.
39
+ - **Native TUI, too.** A recent-sessions section in the opencode sidebar plus a
40
+ `ctrl+o` picker, both themed by your opencode theme.
41
+ - **Fast.** The database is scanned once per refresh and cached; keystrokes only
42
+ re-render the last snapshot, so typing never triggers a query storm.
43
+
44
+ ## Install
45
+
46
+ With npm:
47
+
48
+ ```bash
49
+ npm install -g opencode-sesh
50
+ sesh install
51
+ ```
52
+
53
+ Or from source:
54
+
55
+ ```bash
56
+ git clone https://github.com/ozandogrultan/opencode-sesh.git
57
+ cd opencode-sesh
58
+ bash install.sh
59
+ ```
60
+
61
+ `sesh install` (or `bash install.sh`) links `sesh` into `~/.local/bin`, copies
62
+ the `sesh-list` tool and the TUI panel into
63
+ `${XDG_CONFIG_HOME:-~/.config}/opencode`, registers the panel in `tui.json`, and
64
+ declares the plugin dependencies (opencode installs them on next start). The
65
+ panel registers the `/sesh` slash command. It never edits your shell rc, and any
66
+ file it would overwrite is backed up first.
67
+
68
+ The npm package ships the same scripts and installer; the runtime tools below
69
+ are still required.
70
+
71
+ Restart opencode to load the panel, then:
72
+
73
+ ```bash
74
+ sesh # terminal picker
75
+ ```
76
+
77
+ ```text
78
+ /sesh # inside opencode — picker (TUI plugin)
79
+ ctrl+o # inside opencode — picker
80
+ ```
81
+
82
+ ### Requirements
83
+
84
+ - [opencode](https://opencode.ai), used at least once
85
+ - `bash`, `jq`, and [`fzf`](https://github.com/junegunn/fzf) `>= 0.52`
86
+ - `sqlite3` recommended — the picker queries the session DB directly in
87
+ milliseconds and falls back to the slower `opencode db` CLI without it
88
+ - [`glow`](https://github.com/charmbracelet/glow) optional — styles the
89
+ transcript preview as Markdown; without it the preview shows the plain
90
+ Markdown, unchanged otherwise
91
+
92
+ ### Uninstall
93
+
94
+ ```bash
95
+ sesh uninstall # npm installs
96
+ bash install.sh --uninstall # from source
97
+ ```
98
+
99
+ ## Usage
100
+
101
+ ### Terminal picker
102
+
103
+ | Key | Action |
104
+ | --- | --- |
105
+ | Type | Search titles and transcript text across all directories |
106
+ | Enter | Resume the selected session in this terminal |
107
+ | Ctrl-F Enter | Resume as a fork (the original is untouched) |
108
+ | Ctrl-G | Toggle current-directory scope / all sessions |
109
+ | Space (empty query) | Toggle the transcript preview |
110
+ | `?` (empty query) | Toggle shortcut help |
111
+ | Ctrl-X | Delete the selected session |
112
+ | Escape | Exit |
113
+
114
+ Flags: `--cwd` (current directory only), `--limit N` (default: all),
115
+ `--archived`, `--print` (print `id<TAB>cwd` instead of resuming), `--fork`,
116
+ `--query TEXT`, `--check`.
117
+
118
+ Because `--print` just emits the id and directory, `sesh` doubles as a scriptable
119
+ session lookup:
120
+
121
+ ```bash
122
+ read -r id cwd < <(sesh --print --query "auth")
123
+ ```
124
+
125
+ ### TUI panel
126
+
127
+ | Key | Action |
128
+ | --- | --- |
129
+ | `/sesh` | Open the full picker |
130
+ | `ctrl+o` | Open the full picker (also in the command palette) |
131
+ | Type | Search titles, directories and transcript text |
132
+ | `↑`/`↓`, `PgUp`/`PgDn`, `Home`/`End` | Move the selection |
133
+ | Space | Toggle the transcript preview |
134
+ | Enter | Open the selected session |
135
+ | Esc | Close |
136
+
137
+ Click the sidebar's `search…` box to filter recent sessions in place; click
138
+ elsewhere or press Esc to leave search.
139
+
140
+ ## Configuration
141
+
142
+ All variables are optional.
143
+
144
+ | Variable | Default | Effect |
145
+ | --- | --- | --- |
146
+ | `SESH_DB` | `opencode db path` | opencode SQLite database |
147
+ | `SESH_SQLITE` | `sqlite3` | query tool (`opencode db` is ~300 ms/call) |
148
+ | `SESH_JQ` / `SESH_FZF` | `PATH` | explicit executable overrides |
149
+ | `SESH_GLOW` | first `glow` on `PATH` | optional Markdown preview renderer |
150
+ | `SESH_OPENCODE` | `opencode` | opencode executable (resume / delete) |
151
+ | `SESH_CACHE_DIR` | `~/.cache/sesh` | persistent search-index cache |
152
+
153
+ ## How it works
154
+
155
+ `bin/sesh-list.sh` is the only place that reads the opencode session store
156
+ (SQLite: `session` / `message` / `part`). Each refresh extracts session metadata
157
+ and text-only parts into a persistent per-session cache keyed on `time_updated`
158
+ and part count, then atomically publishes a `snapshot.jsonl`. Every keystroke
159
+ re-renders that snapshot with `jq` alone, so typing never starts competing
160
+ database scans. Reasoning and tool payloads are never indexed or previewed, and
161
+ session ids are validated before any SQL interpolation.
162
+
163
+ Four flat scripts back the terminal UI — the picker (`sesh.sh`), snapshot
164
+ builder (`sesh-list.sh`), preview renderer (`sesh-preview.sh`) and deleter
165
+ (`sesh-delete.sh`) — with one worker process per picker serializing refreshes.
166
+ The TUI panel (`tui/sesh-panel.tsx`) is a SolidJS OpenTUI plugin that talks to
167
+ the opencode SDK over the same store.
168
+
169
+ ## FAQ
170
+
171
+ **Does it replace opencode's native session list?** No. `<leader>l` and the
172
+ native `/sessions` command are untouched; `sesh` is additive.
173
+
174
+ **Is there an agent-facing list?** Yes — the `sesh-list` tool lets the model list
175
+ every session across all directories (the native `opencode session list` only
176
+ covers the current project) and offer to resume one.
177
+
178
+ **Where is my data?** It reads the opencode database read-only and caches
179
+ extracted text under `~/.cache/sesh`. Nothing is uploaded anywhere.
180
+
181
+ **Does it work on Windows?** It targets macOS and Linux. WSL should work; native
182
+ Windows is untested.
183
+
184
+ **Why a separate terminal picker *and* a TUI panel?** The panel is always one
185
+ keystroke away while you work. The terminal picker is fullscreen and works from
186
+ any shell, including outside opencode.
187
+
188
+ ## Contributing
189
+
190
+ Issues and PRs are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full
191
+ guide; this project follows the [Code of Conduct](CODE_OF_CONDUCT.md). Run the
192
+ checks before opening a PR:
193
+
194
+ ```bash
195
+ npm install
196
+ npm test # fixture-database regression suite
197
+ npm run typecheck # tsc over tui/ and opencode/
198
+ npm run lint:sh # bash -n on every script
199
+ ```
200
+
201
+ `HOME=/tmp/fakehome bash install.sh` smoke-tests the installer without touching
202
+ your real opencode config. [AGENTS.md](AGENTS.md) documents the architecture and
203
+ the hard-won TUI rules.
204
+
205
+ ## License
206
+
207
+ [MIT](LICENSE)
package/bin/sesh ADDED
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+ set -e
3
+ source_path=${BASH_SOURCE[0]}
4
+ links=0
5
+ while [ -L "$source_path" ]; do
6
+ links=$((links + 1))
7
+ [ "$links" -le 40 ] || { echo 'sesh: launcher symlink loop.' >&2; exit 1; }
8
+ source_dir="$(cd "$(dirname "$source_path")" && pwd -P)"
9
+ link_target=$(readlink "$source_path")
10
+ case "$link_target" in
11
+ /*) source_path=$link_target ;;
12
+ *) source_path="$source_dir/$link_target" ;;
13
+ esac
14
+ done
15
+ SCRIPT_DIR="$(cd "$(dirname "$source_path")" && pwd -P)"
16
+ case ${1:-} in
17
+ install)
18
+ shift
19
+ exec bash "$SCRIPT_DIR/../install.sh" "$@"
20
+ ;;
21
+ uninstall)
22
+ shift
23
+ exec bash "$SCRIPT_DIR/../install.sh" --uninstall "$@"
24
+ ;;
25
+ esac
26
+ exec "$SCRIPT_DIR/sesh.sh" "$@"
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bash
2
+ # Usage: sesh-delete.sh STATE_DIR SESSION_ID
3
+ # Ctrl-X deletes through the opencode CLI so its own safeguards apply.
4
+ # There is no confirmation prompt, matching the Claude picker.
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ state_dir=${1:-}
9
+ session_id=${2:-}
10
+ snapshot="$state_dir/snapshot.jsonl"
11
+ lock="$state_dir/refresh.lock"
12
+ OPENCODE_BIN=${SESH_OPENCODE:-opencode}
13
+ JQ_BIN=${SESH_JQ:-jq}
14
+
15
+ lock_owned=0
16
+ pause() { release_lock; read -r -n 1 -s -p "Press any key to return..." _ || true; echo; }
17
+ release_lock() {
18
+ [ "$lock_owned" = 1 ] || return 0
19
+ rm -f "$lock/pid" 2>/dev/null || true
20
+ rmdir "$lock" 2>/dev/null || true
21
+ lock_owned=0
22
+ }
23
+ # Mirrors the acquirer in sesh-list.sh, including its stale-owner
24
+ # recovery. Recovery is conservative -- an unreadable or non-numeric owner,
25
+ # or one still alive, always loses the race.
26
+ acquire_lock() {
27
+ if mkdir "$lock" 2>/dev/null; then printf '%s\n' "$$" > "$lock/pid"; return 0; fi
28
+ local owner=''; [ -f "$lock/pid" ] && owner=$(cat "$lock/pid" 2>/dev/null || true)
29
+ case "$owner" in ''|*[!0-9]*) return 1;; esac
30
+ kill -0 "$owner" 2>/dev/null && return 1
31
+ rm -f "$lock/pid" 2>/dev/null || return 1
32
+ rmdir "$lock" 2>/dev/null || return 1
33
+ mkdir "$lock" 2>/dev/null || return 1
34
+ printf '%s\n' "$$" > "$lock/pid"
35
+ }
36
+
37
+ # fzf passes an empty second field for headers and notices. They are never
38
+ # actions, and should not surface a misleading failure prompt.
39
+ [ -n "$session_id" ] || exit 0
40
+ case "$session_id" in ses_[A-Za-z0-9]*) ;; *) echo "Invalid session identity."; pause; exit 0;; esac
41
+ [ -n "$state_dir" ] && [ -f "$snapshot" ] || { echo "No session snapshot available."; pause; exit 0; }
42
+
43
+ # Serialize this action with the sole snapshot publisher. A short collision
44
+ # is safer than deleting against a moving snapshot.
45
+ if ! acquire_lock; then
46
+ echo "Session list is refreshing; try deletion again in a moment."
47
+ pause
48
+ exit 0
49
+ fi
50
+ lock_owned=1
51
+ trap 'release_lock' EXIT
52
+ trap 'exit 130' HUP INT TERM
53
+
54
+ if ! "$JQ_BIN" -se --arg sid "$session_id" 'any(.sessionId == $sid)' "$snapshot" >/dev/null 2>&1; then
55
+ echo "The selected session is no longer in this snapshot."
56
+ pause
57
+ exit 0
58
+ fi
59
+
60
+ command -v "$OPENCODE_BIN" >/dev/null 2>&1 || { echo "Refusing deletion: opencode executable is unavailable."; pause; exit 0; }
61
+ "$OPENCODE_BIN" session delete "$session_id" || { echo "opencode could not delete the session."; pause; exit 1; }
62
+ echo "Session deleted."
63
+
64
+ SESH_LOCK_HELD="$state_dir" "$SCRIPT_DIR/sesh-list.sh" --refresh --state-dir "$state_dir" >/dev/null || true
65
+ release_lock
66
+ trap - EXIT HUP INT TERM