opencode-sesh 0.1.6 → 0.2.0

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 CHANGED
@@ -38,6 +38,18 @@ fi
38
38
  if [ "${1:-}" = --check ]; then
39
39
  "$SESH_JQ" -en '"2026-01-01T00:00:00Z" | fromdateiso8601 | type == "number"' >/dev/null || { echo 'jq capability check failed' >&2; exit 1; }
40
40
  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; }
41
+ # The TUI panel is a copied file that opencode imports once at startup. Report
42
+ # when the installed copy has fallen behind this package so upgrades are not
43
+ # silently stale.
44
+ panel_bundled="$SCRIPT_DIR/../tui/sesh-panel.tsx"
45
+ panel_installed="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/plugins/sesh-panel.tsx"
46
+ if [ -f "$panel_installed" ] && [ -f "$panel_bundled" ]; then
47
+ if cmp -s "$panel_bundled" "$panel_installed"; then
48
+ echo 'sesh: sidebar panel is current'
49
+ else
50
+ echo 'sesh: sidebar panel is out of date; run `sesh install`, then fully quit and reopen opencode' >&2
51
+ fi
52
+ fi
41
53
  echo 'sesh: dependencies ready'
42
54
  exit 0
43
55
  fi
@@ -47,6 +59,7 @@ picker_limit=0
47
59
  picker_archived=0
48
60
  picker_print=0
49
61
  picker_fork=0
62
+ picker_json=0
50
63
  picker_query=''
51
64
  while [ "$#" -gt 0 ]; do
52
65
  case "$1" in
@@ -55,12 +68,14 @@ while [ "$#" -gt 0 ]; do
55
68
  --archived) picker_archived=1 ;;
56
69
  --print) picker_print=1 ;;
57
70
  --fork) picker_fork=1 ;;
71
+ --json) picker_json=1 ;;
58
72
  --query) picker_query=${2:-}; shift ;;
59
73
  *) echo "unknown option: $1" >&2; exit 2 ;;
60
74
  esac
61
75
  shift
62
76
  done
63
77
  case "$picker_limit" in ''|*[!0-9]*) echo "--limit must be a non-negative integer" >&2; exit 2;; esac
78
+ [ "$picker_json" = 0 ] || [ "$picker_print" = 1 ] || { echo '--json requires --print' >&2; exit 2; }
64
79
 
65
80
  shell_quote() {
66
81
  # Quotes fixed command arguments embedded in fzf's shell action strings.
@@ -94,6 +109,12 @@ trap cleanup EXIT
94
109
  trap 'exit 130' HUP INT TERM
95
110
 
96
111
  cache_cmd="$q_worker render --state-dir $q_state --query {q} --selected-id {6}"
112
+ # fzf's --header is the only place a persistent scope/status line can live
113
+ # without becoming a selectable row, so it is refreshed by transform-header on
114
+ # start, on the 3s poll and after either action that can change it. Order
115
+ # matters: a transform-* action placed AFTER reload/reload-sync in the chain
116
+ # leaves the list empty, so the header is always transformed first.
117
+ header_cmd="$q_list --header --state-dir $q_state"
97
118
  preview_cmd="if [ -f $q_state/help ]; then $q_shortcuts; else $q_preview $q_state {2}; fi"
98
119
  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(?)'"
99
120
  toggle_cmd="$q_list --toggle-scope --state-dir $q_state >/dev/null"
@@ -101,16 +122,18 @@ delete_cmd="$q_delete $q_state {2}"
101
122
 
102
123
  query="$picker_query"
103
124
  while :; do
104
- selection=''
105
- set +e
106
- 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 --track --id-nth=6 --disabled --layout=reverse --info=hidden --prompt='❯ ' \
107
- --preview="$preview_cmd" --preview-window='right,50%,wrap,hidden' \
108
- --bind='space:transform:[ -z {q} ] && echo toggle-preview || echo "put( )"' \
109
- --bind="$help_bind" \
110
- --bind="ctrl-x:execute($delete_cmd)+reload($cache_cmd)" \
111
- --bind="ctrl-g:execute($toggle_cmd)+reload($cache_cmd)" \
112
- --bind="change:reload:$cache_cmd" \
113
- --bind="start,every(3):reload-sync:$cache_cmd")
125
+ header_line=$("$SCRIPT_DIR/sesh-list.sh" --header --state-dir "$state_dir" 2>/dev/null || true)
126
+ selection=''
127
+ set +e
128
+ 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 --track --id-nth=6 --disabled --layout=reverse --info=hidden --prompt='❯ ' \
129
+ --preview="$preview_cmd" --preview-window='right,50%,wrap,hidden' \
130
+ --header-first --header="$header_line" \
131
+ --bind='space:transform:[ -z {q} ] && echo toggle-preview || echo "put( )"' \
132
+ --bind="$help_bind" \
133
+ --bind="ctrl-x:execute($delete_cmd)+transform-header($header_cmd)+reload($cache_cmd)" \
134
+ --bind="ctrl-g:execute($toggle_cmd)+transform-header($header_cmd)+reload($cache_cmd)" \
135
+ --bind="change:reload:$cache_cmd" \
136
+ --bind="start,every(3):transform-header($header_cmd)+reload-sync:$cache_cmd")
114
137
  status=$?
115
138
  set -e
116
139
  case "$status" in 0) ;; 1|130) exit 0;; *) echo "sesh: picker failed ($status)" >&2; exit "$status";; esac
@@ -120,13 +143,30 @@ selection=${selection#*$'\n'}
120
143
  key=${selection%%$'\n'*}
121
144
  selection=${selection#*$'\n'}
122
145
  session_id=$(printf '%s\n' "$selection" | cut -f2)
123
- [ -n "$session_id" ] || continue
146
+ tracking_id=$(printf '%s\n' "$selection" | cut -f6)
147
+ if [ -z "$session_id" ]; then
148
+ # Enter/Ctrl-F on a directory header or a notice row. Leave a one-shot
149
+ # message for the reopened picker so the keypress visibly did something,
150
+ # and keep the query so the user has not lost their place.
151
+ case "$tracking_id" in
152
+ hdr:*) printf '%s' 'That row is a directory header, not a session.' > "$state_dir/action-notice" ;;
153
+ notice:*) printf '%s' 'That row is a notice, not a session; pick a session row.' > "$state_dir/action-notice" ;;
154
+ esac
155
+ continue
156
+ fi
124
157
  # Dispatch queues a new worker scan and waits for that exact serialized poll.
125
158
  # It cannot accept a previously fresh status while the worker owns its lock.
126
- poll_target=$("$SCRIPT_DIR/sesh-refresh-worker.sh" request --state-dir "$state_dir")
127
- if ! "$SCRIPT_DIR/sesh-refresh-worker.sh" wait --state-dir "$state_dir" --target "$poll_target" --timeout 12; then
128
- echo 'Live state unavailable; retry selection after refresh' >&2
129
- continue
159
+ # A --print lookup reads the published snapshot, so it skips the wait entirely
160
+ # and stays usable from scripts without paying a rescan.
161
+ if [ "$picker_print" = 0 ]; then
162
+ printf 'sesh: refreshing session state…' >&2
163
+ poll_target=$("$SCRIPT_DIR/sesh-refresh-worker.sh" request --state-dir "$state_dir")
164
+ if ! "$SCRIPT_DIR/sesh-refresh-worker.sh" wait --state-dir "$state_dir" --target "$poll_target" --timeout 12; then
165
+ printf '\r\033[K' >&2
166
+ echo 'sesh: session state unavailable; selection cancelled, retry after refresh' >&2
167
+ continue
168
+ fi
169
+ printf '\r\033[K' >&2
130
170
  fi
131
171
  record=$("$SESH_JQ" -c --arg sid "$session_id" 'select(.sessionId==$sid)' "$state_dir/snapshot.jsonl")
132
172
  [ -n "$record" ] || continue
@@ -134,7 +174,10 @@ cwd=$(printf '%s' "$record" | "$SESH_JQ" -r '.cwd')
134
174
  fork=0
135
175
  if [ "$picker_fork" = 1 ] || [ "$key" = ctrl-f ]; then fork=1; fi
136
176
  if [ "$picker_print" = 1 ]; then
137
- if [ "$fork" = 1 ]; then printf '%s\t%s\tfork\n' "$session_id" "$cwd"; else printf '%s\t%s\n' "$session_id" "$cwd"; fi
177
+ if [ "$picker_json" = 1 ]; then
178
+ "$SESH_JQ" -cn --arg sessionId "$session_id" --arg cwd "$cwd" --argjson fork "$fork" \
179
+ '{sessionId: $sessionId, cwd: $cwd, fork: ($fork == 1)}'
180
+ elif [ "$fork" = 1 ]; then printf '%s\t%s\tfork\n' "$session_id" "$cwd"; else printf '%s\t%s\n' "$session_id" "$cwd"; fi
138
181
  exit 0
139
182
  fi
140
183
  if [ ! -d "$cwd" ]; then
package/install.sh CHANGED
@@ -19,6 +19,7 @@ BIN_DIR="${XDG_BIN_HOME:-$HOME/.local/bin}"
19
19
  INSTALL_TOOL=1
20
20
  INSTALL_TUI=1
21
21
  UNINSTALL=0
22
+ SYNC_PANEL=0
22
23
 
23
24
  # Fallback plugin version for when the opencode CLI is unavailable; the
24
25
  # installer prefers the local opencode version so the panel matches its SDK.
@@ -43,6 +44,7 @@ Usage: bash install.sh [--bin-dir DIR] [--no-tool] [--no-tui] [--uninstall]
43
44
  --no-tool Skip installing the sesh-list custom tool.
44
45
  --no-tui Skip installing the in-TUI sessions panel plugin.
45
46
  --uninstall Remove everything this script installed.
47
+ --sync-panel Refresh an already-installed TUI panel only (used by postinstall).
46
48
  --help Show this help.
47
49
  USAGE
48
50
  }
@@ -53,6 +55,7 @@ while [ "$#" -gt 0 ]; do
53
55
  --no-tool) INSTALL_TOOL=0; shift ;;
54
56
  --no-tui) INSTALL_TUI=0; shift ;;
55
57
  --uninstall) UNINSTALL=1; shift ;;
58
+ --sync-panel) SYNC_PANEL=1; shift ;;
56
59
  --help) usage; exit 0 ;;
57
60
  *) fail "unknown option: $1" ;;
58
61
  esac
@@ -110,62 +113,6 @@ launcher_is_ours() {
110
113
  [ -f "$target" ] && grep -qF 'Pick an opencode session' "$target" 2>/dev/null
111
114
  }
112
115
 
113
- if [ "$UNINSTALL" = 1 ]; then
114
- if [ -L "$LAUNCHER" ]; then
115
- if launcher_is_ours "$LAUNCHER"; then
116
- rm -f "$LAUNCHER" && note "removed $LAUNCHER"
117
- else
118
- note "left $LAUNCHER untouched (not a sesh launcher: $(readlink "$LAUNCHER" 2>/dev/null))"
119
- fi
120
- fi
121
- remove_if_marker "$COMMAND_DST" "rich interactive picker" && note "removed $COMMAND_DST"
122
- remove_if_marker "$TOOL_DST" "list opencode sessions for the agent" && note "removed $TOOL_DST"
123
- remove_if_marker "$PANEL_DST" "@jsxImportSource @opentui/solid" && note "removed $PANEL_DST"
124
- remove_tui_entry && note "unregistered the panel from $TUI_JSON"
125
- note "uninstalled (extraction cache and plugin dependencies left intact)"
126
- exit 0
127
- fi
128
-
129
- for path in \
130
- bin/sesh \
131
- bin/sesh.sh \
132
- bin/sesh-list.sh \
133
- bin/sesh-preview.sh \
134
- bin/sesh-delete.sh \
135
- bin/sesh-refresh-worker.sh \
136
- bin/sesh-shortcuts.sh \
137
- opencode/tools/sesh-list.ts \
138
- tui/sesh-panel.tsx \
139
- themes/glow-dark-clean.json; do
140
- [ -f "$SOURCE_DIR/$path" ] || fail "installer bundle is incomplete: missing $path."
141
- done
142
-
143
- for tool in bash jq fzf; do
144
- command -v "$tool" >/dev/null 2>&1 || fail "required tool '$tool' is unavailable."
145
- done
146
- fzf_version=$(fzf --version 2>/dev/null || true)
147
- if [[ "$fzf_version" =~ ^([0-9]+)\.([0-9]+)(\.([0-9]+))? ]]; then
148
- if [ "$((10#${BASH_REMATCH[1]}))" -eq 0 ] && [ "$((10#${BASH_REMATCH[2]}))" -lt 73 ]; then
149
- fail "fzf >= 0.73.0 is required (found $fzf_version)."
150
- fi
151
- else
152
- fail "cannot parse fzf version from: $fzf_version."
153
- fi
154
- command -v sqlite3 >/dev/null 2>&1 || command -v opencode >/dev/null 2>&1 \
155
- || fail "either sqlite3 or the opencode CLI is required."
156
- command -v opencode >/dev/null 2>&1 || note "opencode CLI not found: listing and previews work via sqlite3, but resume and delete need opencode."
157
-
158
- umask 077
159
- mkdir -p "$BIN_DIR"
160
- if [ -e "$LAUNCHER" ] && [ ! -L "$LAUNCHER" ]; then
161
- fail "$LAUNCHER already exists and is not a symlink. Move it aside, then retry."
162
- fi
163
- if [ -L "$LAUNCHER" ] && ! launcher_is_ours "$LAUNCHER"; then
164
- fail "$LAUNCHER is a symlink to $(readlink "$LAUNCHER" 2>/dev/null), which is not a sesh install. Move it aside, then retry."
165
- fi
166
- ln -sfn "$SOURCE_DIR/bin/sesh" "$LAUNCHER"
167
- note "linked $LAUNCHER -> $SOURCE_DIR/bin/sesh"
168
-
169
116
  install_file() {
170
117
  local src=$1 dst=$2
171
118
  mkdir -p "$(dirname "$dst")"
@@ -181,26 +128,20 @@ install_file() {
181
128
  note "installed $dst"
182
129
  }
183
130
 
184
- # Earlier versions shipped a markdown `/sesh` command that could only prompt the
185
- # agent; the TUI plugin now registers the slash itself. Drop the old file so the
186
- # two do not collide.
187
- remove_if_marker "$COMMAND_DST" "rich interactive picker" && note "removed obsolete markdown command $COMMAND_DST"
188
- [ "$INSTALL_TOOL" = 1 ] && install_file "$SOURCE_DIR/opencode/tools/sesh-list.ts" "$TOOL_DST"
189
-
190
- opencode_version=''
191
- if command -v opencode >/dev/null 2>&1; then
192
- raw_version=$(opencode --version 2>/dev/null || true)
193
- if [[ "$raw_version" =~ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then opencode_version="${BASH_REMATCH[1]}"; fi
194
- fi
195
- plugin_version="${opencode_version:-$PLUGIN_VERSION_FALLBACK}"
196
-
197
- if [ "$INSTALL_TUI" = 1 ]; then
131
+ install_tui_panel() {
198
132
  install_file "$SOURCE_DIR/tui/sesh-panel.tsx" "$PANEL_DST"
199
133
 
200
- # opencode has no persistent-sidebar API: the panel is an xlarge modal
201
- # dialog (see tui/sesh-panel.tsx). Register it idempotently and drop the
202
- # previous filename if an older install left it behind.
203
- tui_created=0
134
+ local opencode_version='' raw_version='' plugin_version=''
135
+ if command -v opencode >/dev/null 2>&1; then
136
+ raw_version=$(opencode --version 2>/dev/null || true)
137
+ if [[ "$raw_version" =~ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then opencode_version="${BASH_REMATCH[1]}"; fi
138
+ fi
139
+ plugin_version="${opencode_version:-$PLUGIN_VERSION_FALLBACK}"
140
+
141
+ # opencode has no persistent-sidebar API: the panel is an xlarge modal dialog
142
+ # (see tui/sesh-panel.tsx). Register it idempotently and drop the previous
143
+ # filename if an older install left it behind.
144
+ local tui_created=0 tui_tmp=''
204
145
  if [ ! -f "$TUI_JSON" ]; then
205
146
  printf '{\n "$schema": "https://opencode.ai/tui.json",\n "plugin": []\n}\n' > "$TUI_JSON"
206
147
  tui_created=1
@@ -228,7 +169,7 @@ if [ "$INSTALL_TUI" = 1 ]; then
228
169
  # Local plugins resolve their imports from the config directory, and opencode
229
170
  # runs `bun install` there at startup. Keep the required packages declared
230
171
  # without clobbering versions the user already pinned.
231
- pkg_created=0
172
+ local pkg_created=0 pkg_tmp=''
232
173
  if [ ! -f "$PACKAGE_JSON" ]; then
233
174
  printf '{\n "dependencies": {}\n}\n' > "$PACKAGE_JSON"
234
175
  pkg_created=1
@@ -257,12 +198,104 @@ if [ "$INSTALL_TUI" = 1 ]; then
257
198
  rm -f "$pkg_tmp"
258
199
  note "warning: could not update $PACKAGE_JSON; the TUI panel may fail to load"
259
200
  fi
201
+ }
202
+
203
+ # opencode imports plugins once at startup and has no hot reload, so a running
204
+ # instance keeps the old panel until it is fully quit (reloading a window reuses
205
+ # the same process). Say so explicitly and name the processes still holding it.
206
+ note_restart() {
207
+ note "restart opencode to load the updated panel: quit it completely"
208
+ note " (all windows and background processes), then reopen."
209
+ if command -v pgrep >/dev/null 2>&1; then
210
+ local pids
211
+ pids=$(pgrep -x opencode 2>/dev/null | tr '\n' ' ' | sed 's/ $//' || true)
212
+ [ -n "$pids" ] && note " still running: opencode PID(s) $pids"
213
+ fi
214
+ return 0
215
+ }
216
+
217
+ if [ "$UNINSTALL" = 1 ]; then
218
+ if [ -L "$LAUNCHER" ]; then
219
+ if launcher_is_ours "$LAUNCHER"; then
220
+ rm -f "$LAUNCHER" && note "removed $LAUNCHER"
221
+ else
222
+ note "left $LAUNCHER untouched (not a sesh launcher: $(readlink "$LAUNCHER" 2>/dev/null))"
223
+ fi
224
+ fi
225
+ remove_if_marker "$COMMAND_DST" "rich interactive picker" && note "removed $COMMAND_DST"
226
+ remove_if_marker "$TOOL_DST" "list opencode sessions for the agent" && note "removed $TOOL_DST"
227
+ remove_if_marker "$PANEL_DST" "@jsxImportSource @opentui/solid" && note "removed $PANEL_DST"
228
+ remove_tui_entry && note "unregistered the panel from $TUI_JSON"
229
+ note "uninstalled (extraction cache and plugin dependencies left intact)"
230
+ exit 0
231
+ fi
232
+
233
+ for path in \
234
+ bin/sesh \
235
+ bin/sesh.sh \
236
+ bin/sesh-list.sh \
237
+ bin/sesh-preview.sh \
238
+ bin/sesh-delete.sh \
239
+ bin/sesh-refresh-worker.sh \
240
+ bin/sesh-shortcuts.sh \
241
+ opencode/tools/sesh-list.ts \
242
+ tui/sesh-panel.tsx \
243
+ themes/glow-dark-clean.json; do
244
+ [ -f "$SOURCE_DIR/$path" ] || fail "installer bundle is incomplete: missing $path."
245
+ done
246
+
247
+ if [ "$SYNC_PANEL" = 1 ]; then
248
+ # Postinstall path: refresh a panel the user already opted into, without
249
+ # touching the launcher or requiring fzf/sqlite. No-op when the panel is
250
+ # absent, so `npm install` never adds a TUI panel on its own.
251
+ [ -f "$PANEL_DST" ] || { note "sesh sidebar panel is not installed; nothing to sync."; exit 0; }
252
+ umask 077
253
+ install_tui_panel
254
+ note_restart
255
+ exit 0
256
+ fi
257
+
258
+ for tool in bash jq fzf; do
259
+ command -v "$tool" >/dev/null 2>&1 || fail "required tool '$tool' is unavailable."
260
+ done
261
+ fzf_version=$(fzf --version 2>/dev/null || true)
262
+ if [[ "$fzf_version" =~ ^([0-9]+)\.([0-9]+)(\.([0-9]+))? ]]; then
263
+ if [ "$((10#${BASH_REMATCH[1]}))" -eq 0 ] && [ "$((10#${BASH_REMATCH[2]}))" -lt 73 ]; then
264
+ fail "fzf >= 0.73.0 is required (found $fzf_version)."
265
+ fi
266
+ else
267
+ fail "cannot parse fzf version from: $fzf_version."
268
+ fi
269
+ command -v sqlite3 >/dev/null 2>&1 || command -v opencode >/dev/null 2>&1 \
270
+ || fail "either sqlite3 or the opencode CLI is required."
271
+ command -v opencode >/dev/null 2>&1 || note "opencode CLI not found: listing and previews work via sqlite3, but resume and delete need opencode."
272
+
273
+ umask 077
274
+ mkdir -p "$BIN_DIR"
275
+ if [ -e "$LAUNCHER" ] && [ ! -L "$LAUNCHER" ]; then
276
+ fail "$LAUNCHER already exists and is not a symlink. Move it aside, then retry."
277
+ fi
278
+ if [ -L "$LAUNCHER" ] && ! launcher_is_ours "$LAUNCHER"; then
279
+ fail "$LAUNCHER is a symlink to $(readlink "$LAUNCHER" 2>/dev/null), which is not a sesh install. Move it aside, then retry."
280
+ fi
281
+ ln -sfn "$SOURCE_DIR/bin/sesh" "$LAUNCHER"
282
+ note "linked $LAUNCHER -> $SOURCE_DIR/bin/sesh"
283
+
284
+ # Earlier versions shipped a markdown `/sesh` command that could only prompt the
285
+ # agent; the TUI plugin now registers the slash itself. Drop the old file so the
286
+ # two do not collide.
287
+ remove_if_marker "$COMMAND_DST" "rich interactive picker" && note "removed obsolete markdown command $COMMAND_DST"
288
+ [ "$INSTALL_TOOL" = 1 ] && install_file "$SOURCE_DIR/opencode/tools/sesh-list.ts" "$TOOL_DST"
289
+
290
+ if [ "$INSTALL_TUI" = 1 ]; then
291
+ install_tui_panel
260
292
  fi
261
293
 
262
294
  "$LAUNCHER" --check || fail "post-install check failed."
263
295
  note "done"
264
296
  note " picker: run 'sesh' in any terminal, or '/sesh' inside opencode"
265
- note " sidebar: restart opencode to load the panel (ctrl+o opens the full picker)"
297
+ note " sidebar: ctrl+o opens the full picker"
298
+ note_restart
266
299
  case ":$PATH:" in
267
300
  *":$BIN_DIR:"*) ;;
268
301
  *) note " note: $BIN_DIR is not on PATH; add it to use the launcher anywhere." ;;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sesh",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Browse, search, preview and resume opencode sessions from the terminal and the TUI.",
6
6
  "license": "MIT",
@@ -36,16 +36,19 @@
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  },
39
+ "packageManager": "bun@1.4.2",
39
40
  "engines": {
40
41
  "node": ">=20"
41
42
  },
42
43
  "scripts": {
43
44
  "prepare": "husky",
44
- "test": "bash tests/sesh.sh",
45
+ "postinstall": "bash install.sh --sync-panel || true",
46
+ "test": "bash tests/sesh.sh && bash tests/changelog.sh",
47
+ "test:changelog": "bash tests/changelog.sh",
45
48
  "test:picker": "python3 tests/picker.py",
46
49
  "typecheck": "tsc -p tsconfig.json",
47
- "lint:sh": "for script in bin/*.sh bin/sesh install.sh tests/*.sh; do bash -n \"$script\" || exit; done",
48
- "prepublishOnly": "npm test && npm run typecheck"
50
+ "lint:sh": "for script in bin/*.sh bin/sesh install.sh tests/*.sh scripts/*.sh; do bash -n \"$script\" || exit; done",
51
+ "prepublishOnly": "bun run test && bun run typecheck"
49
52
  },
50
53
  "devDependencies": {
51
54
  "@commitlint/cli": "^21.2.2",