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/bin/sesh.sh ADDED
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env bash
2
+ # Pick an opencode session. Native to opencode, terminal-agnostic: no iTerm2,
3
+ # no AppleScript, no panes, no window management. Stock fzf is the only UI.
4
+ # Refreshes publish one state-dir snapshot; fzf query changes render that
5
+ # snapshot only, so typing never starts competing scans.
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+
10
+ resolve_tool() {
11
+ local configured=$1 fallback=$2
12
+ if [ -n "$configured" ]; then
13
+ case "$configured" in /*) [ -x "$configured" ] && { printf '%s\n' "$configured"; return; };; esac
14
+ return 1
15
+ fi
16
+ command -v "$fallback"
17
+ }
18
+ SESH_FZF=$(resolve_tool "${SESH_FZF:-}" fzf) || { echo 'sesh: fzf executable unavailable' >&2; exit 1; }
19
+ SESH_JQ=$(resolve_tool "${SESH_JQ:-}" jq) || { echo 'sesh: jq executable unavailable' >&2; exit 1; }
20
+ export SESH_FZF SESH_JQ
21
+ OPENCODE_BIN=${SESH_OPENCODE:-opencode}
22
+
23
+ version=$("$SESH_FZF" --version 2>/dev/null || true)
24
+ # --bind transform: and start,every():reload-sync need a modern stock fzf.
25
+ if [[ "$version" =~ ^([0-9]+)\.([0-9]+)(\.([0-9]+))? ]]; then
26
+ fzf_major=$((10#${BASH_REMATCH[1]}))
27
+ fzf_minor=$((10#${BASH_REMATCH[2]}))
28
+ if [ "$fzf_major" -eq 0 ] && [ "$fzf_minor" -lt 52 ]; then
29
+ echo "sesh: fzf >= 0.52.0 is required (found $version)" >&2
30
+ exit 1
31
+ fi
32
+ else
33
+ echo "sesh: cannot parse fzf version from: $version" >&2
34
+ exit 1
35
+ fi
36
+
37
+ if [ "${1:-}" = --check ]; then
38
+ "$SESH_JQ" -en '"2026-01-01T00:00:00Z" | fromdateiso8601 | type == "number"' >/dev/null || { echo 'jq capability check failed' >&2; exit 1; }
39
+ command -v sqlite3 >/dev/null 2>&1 || command -v "$OPENCODE_BIN" >/dev/null 2>&1 || { echo 'neither sqlite3 nor opencode is available' >&2; exit 1; }
40
+ echo 'sesh: dependencies ready'
41
+ exit 0
42
+ fi
43
+
44
+ scope_value=''
45
+ picker_limit=0
46
+ picker_archived=0
47
+ picker_print=0
48
+ picker_fork=0
49
+ picker_query=''
50
+ while [ "$#" -gt 0 ]; do
51
+ case "$1" in
52
+ --cwd) scope_value=$PWD ;;
53
+ --limit) picker_limit=${2:-}; shift ;;
54
+ --archived) picker_archived=1 ;;
55
+ --print) picker_print=1 ;;
56
+ --fork) picker_fork=1 ;;
57
+ --query) picker_query=${2:-}; shift ;;
58
+ *) echo "unknown option: $1" >&2; exit 2 ;;
59
+ esac
60
+ shift
61
+ done
62
+ case "$picker_limit" in ''|*[!0-9]*) echo "--limit must be a non-negative integer" >&2; exit 2;; esac
63
+
64
+ shell_quote() {
65
+ # Quotes fixed command arguments embedded in fzf's shell action strings.
66
+ # Selection data never crosses this boundary; fzf supplies {q}/{2} safely.
67
+ printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\"'\\\"'/g")"
68
+ }
69
+
70
+ state_dir=$(mktemp -d "${TMPDIR:-/tmp}/sesh.XXXXXX")
71
+ chmod 700 "$state_dir"
72
+
73
+ q_list=$(shell_quote "$SCRIPT_DIR/sesh-list.sh")
74
+ q_worker=$(shell_quote "$SCRIPT_DIR/sesh-refresh-worker.sh")
75
+ q_preview=$(shell_quote "$SCRIPT_DIR/sesh-preview.sh")
76
+ q_delete=$(shell_quote "$SCRIPT_DIR/sesh-delete.sh")
77
+ q_shortcuts=$(shell_quote "$SCRIPT_DIR/sesh-shortcuts.sh")
78
+ q_state=$(shell_quote "$state_dir")
79
+ export SESH_LIST="$SCRIPT_DIR/sesh-list.sh"
80
+ # The shared worker persists scope/limit; the scope toggle and the archived
81
+ # filter need this picker's directory and flags too, so merge them up front.
82
+ "$SCRIPT_DIR/sesh-refresh-worker.sh" init --state-dir "$state_dir" --scope "$scope_value" --limit "$picker_limit"
83
+ "$SESH_JQ" -c --arg cwd "$PWD" --argjson archived "$picker_archived" '.cwd = $cwd | .archived = $archived' "$state_dir/settings.json" > "$state_dir/settings.tmp" && mv "$state_dir/settings.tmp" "$state_dir/settings.json"
84
+ "$SCRIPT_DIR/sesh-refresh-worker.sh" worker --state-dir "$state_dir" &
85
+ worker_pid=$!
86
+ cleanup() {
87
+ # Reap this picker's exact worker before its private state disappears.
88
+ kill -TERM "$worker_pid" 2>/dev/null || true
89
+ wait "$worker_pid" 2>/dev/null || true
90
+ rm -rf "$state_dir"
91
+ }
92
+ trap cleanup EXIT
93
+ trap 'exit 130' HUP INT TERM
94
+
95
+ cache_cmd="$q_worker render --state-dir $q_state --query {q}"
96
+ preview_cmd="if [ -f $q_state/help ]; then $q_shortcuts; else $q_preview $q_state {2}; fi"
97
+ help_bind="?:transform:[ -z {q} ] && ( [ -f $q_state/help ] && rm -f $q_state/help && echo hide-preview || ( : > $q_state/help && echo show-preview+refresh-preview ) ) || echo 'put(?)'"
98
+ toggle_cmd="$q_list --toggle-scope --state-dir $q_state >/dev/null"
99
+ delete_cmd="$q_delete $q_state {2}"
100
+
101
+ query="$picker_query"
102
+ while :; do
103
+ selection=''
104
+ set +e
105
+ selection=$(FZF_DEFAULT_COMMAND='printf ""' FZF_DEFAULT_OPTS='' FZF_DEFAULT_OPTS_FILE='' "$SESH_FZF" --print-query --expect=ctrl-f --query="$query" --ansi --delimiter=$'\t' --with-nth=4 --disabled --layout=reverse --info=hidden --prompt='❯ ' \
106
+ --preview="$preview_cmd" --preview-window='right,50%,wrap,hidden' \
107
+ --bind='space:transform:[ -z {q} ] && echo toggle-preview || echo "put( )"' \
108
+ --bind="$help_bind" \
109
+ --bind="ctrl-x:execute($delete_cmd)+reload($cache_cmd)" \
110
+ --bind="ctrl-g:execute($toggle_cmd)+reload($cache_cmd)" \
111
+ --bind="change:reload:$cache_cmd" \
112
+ --bind="start,every(3):reload-sync:$cache_cmd")
113
+ status=$?
114
+ set -e
115
+ case "$status" in 0) ;; 1|130) exit 0;; *) echo "sesh: picker failed ($status)" >&2; exit "$status";; esac
116
+ key=${selection%%$'\n'*}
117
+ selection=${selection#*$'\n'}
118
+ query=${selection%%$'\n'*}
119
+ selection=${selection#*$'\n'}
120
+ session_id=$(printf '%s\n' "$selection" | cut -f2)
121
+ [ -n "$session_id" ] || continue
122
+ # Dispatch queues a new worker scan and waits for that exact serialized poll.
123
+ # It cannot accept a previously fresh status while the worker owns its lock.
124
+ poll_target=$("$SCRIPT_DIR/sesh-refresh-worker.sh" request --state-dir "$state_dir")
125
+ if ! "$SCRIPT_DIR/sesh-refresh-worker.sh" wait --state-dir "$state_dir" --target "$poll_target" --timeout 12; then
126
+ echo 'Live state unavailable; retry selection after refresh' >&2
127
+ continue
128
+ fi
129
+ record=$("$SESH_JQ" -c --arg sid "$session_id" 'select(.sessionId==$sid)' "$state_dir/snapshot.jsonl")
130
+ [ -n "$record" ] || continue
131
+ cwd=$(printf '%s' "$record" | "$SESH_JQ" -r '.cwd')
132
+ fork=0
133
+ if [ "$picker_fork" = 1 ] || [ "$key" = ctrl-f ]; then fork=1; fi
134
+ if [ "$picker_print" = 1 ]; then
135
+ if [ "$fork" = 1 ]; then printf '%s\t%s\tfork\n' "$session_id" "$cwd"; else printf '%s\t%s\n' "$session_id" "$cwd"; fi
136
+ exit 0
137
+ fi
138
+ if [ ! -d "$cwd" ]; then
139
+ echo 'sesh: session directory is unavailable; returning to the picker.' >&2
140
+ continue
141
+ fi
142
+ command -v "$OPENCODE_BIN" >/dev/null 2>&1 || { echo 'sesh: opencode executable unavailable' >&2; exit 1; }
143
+ # Resume in place: this terminal becomes the session. No tabs, no panes.
144
+ if [ "$fork" = 1 ]; then
145
+ ( cd "$cwd" && exec "$OPENCODE_BIN" --session "$session_id" --fork )
146
+ else
147
+ ( cd "$cwd" && exec "$OPENCODE_BIN" --session "$session_id" )
148
+ fi
149
+ echo 'sesh: opencode exited; returning to the picker.' >&2
150
+ done
package/install.sh ADDED
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env bash
2
+ # Install sesh for the current user.
3
+ # Terminal- and OS-agnostic: macOS and Linux, any terminal. No administrator
4
+ # privileges, downloads, or shell-rc edits. Runtime requires jq, fzf >= 0.52
5
+ # and sqlite3 or the opencode CLI; the in-TUI panel additionally needs the
6
+ # packages opencode installs for local plugins (handled below).
7
+ set -euo pipefail
8
+
9
+ fail() { printf 'sesh install: %s\n' "$*" >&2; exit 1; }
10
+ note() { printf 'sesh install: %s\n' "$*"; }
11
+
12
+ SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
13
+
14
+ # Follow the same XDG locations opencode itself reads.
15
+ CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
16
+ CONFIG_DIR="$CONFIG_HOME/opencode"
17
+ BIN_DIR="${XDG_BIN_HOME:-$HOME/.local/bin}"
18
+
19
+ INSTALL_TOOL=1
20
+ INSTALL_TUI=1
21
+ UNINSTALL=0
22
+
23
+ # Fallback plugin version for when the opencode CLI is unavailable; the
24
+ # installer prefers the local opencode version so the panel matches its SDK.
25
+ PLUGIN_VERSION_FALLBACK="1.18.30"
26
+ # Peer line the panel was developed and typechecked against. A checkout's
27
+ # package.json overrides these, so dependency bumps cannot drift the installed
28
+ # config out of sync (solid-js moves with OpenTUI via @opentui/keymap's peer).
29
+ OPENTUI_RANGE="^0.5.11"
30
+ SOLID_VERSION="1.9.12"
31
+ if [ -f "$SOURCE_DIR/package.json" ] && command -v jq >/dev/null 2>&1; then
32
+ opentui_range=$(jq -r '.devDependencies["@opentui/solid"] // empty' "$SOURCE_DIR/package.json" 2>/dev/null || true)
33
+ solid_version=$(jq -r '.devDependencies["solid-js"] // empty' "$SOURCE_DIR/package.json" 2>/dev/null || true)
34
+ if [ -n "$opentui_range" ]; then OPENTUI_RANGE=$opentui_range; fi
35
+ if [ -n "$solid_version" ]; then SOLID_VERSION=$solid_version; fi
36
+ fi
37
+
38
+ usage() {
39
+ cat <<'USAGE'
40
+ Usage: bash install.sh [--bin-dir DIR] [--no-tool] [--no-tui] [--uninstall]
41
+
42
+ --bin-dir DIR Directory for the sesh launcher (default: ~/.local/bin).
43
+ --no-tool Skip installing the sesh-list custom tool.
44
+ --no-tui Skip installing the in-TUI sessions panel plugin.
45
+ --uninstall Remove everything this script installed.
46
+ --help Show this help.
47
+ USAGE
48
+ }
49
+
50
+ while [ "$#" -gt 0 ]; do
51
+ case "$1" in
52
+ --bin-dir) [ "$#" -ge 2 ] || fail "--bin-dir needs a directory."; BIN_DIR="$2"; shift 2 ;;
53
+ --no-tool) INSTALL_TOOL=0; shift ;;
54
+ --no-tui) INSTALL_TUI=0; shift ;;
55
+ --uninstall) UNINSTALL=1; shift ;;
56
+ --help) usage; exit 0 ;;
57
+ *) fail "unknown option: $1" ;;
58
+ esac
59
+ done
60
+
61
+ LAUNCHER="$BIN_DIR/sesh"
62
+ # `/sesh` is registered by the TUI plugin (a markdown command cannot open the
63
+ # dialog); this path only exists so we can clean up an obsolete install.
64
+ COMMAND_DST="$CONFIG_DIR/commands/sesh.md"
65
+ TOOL_DST="$CONFIG_DIR/tools/sesh-list.ts"
66
+ PANEL_DST="$CONFIG_DIR/plugins/sesh-panel.tsx"
67
+ PACKAGE_JSON="$CONFIG_DIR/package.json"
68
+ TUI_JSON="$CONFIG_DIR/tui.json"
69
+
70
+ # Remove a file only when it carries our marker, so a user's own file with a
71
+ # colliding name is never destroyed.
72
+ remove_if_marker() {
73
+ local file=$1 marker=$2
74
+ if [ -f "$file" ] && grep -qF "$marker" "$file" 2>/dev/null; then
75
+ rm -f "$file"
76
+ return 0
77
+ fi
78
+ return 1
79
+ }
80
+
81
+ remove_tui_entry() {
82
+ [ -f "$TUI_JSON" ] || return 0
83
+ command -v jq >/dev/null 2>&1 || return 0
84
+ local tmp
85
+ tmp=$(mktemp "${TMPDIR:-/tmp}/sesh-tui.XXXXXX") || return 0
86
+ if jq 'if (.plugin | type) == "array" then .plugin = [.plugin[] | select(. != "./plugins/sesh-panel.tsx")] else . end' "$TUI_JSON" > "$tmp" 2>/dev/null; then
87
+ cmp -s "$TUI_JSON" "$tmp" || { cp -p "$TUI_JSON" "$TUI_JSON.bak.$(date +%Y%m%d%H%M%S)"; mv "$tmp" "$TUI_JSON"; }
88
+ rm -f "$tmp"
89
+ else
90
+ rm -f "$tmp"
91
+ fi
92
+ }
93
+
94
+ if [ "$UNINSTALL" = 1 ]; then
95
+ [ -L "$LAUNCHER" ] && rm -f "$LAUNCHER" && note "removed $LAUNCHER"
96
+ remove_if_marker "$COMMAND_DST" "rich interactive picker" && note "removed $COMMAND_DST"
97
+ remove_if_marker "$TOOL_DST" "list opencode sessions for the agent" && note "removed $TOOL_DST"
98
+ remove_if_marker "$PANEL_DST" "@jsxImportSource @opentui/solid" && note "removed $PANEL_DST"
99
+ remove_tui_entry && note "unregistered the panel from $TUI_JSON"
100
+ note "uninstalled (extraction cache and plugin dependencies left intact)"
101
+ exit 0
102
+ fi
103
+
104
+ for path in \
105
+ bin/sesh \
106
+ bin/sesh.sh \
107
+ bin/sesh-list.sh \
108
+ bin/sesh-preview.sh \
109
+ bin/sesh-delete.sh \
110
+ bin/sesh-refresh-worker.sh \
111
+ bin/sesh-shortcuts.sh \
112
+ opencode/tools/sesh-list.ts \
113
+ tui/sesh-panel.tsx \
114
+ themes/glow-dark-clean.json; do
115
+ [ -f "$SOURCE_DIR/$path" ] || fail "installer bundle is incomplete: missing $path."
116
+ done
117
+
118
+ for tool in bash jq fzf; do
119
+ command -v "$tool" >/dev/null 2>&1 || fail "required tool '$tool' is unavailable."
120
+ done
121
+ fzf_version=$(fzf --version 2>/dev/null || true)
122
+ if [[ "$fzf_version" =~ ^([0-9]+)\.([0-9]+)(\.([0-9]+))? ]]; then
123
+ if [ "$((10#${BASH_REMATCH[1]}))" -eq 0 ] && [ "$((10#${BASH_REMATCH[2]}))" -lt 52 ]; then
124
+ fail "fzf >= 0.52.0 is required (found $fzf_version)."
125
+ fi
126
+ else
127
+ fail "cannot parse fzf version from: $fzf_version."
128
+ fi
129
+ command -v sqlite3 >/dev/null 2>&1 || command -v opencode >/dev/null 2>&1 \
130
+ || fail "either sqlite3 or the opencode CLI is required."
131
+ command -v opencode >/dev/null 2>&1 || note "opencode CLI not found: listing and previews work via sqlite3, but resume and delete need opencode."
132
+
133
+ umask 077
134
+ mkdir -p "$BIN_DIR"
135
+ if [ -e "$LAUNCHER" ] && [ ! -L "$LAUNCHER" ]; then
136
+ fail "$LAUNCHER already exists and is not a symlink. Move it aside, then retry."
137
+ fi
138
+ ln -sfn "$SOURCE_DIR/bin/sesh" "$LAUNCHER"
139
+ note "linked $LAUNCHER -> $SOURCE_DIR/bin/sesh"
140
+
141
+ install_file() {
142
+ local src=$1 dst=$2
143
+ mkdir -p "$(dirname "$dst")"
144
+ if [ -f "$dst" ] && cmp -s "$src" "$dst"; then
145
+ note "up to date $dst"
146
+ return 0
147
+ fi
148
+ if [ -f "$dst" ]; then
149
+ cp -p "$dst" "$dst.bak.$(date +%Y%m%d%H%M%S)"
150
+ note "backed up existing $(basename "$dst")"
151
+ fi
152
+ cp -p "$src" "$dst"
153
+ note "installed $dst"
154
+ }
155
+
156
+ # Earlier versions shipped a markdown `/sesh` command that could only prompt the
157
+ # agent; the TUI plugin now registers the slash itself. Drop the old file so the
158
+ # two do not collide.
159
+ remove_if_marker "$COMMAND_DST" "rich interactive picker" && note "removed obsolete markdown command $COMMAND_DST"
160
+ [ "$INSTALL_TOOL" = 1 ] && install_file "$SOURCE_DIR/opencode/tools/sesh-list.ts" "$TOOL_DST"
161
+
162
+ opencode_version=''
163
+ if command -v opencode >/dev/null 2>&1; then
164
+ raw_version=$(opencode --version 2>/dev/null || true)
165
+ if [[ "$raw_version" =~ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then opencode_version="${BASH_REMATCH[1]}"; fi
166
+ fi
167
+ plugin_version="${opencode_version:-$PLUGIN_VERSION_FALLBACK}"
168
+
169
+ if [ "$INSTALL_TUI" = 1 ]; then
170
+ install_file "$SOURCE_DIR/tui/sesh-panel.tsx" "$PANEL_DST"
171
+
172
+ # opencode has no persistent-sidebar API: the panel is an xlarge modal
173
+ # dialog (see tui/sesh-panel.tsx). Register it idempotently and drop the
174
+ # previous filename if an older install left it behind.
175
+ tui_created=0
176
+ if [ ! -f "$TUI_JSON" ]; then
177
+ printf '{\n "$schema": "https://opencode.ai/tui.json",\n "plugin": []\n}\n' > "$TUI_JSON"
178
+ tui_created=1
179
+ note "created $TUI_JSON"
180
+ fi
181
+ tui_tmp=$(mktemp "${TMPDIR:-/tmp}/sesh-tui.XXXXXX")
182
+ if jq '
183
+ (.plugin // []) as $plugins
184
+ | ($plugins | map(select(. != "./plugins/sessions-panel.tsx"))) as $clean
185
+ | .plugin = (if ($clean | index("./plugins/sesh-panel.tsx")) then $clean else $clean + ["./plugins/sesh-panel.tsx"] end)
186
+ ' "$TUI_JSON" > "$tui_tmp" 2>/dev/null; then
187
+ if cmp -s "$TUI_JSON" "$tui_tmp"; then
188
+ note "tui.json already registers the sesh panel"
189
+ rm -f "$tui_tmp"
190
+ else
191
+ [ "$tui_created" = 1 ] || cp -p "$TUI_JSON" "$TUI_JSON.bak.$(date +%Y%m%d%H%M%S)"
192
+ mv "$tui_tmp" "$TUI_JSON"
193
+ note "registered ./plugins/sesh-panel.tsx in tui.json"
194
+ fi
195
+ else
196
+ rm -f "$tui_tmp"
197
+ note "warning: could not update $TUI_JSON; add \"./plugins/sesh-panel.tsx\" to its plugin list by hand"
198
+ fi
199
+
200
+ # Local plugins resolve their imports from the config directory, and opencode
201
+ # runs `bun install` there at startup. Keep the required packages declared
202
+ # without clobbering versions the user already pinned.
203
+ pkg_created=0
204
+ if [ ! -f "$PACKAGE_JSON" ]; then
205
+ printf '{\n "dependencies": {}\n}\n' > "$PACKAGE_JSON"
206
+ pkg_created=1
207
+ note "created $PACKAGE_JSON"
208
+ fi
209
+ pkg_tmp=$(mktemp "${TMPDIR:-/tmp}/sesh-pkg.XXXXXX")
210
+ if jq --arg p "$plugin_version" --arg o "$OPENTUI_RANGE" --arg s "$SOLID_VERSION" '
211
+ .dependencies = ((.dependencies // {}) as $d
212
+ | {
213
+ "@opencode-ai/plugin": ($d["@opencode-ai/plugin"] // $p),
214
+ "@opentui/core": ($d["@opentui/core"] // $o),
215
+ "@opentui/keymap": ($d["@opentui/keymap"] // $o),
216
+ "@opentui/solid": ($d["@opentui/solid"] // $o),
217
+ "solid-js": ($d["solid-js"] // $s)
218
+ } + $d)
219
+ ' "$PACKAGE_JSON" > "$pkg_tmp" 2>/dev/null; then
220
+ if cmp -s "$PACKAGE_JSON" "$pkg_tmp"; then
221
+ note "plugin dependencies already present in $PACKAGE_JSON"
222
+ rm -f "$pkg_tmp"
223
+ else
224
+ [ "$pkg_created" = 1 ] || cp -p "$PACKAGE_JSON" "$PACKAGE_JSON.bak.$(date +%Y%m%d%H%M%S)"
225
+ mv "$pkg_tmp" "$PACKAGE_JSON"
226
+ note "declared plugin dependencies in $PACKAGE_JSON (opencode installs them on next start)"
227
+ fi
228
+ else
229
+ rm -f "$pkg_tmp"
230
+ note "warning: could not update $PACKAGE_JSON; the TUI panel may fail to load"
231
+ fi
232
+ fi
233
+
234
+ "$LAUNCHER" --check || fail "post-install check failed."
235
+ note "done"
236
+ note " picker: run 'sesh' in any terminal, or '/sesh' inside opencode"
237
+ note " sidebar: restart opencode to load the panel (ctrl+o opens the full picker)"
238
+ case ":$PATH:" in
239
+ *":$BIN_DIR:"*) ;;
240
+ *) note " note: $BIN_DIR is not on PATH; add it to use the launcher anywhere." ;;
241
+ esac
@@ -0,0 +1,42 @@
1
+ // Custom tool: list opencode sessions for the agent.
2
+ // Install: copy to ~/.config/opencode/tools/sesh-list.ts (global) or
3
+ // .opencode/tools/sesh-list.ts (project). Requires the @opencode-ai/plugin
4
+ // package next to it — see https://opencode.ai/docs/custom-tools/
5
+ //
6
+ // Lets the agent browse previous work across ALL project directories and
7
+ // offer to resume one, without depending on the external fzf picker.
8
+ import { tool } from "@opencode-ai/plugin"
9
+
10
+ export default tool({
11
+ description: "List opencode sessions across all project directories, newest first",
12
+ args: {
13
+ limit: tool.schema
14
+ .number()
15
+ .describe("Maximum sessions to return")
16
+ .default(50),
17
+ directory: tool.schema
18
+ .string()
19
+ .describe("Only sessions from this directory (default: all directories)")
20
+ .optional(),
21
+ },
22
+ async execute(args) {
23
+ const bun = (globalThis as any).Bun
24
+ if (!bun || typeof bun.$ !== "function") {
25
+ throw new Error("This tool requires the Bun runtime (globalThis.Bun.$)")
26
+ }
27
+
28
+ const out = await bun.$`opencode session list -n ${args.limit} --format json`.json()
29
+ const sessions = (Array.isArray(out) ? out : []).filter(
30
+ (s) => !args.directory || s.directory === args.directory,
31
+ )
32
+ return JSON.stringify(
33
+ sessions.map((s) => ({
34
+ id: s.id,
35
+ title: s.title,
36
+ directory: s.directory,
37
+ updated: s.updated,
38
+ created: s.created,
39
+ })),
40
+ )
41
+ },
42
+ })
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "opencode-sesh",
3
+ "version": "0.1.3",
4
+ "type": "module",
5
+ "description": "Browse, search, preview and resume opencode sessions from the terminal and the TUI.",
6
+ "license": "MIT",
7
+ "author": "Ozan Dogrultan",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ozandogrultan/opencode-sesh.git"
11
+ },
12
+ "homepage": "https://github.com/ozandogrultan/opencode-sesh#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/ozandogrultan/opencode-sesh/issues"
15
+ },
16
+ "keywords": [
17
+ "opencode",
18
+ "sessions",
19
+ "session-browser",
20
+ "fzf",
21
+ "tui",
22
+ "cli"
23
+ ],
24
+ "bin": {
25
+ "sesh": "bin/sesh"
26
+ },
27
+ "files": [
28
+ "bin/",
29
+ "opencode/",
30
+ "themes/",
31
+ "tui/",
32
+ "install.sh",
33
+ "AGENTS.md",
34
+ "CHANGELOG.md"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "engines": {
40
+ "node": ">=20"
41
+ },
42
+ "scripts": {
43
+ "test": "bash tests/sesh.sh",
44
+ "typecheck": "tsc -p tsconfig.json",
45
+ "lint:sh": "bash -n bin/*.sh bin/sesh install.sh tests/sesh.sh"
46
+ },
47
+ "devDependencies": {
48
+ "@opencode-ai/plugin": "1.18.30",
49
+ "@opencode-ai/sdk": "1.18.30",
50
+ "@opentui/core": "^0.5.11",
51
+ "@opentui/keymap": "^0.5.11",
52
+ "@opentui/solid": "^0.5.11",
53
+ "@types/node": "26.5.1",
54
+ "solid-js": "1.9.12",
55
+ "typescript": "7.0.2"
56
+ }
57
+ }
@@ -0,0 +1,191 @@
1
+ {
2
+ "document": {
3
+ "block_prefix": "\n",
4
+ "block_suffix": "\n",
5
+ "color": "252",
6
+ "margin": 2
7
+ },
8
+ "block_quote": {
9
+ "indent": 1,
10
+ "indent_token": "\u2502 "
11
+ },
12
+ "paragraph": {},
13
+ "list": {
14
+ "level_indent": 2
15
+ },
16
+ "heading": {
17
+ "block_suffix": "\n",
18
+ "color": "39",
19
+ "bold": true
20
+ },
21
+ "h1": {
22
+ "prefix": " ",
23
+ "suffix": " ",
24
+ "color": "228",
25
+ "background_color": "63",
26
+ "bold": true
27
+ },
28
+ "h2": {
29
+ "prefix": ""
30
+ },
31
+ "h3": {
32
+ "prefix": ""
33
+ },
34
+ "h4": {
35
+ "prefix": ""
36
+ },
37
+ "h5": {
38
+ "prefix": ""
39
+ },
40
+ "h6": {
41
+ "prefix": "",
42
+ "color": "35",
43
+ "bold": false
44
+ },
45
+ "text": {},
46
+ "strikethrough": {
47
+ "crossed_out": true
48
+ },
49
+ "emph": {
50
+ "italic": true
51
+ },
52
+ "strong": {
53
+ "bold": true
54
+ },
55
+ "hr": {
56
+ "color": "240",
57
+ "format": "\n--------\n"
58
+ },
59
+ "item": {
60
+ "block_prefix": "\u2022 "
61
+ },
62
+ "enumeration": {
63
+ "block_prefix": ". "
64
+ },
65
+ "task": {
66
+ "ticked": "[\u2713] ",
67
+ "unticked": "[ ] "
68
+ },
69
+ "link": {
70
+ "color": "30",
71
+ "underline": true
72
+ },
73
+ "link_text": {
74
+ "color": "35",
75
+ "bold": true
76
+ },
77
+ "image": {
78
+ "color": "212",
79
+ "underline": true
80
+ },
81
+ "image_text": {
82
+ "color": "243",
83
+ "format": "Image: {{.text}} \u2192"
84
+ },
85
+ "code": {
86
+ "prefix": "\u00a0",
87
+ "suffix": "\u00a0",
88
+ "color": "203",
89
+ "background_color": "236"
90
+ },
91
+ "code_block": {
92
+ "color": "244",
93
+ "margin": 2,
94
+ "chroma": {
95
+ "text": {
96
+ "color": "#C4C4C4"
97
+ },
98
+ "error": {
99
+ "color": "#F1F1F1",
100
+ "background_color": "#F05B5B"
101
+ },
102
+ "comment": {
103
+ "color": "#676767"
104
+ },
105
+ "comment_preproc": {
106
+ "color": "#FF875F"
107
+ },
108
+ "keyword": {
109
+ "color": "#00AAFF"
110
+ },
111
+ "keyword_reserved": {
112
+ "color": "#FF5FD2"
113
+ },
114
+ "keyword_namespace": {
115
+ "color": "#FF5F87"
116
+ },
117
+ "keyword_type": {
118
+ "color": "#6E6ED8"
119
+ },
120
+ "operator": {
121
+ "color": "#EF8080"
122
+ },
123
+ "punctuation": {
124
+ "color": "#E8E8A8"
125
+ },
126
+ "name": {
127
+ "color": "#C4C4C4"
128
+ },
129
+ "name_builtin": {
130
+ "color": "#FF8EC7"
131
+ },
132
+ "name_tag": {
133
+ "color": "#B083EA"
134
+ },
135
+ "name_attribute": {
136
+ "color": "#7A7AE6"
137
+ },
138
+ "name_class": {
139
+ "color": "#F1F1F1",
140
+ "underline": true,
141
+ "bold": true
142
+ },
143
+ "name_constant": {},
144
+ "name_decorator": {
145
+ "color": "#FFFF87"
146
+ },
147
+ "name_exception": {},
148
+ "name_function": {
149
+ "color": "#00D787"
150
+ },
151
+ "name_other": {},
152
+ "literal": {},
153
+ "literal_number": {
154
+ "color": "#6EEFC0"
155
+ },
156
+ "literal_date": {},
157
+ "literal_string": {
158
+ "color": "#C69669"
159
+ },
160
+ "literal_string_escape": {
161
+ "color": "#AFFFD7"
162
+ },
163
+ "generic_deleted": {
164
+ "color": "#FD5B5B"
165
+ },
166
+ "generic_emph": {
167
+ "italic": true
168
+ },
169
+ "generic_inserted": {
170
+ "color": "#00D787"
171
+ },
172
+ "generic_strong": {
173
+ "bold": true
174
+ },
175
+ "generic_subheading": {
176
+ "color": "#777777"
177
+ },
178
+ "background": {
179
+ "background_color": "#373737"
180
+ }
181
+ }
182
+ },
183
+ "table": {},
184
+ "definition_list": {},
185
+ "definition_term": {},
186
+ "definition_description": {
187
+ "block_prefix": "\n\ud83e\udc36 "
188
+ },
189
+ "html_block": {},
190
+ "html_span": {}
191
+ }