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 +129 -0
- package/CHANGELOG.md +55 -0
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/bin/sesh +26 -0
- package/bin/sesh-delete.sh +66 -0
- package/bin/sesh-list.sh +294 -0
- package/bin/sesh-preview.sh +68 -0
- package/bin/sesh-refresh-worker.sh +162 -0
- package/bin/sesh-shortcuts.sh +17 -0
- package/bin/sesh.sh +150 -0
- package/install.sh +241 -0
- package/opencode/tools/sesh-list.ts +42 -0
- package/package.json +57 -0
- package/themes/glow-dark-clean.json +191 -0
- package/tui/sesh-panel.tsx +1177 -0
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
|
+
[](https://github.com/ozandogrultan/opencode-sesh/actions/workflows/ci.yml)
|
|
12
|
+
[](LICENSE)
|
|
13
|
+
[](#contributing)
|
|
14
|
+
[](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
|