unsnooze 1.12.2 → 1.14.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/CHANGELOG.md +84 -0
- package/README.md +286 -22
- package/bin/unsnooze.js +52 -3
- package/package.json +7 -4
- package/src/agents/agy.js +2 -0
- package/src/agents/claude.js +2 -0
- package/src/agents/codex.js +2 -0
- package/src/agents/grok.js +2 -0
- package/src/agents/index.js +7 -1
- package/src/agents/kimi.js +2 -0
- package/src/agents/opencode.js +2 -0
- package/src/agents/qwen.js +2 -0
- package/src/askpass.js +188 -0
- package/src/cli.js +149 -25
- package/src/config.js +15 -0
- package/src/dashboard/App.js +390 -0
- package/src/dashboard/Logo.js +74 -0
- package/src/dashboard/components/TextInput.js +63 -0
- package/src/dashboard/data.js +143 -0
- package/src/dashboard/mark.js +51 -0
- package/src/dashboard/mouse-protocol.js +75 -0
- package/src/dashboard/mouse.js +125 -0
- package/src/dashboard/run.js +108 -0
- package/src/dashboard/tabs/DoctorTab.js +35 -0
- package/src/dashboard/tabs/FleetTab.js +94 -0
- package/src/dashboard/tabs/LogsTab.js +28 -0
- package/src/dashboard/tabs/PromptsTab.js +406 -0
- package/src/dashboard/tabs/SessionsTab.js +33 -0
- package/src/dashboard/tabs/StatusTab.js +58 -0
- package/src/dashboard/tabs/UsageTab.js +68 -0
- package/src/dashboard/theme.js +32 -0
- package/src/doctor.js +22 -16
- package/src/fleet.js +927 -0
- package/src/hook.js +19 -0
- package/src/install.js +9 -0
- package/src/monitor.js +14 -0
- package/src/prompt-queue.js +561 -0
- package/src/prompt.js +411 -0
- package/src/remote.js +223 -0
- package/src/resumer.js +60 -16
- package/src/settings.js +13 -0
- package/src/state.js +33 -9
- package/src/time-parser.js +15 -1
- package/src/tui.js +395 -0
- package/src/usage.js +1537 -0
- package/src/watcher.js +79 -8
- package/src/watchers/claude.js +3 -0
- package/src/watchers/codex.js +8 -8
- package/src/wizard.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,90 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.14.0 — 2026-07-19
|
|
6
|
+
|
|
7
|
+
- **Queued prompts**: `unsnooze prompt add [--agent id] [--project path]
|
|
8
|
+
[--at time|--now] <text...>` (plus `list`/`remove`/`clear`) queues a
|
|
9
|
+
one-shot prompt that spawns a **brand-new** agent session in a project
|
|
10
|
+
directory once a usage limit clears. `--now`/`--at` (epoch, ISO-8601,
|
|
11
|
+
`+2h30m`, or a bare clock time) skip the reset wait; with no reset signal
|
|
12
|
+
at all, a `next-reset` entry delivers on the very next daemon tick and
|
|
13
|
+
`prompt add` prints a notice to that effect. Delivery is verified against
|
|
14
|
+
a fresh limit banner and backs off on failure — the same backoff floor
|
|
15
|
+
applies to every mode, capped at 5 attempts (same as resume) before an
|
|
16
|
+
entry is marked failed. `autoResume` does not gate delivery. Dashboard:
|
|
17
|
+
a new **Prompts** tab (`7`) — list, `a` to add, `d`/`x` to remove — plus a
|
|
18
|
+
queued-count hint on the Status tab. Fleet: `--host <name>` relays the
|
|
19
|
+
same subcommands to a registered host's own queue (`--project`/`--agent`
|
|
20
|
+
required, everything re-validated server-side); the new `remoteQueue`
|
|
21
|
+
setting (default on) lets a host opt out of all queue traffic, answering
|
|
22
|
+
a typed `disabled` instead of silently dropping it.
|
|
23
|
+
- **Fleet password auth**: hosts can now use a password instead of an ssh key
|
|
24
|
+
— `unsnooze hosts add <name> <dest> --auth password --source
|
|
25
|
+
prompt|env|keychain|command` (`prompt` is interactive no-echo and the
|
|
26
|
+
default; `env`/`keychain`/`command` are daemon-capable). `keychain` is a
|
|
27
|
+
macOS-only built-in; Windows and Linux use `--source command` with a
|
|
28
|
+
per-OS recipe (Linux `pass`/`secret-tool`, Windows `powershell`/`op read`;
|
|
29
|
+
`security` is the macOS recipe — see README's per-OS table). `unsnooze hosts test <name>` pre-flights a host without
|
|
30
|
+
ever printing its secret. Auth-gapped hosts render as `needs-auth` in
|
|
31
|
+
`fleet`/the dashboard, distinct from `unreachable`. Security: the password
|
|
32
|
+
never touches argv, `ps`, or unsnooze's own environment — it flows through
|
|
33
|
+
OpenSSH's `SSH_ASKPASS` hook, helper-stdout → ssh, in-process; unsnooze
|
|
34
|
+
stores no plaintext itself; keys stay the unchanged, BatchMode-hardened
|
|
35
|
+
default.
|
|
36
|
+
- **Fleet — sessions on every machine, over your own SSH**: `unsnooze hosts
|
|
37
|
+
[add|rm|list]` registers ssh destinations; `unsnooze fleet [--json]` and
|
|
38
|
+
the dashboard's new **Fleet** tab fan out to every host in parallel and
|
|
39
|
+
show each one's tracked sessions (state, reset countdown, attach hint),
|
|
40
|
+
with a bounded-concurrency ssh pool, per-host timeouts, and a 24h stale
|
|
41
|
+
cache so one dead box never blocks the rest. `unsnooze _remote` is the
|
|
42
|
+
single remote entrypoint (`status`/`resume`/`cancel`), safe to lock to an
|
|
43
|
+
`authorized_keys` forced command; `unsnooze status --json` and a shared
|
|
44
|
+
resume core back both the local and remote paths. Security posture: no
|
|
45
|
+
listening ports, no custom auth, no tokens — transport is plain OpenSSH
|
|
46
|
+
with host-key checking never weakened; the remote is always the one that
|
|
47
|
+
types, under its own gates; and every field a remote returns is
|
|
48
|
+
control-character-stripped, length-capped, and extracted into fresh
|
|
49
|
+
objects before it touches your terminal or state.
|
|
50
|
+
- **Dashboard mouse support**: click tabs and session rows, wheel-scroll the
|
|
51
|
+
status/sessions lists and a real scrollback window in Logs, clickable
|
|
52
|
+
footer hints (refresh / help / quit). Full keyboard parity kept; `m` (or
|
|
53
|
+
`mouse` config / `UNSNOOZE_MOUSE`) toggles it live so terminal text
|
|
54
|
+
selection is one keypress away. Tracking modes are always cleared on exit,
|
|
55
|
+
crash, and Ctrl-Z — no hijacked mouse after quit.
|
|
56
|
+
|
|
57
|
+
## 1.13.0 — 2026-07-17
|
|
58
|
+
|
|
59
|
+
- **`unsnooze usage` — know the wall before you hit it**: burn-rate &
|
|
60
|
+
time-to-limit forecast per agent × window. Every figure carries its
|
|
61
|
+
provenance — `(exact)` from Codex rollouts or the opt-in Claude statusline
|
|
62
|
+
shim, `(calibrated from N stops)` learned from unsnooze's own recorded
|
|
63
|
+
limit stops, or `(estimated)` while calibrating. Weighted token burn
|
|
64
|
+
(cache reads ×0.1) over active minutes, account-wide including subagent
|
|
65
|
+
transcripts, per Opus/Sonnet bucket on Max plans. ETA shown as a band,
|
|
66
|
+
cross-checked against the observed reset time — never a false-precision
|
|
67
|
+
minute, never a blind now+5h.
|
|
68
|
+
- **Pre-wall warnings from the daemon**: hybrid thresholds (80/95% and
|
|
69
|
+
≤30/≤10 min to the wall at current pace), deduped per window instance,
|
|
70
|
+
with a `/compact now` nudge sized from the same context estimator as
|
|
71
|
+
`unsnooze status`. Notify-only — unsnooze still never types anything you
|
|
72
|
+
didn't configure.
|
|
73
|
+
- **`unsnooze usage --install-statusline`**: opt-in shim that persists
|
|
74
|
+
Claude's exact server-side percentages; chains your existing statusline
|
|
75
|
+
command (backed up once, restored on uninstall).
|
|
76
|
+
- **Live dashboard**: `unsnooze dashboard` (also `status`/`usage` on an
|
|
77
|
+
interactive TTY) — full-screen Ink TUI with Status, Usage, Sessions,
|
|
78
|
+
Doctor and Logs tabs, animated ❯ z z z brand mark, help overlay (`?`),
|
|
79
|
+
and a compact layout down to 80×24. Pipes and `--json` stay plain.
|
|
80
|
+
- **Fix**: undated limit banners crossing midnight ("resets 12:04am" seen
|
|
81
|
+
at 9pm) parsed as already-past and were dropped or resumed due-now into a
|
|
82
|
+
still-live limit; they now roll to tomorrow when the announced time is
|
|
83
|
+
within a window's reach, while genuinely stale banners still resolve
|
|
84
|
+
due-now.
|
|
85
|
+
- Codex windows are labeled from `window_minutes` everywhere (300 → 5h,
|
|
86
|
+
10080 → weekly, 43200 → 30d on the go plan) — stop records no longer
|
|
87
|
+
conflate the monthly window with the weekly bucket.
|
|
88
|
+
|
|
5
89
|
## 1.12.2 — 2026-07-16
|
|
6
90
|
|
|
7
91
|
- **Daemon autostart self-heal**: updating via npm never touches the
|
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
[](package.json)
|
|
10
10
|
[](LICENSE)
|
|
11
11
|
|
|
12
|
+
**[unsnooze.combustortech.in](https://unsnooze.combustortech.in)** — docs · changelog · feedback
|
|
13
|
+
|
|
12
14
|
**Claude Code · Codex CLI · Grok · Qwen · Kimi · OpenCode · Antigravity** — when they hit the 5-hour or weekly usage limit
|
|
13
15
|
("You've hit your usage limit"), your session just… stops.<br/>
|
|
14
16
|
unsnooze auto-resumes them: it tracks **every** limit-stopped session across all
|
|
@@ -68,6 +70,22 @@ your agent handles permissions.
|
|
|
68
70
|
- **Reversible install.** The settings hook and rc-file wrappers are backed up
|
|
69
71
|
first (`*.unsnooze-orig` pristine + `*.unsnooze-bak` rolling); `unsnooze
|
|
70
72
|
uninstall` removes every change. Releases are published with npm provenance.
|
|
73
|
+
- **Fleet adds no network surface.** No listening ports, no custom auth, no
|
|
74
|
+
tokens. Transport is your own OpenSSH (`~/.ssh/config`, keys, agent);
|
|
75
|
+
unsnooze never sets `StrictHostKeyChecking=no` and fails fast on unknown
|
|
76
|
+
hosts instead of auto-trusting them.
|
|
77
|
+
- **The remote is the authority.** Fleet resume only marks a session due —
|
|
78
|
+
the remote daemon types under its own gates (foreground, idle, your
|
|
79
|
+
configured message only). A compromised viewer cannot make a remote type
|
|
80
|
+
anything else.
|
|
81
|
+
- **Remote output is untrusted.** Everything a host returns is
|
|
82
|
+
control-character-stripped and length-capped before it touches your
|
|
83
|
+
terminal (ANSI-injection defense), and parsed fields are extracted —
|
|
84
|
+
never merged — into fresh objects.
|
|
85
|
+
- **Lock a key to unsnooze (optional, recommended for automation):**
|
|
86
|
+
`command="unsnooze _remote",restrict ssh-ed25519 AAAA… unsnooze-fleet`
|
|
87
|
+
in the remote `authorized_keys` confines that key to the fleet
|
|
88
|
+
entrypoint — it can never open a shell.
|
|
71
89
|
|
|
72
90
|
**Honest limits:** unsnooze *does* inject keystrokes into your live terminal on
|
|
73
91
|
your behalf, and it does not sandbox your agent or defend against prompt
|
|
@@ -196,34 +214,276 @@ StopFailure hook (claude, grok) ──────────────┤
|
|
|
196
214
|
</details>
|
|
197
215
|
|
|
198
216
|
Limit events are never persisted by the CLIs themselves; the reset time is
|
|
199
|
-
parsed from the banner text, DST-safe
|
|
200
|
-
—
|
|
201
|
-
|
|
217
|
+
parsed from the banner text, DST-safe. Unparseable banners are never guessed
|
|
218
|
+
at — unsnooze probes the pane on a backoff schedule until a real reset time
|
|
219
|
+
appears. Every resume is verified afterwards (banner came back → reschedule
|
|
220
|
+
from the fresh banner, capped at 5 attempts).
|
|
221
|
+
|
|
222
|
+
## Know the wall before you hit it
|
|
223
|
+
|
|
224
|
+
Recovery (auto-resume) is only half the job. **Prevention** is knowing when the
|
|
225
|
+
wall is coming — so you can `/compact`, pause, or switch models *before* a stop.
|
|
226
|
+
|
|
227
|
+
```sh
|
|
228
|
+
unsnooze usage # burn rate, % used, time-to-limit
|
|
229
|
+
unsnooze usage --json # stable machine shape (exit 2 past warn %)
|
|
230
|
+
unsnooze usage --install-statusline # opt-in: exact Claude % via statusline
|
|
231
|
+
unsnooze usage --uninstall-statusline # remove the shim, restore your statusLine
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
```text
|
|
235
|
+
$ unsnooze usage
|
|
236
|
+
unsnooze usage — account burn & time-to-limit (daemon: running · warnings at 80,95%)
|
|
237
|
+
|
|
238
|
+
claude 5h [█████████████░░░░░░░] ~64% (calibrated from 4 stops)
|
|
239
|
+
burn ~31k weighted tok/min over last 42 active min
|
|
240
|
+
wall ~1h 10m at this pace · window resets 8:00 pm (absolute)
|
|
241
|
+
|
|
242
|
+
codex 5h [███░░░░░░░░░░░░░░░░░] 5% used (exact)
|
|
243
|
+
monthly [██░░░░░░░░░░░░░░░░░░] 5% used (exact) · resets Aug 11
|
|
244
|
+
burn idle — no active burn
|
|
245
|
+
|
|
246
|
+
Estimates are a lower bound: Claude quotas are account-pooled with claude.ai/Desktop.
|
|
247
|
+
Exact Claude percentages available via: unsnooze usage --install-statusline
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
| What you get | How |
|
|
251
|
+
|---|---|
|
|
252
|
+
| **Honest labels** | Every figure is tagged `(exact)`, `(calibrated from N stops)`, or `(estimated — calibrating…)`. Never a bare %. |
|
|
253
|
+
| **Account-wide burn** | Sums all active sessions **and** subagents (the limit is per-account, not per-pane). Idle gaps >5 min are excluded so a quiet hour doesn't hide a fast burn. |
|
|
254
|
+
| **Time-to-wall** | ETA as a band (not a false-precision minute), cross-checked against known reset times. Weekly shown info-only — no unstable weekly ETA. |
|
|
255
|
+
| **Pre-wall warnings** | Daemon notifies at `%` bands (`usageWarnAt`, default `80,95`) **and** time tiers (30 / 10 min). Deduped once per window. |
|
|
256
|
+
| **`/compact` nudge only** | Warnings may *suggest* `/compact` so the eventual wake is cheap. unsnooze **never auto-types it** (same security rule as resume: types only your configured message). |
|
|
257
|
+
|
|
258
|
+
**Provenance ladder** (why this isn't another guessed-quota dashboard):
|
|
259
|
+
|
|
260
|
+
1. **`(exact)`** — Codex always (local `used_percent` + epoch reset). Claude only with the opt-in statusline shim (server-authoritative `rate_limits` from [Claude Code's statusline JSON](https://code.claude.com/docs/en/statusline)).
|
|
261
|
+
2. **`(calibrated from N stops)`** — Claude token burn vs a ceiling learned from **your** limit-stop ledger (the same stops unsnooze already records for resume). Not plan presets, not circular P90-of-history.
|
|
262
|
+
3. **`(estimated)`** — used tokens + burn shown; ceiling unknown until the first observed stop.
|
|
263
|
+
|
|
264
|
+
**Honest limits:** Claude transcript sums are a **lower bound** — subscription quotas are account-pooled with claude.ai and the Desktop app. Without the statusline shim, Claude tops out at calibrated/estimated. Exact Claude % needs Pro/Max after the first API response of a session (absent after `/clear` and on API auth).
|
|
202
265
|
|
|
203
266
|
## Usage
|
|
204
267
|
|
|
268
|
+
On an interactive TTY, `status` / `usage` / `sessions` open a **live dashboard**
|
|
269
|
+
(`unsnooze dashboard`) with the brand logo (`❯` + rising `zzz`), tabs, live
|
|
270
|
+
refresh until you press **`q`** — and mouse support: click tabs and rows,
|
|
271
|
+
wheel to scroll (toggle with `m`; hold Shift — Option in iTerm2 — to select text). Pipes, `NO_COLOR`, `CI`, and `--json` stay plain.
|
|
272
|
+
|
|
273
|
+
```sh
|
|
274
|
+
claude / codex / grok # normal usage — wrapped automatically
|
|
275
|
+
unsnooze status # tracked sessions + reset countdowns
|
|
276
|
+
unsnooze dashboard [tab] # live TUI (status|usage|sessions|doctor|logs|fleet|prompts)
|
|
277
|
+
unsnooze usage [--json] # account burn & time-to-limit forecast
|
|
278
|
+
unsnooze usage --install-statusline # opt-in exact Claude % (chains your statusLine)
|
|
279
|
+
unsnooze resume-now [id|--all] # don't wait for the reset time
|
|
280
|
+
unsnooze cancel [id|--all] # stop tracking a session
|
|
281
|
+
unsnooze message <id> "text" # per-session wake message (--clear to reset)
|
|
282
|
+
unsnooze preview [id] # dry-run: what WOULD happen right now, and why —
|
|
283
|
+
# nothing is typed or opened
|
|
284
|
+
unsnooze sessions # list unsnooze-owned mux sessions + panes
|
|
285
|
+
unsnooze reap [--dry-run|--yes] # close finished panes / empty sessions (default dry-run)
|
|
286
|
+
unsnooze doctor [--fix] # install health check + retire old csg leftovers
|
|
287
|
+
unsnooze prompt add [--agent id] [--project path] [--at time|--now] "text"
|
|
288
|
+
# queue a prompt for a NEW session (see Queued prompts)
|
|
289
|
+
unsnooze prompt list [--json] # list queued prompts
|
|
290
|
+
unsnooze prompt remove <id> # cancel a queued prompt
|
|
291
|
+
unsnooze prompt clear # cancel all pending queued prompts
|
|
292
|
+
unsnooze config list # settings (see below)
|
|
293
|
+
unsnooze config set <k> <v> # e.g. autoResume off
|
|
294
|
+
unsnooze logs [-f] # what unsnooze has been doing
|
|
295
|
+
unsnooze update # update unsnooze itself
|
|
296
|
+
unsnooze daemon # persistent GUI-session watcher (usually run
|
|
297
|
+
# by launchd/systemd via `install --daemon`)
|
|
298
|
+
unsnooze report [agent] # capture a pane to report an undetected banner
|
|
299
|
+
unsnooze uninstall [--purge] # remove wrappers + hooks (+ state with --purge)
|
|
300
|
+
unsnooze help # full command list (also -h / --help)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Queued prompts
|
|
304
|
+
|
|
305
|
+
Queue a prompt now; unsnooze types it into a **brand-new** agent session — a
|
|
306
|
+
fresh window in a project directory — once a usage limit clears (or at a time
|
|
307
|
+
you choose). It's one-shot: each entry is delivered at most once.
|
|
308
|
+
|
|
205
309
|
```sh
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
unsnooze
|
|
210
|
-
unsnooze
|
|
211
|
-
unsnooze
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
unsnooze
|
|
215
|
-
unsnooze
|
|
216
|
-
unsnooze
|
|
217
|
-
unsnooze config set <k> <v> # e.g. autoResume off
|
|
218
|
-
unsnooze logs [-f] # what unsnooze has been doing
|
|
219
|
-
unsnooze update # update unsnooze itself
|
|
220
|
-
unsnooze daemon # persistent GUI-session watcher (usually run
|
|
221
|
-
# by launchd/systemd via `install --daemon`)
|
|
222
|
-
unsnooze report [agent] # capture a pane to report an undetected banner
|
|
223
|
-
unsnooze uninstall [--purge] # remove wrappers + hooks (+ state with --purge)
|
|
224
|
-
unsnooze help # full command list (also -h / --help)
|
|
310
|
+
unsnooze prompt add "run the full test suite and fix any failures"
|
|
311
|
+
# → interactive agent picker on a TTY; queued for next-reset in the cwd
|
|
312
|
+
|
|
313
|
+
unsnooze prompt add --agent codex --project ~/code/api --now "ship the release"
|
|
314
|
+
unsnooze prompt add --at "+2h30m" "rebase onto main" # relative duration
|
|
315
|
+
unsnooze prompt add --at "9pm" "nightly cleanup pass" # next occurrence of a clock time
|
|
316
|
+
unsnooze prompt add --at 1755000000 "..." # epoch (seconds or ms), or an ISO-8601 timestamp
|
|
317
|
+
|
|
318
|
+
unsnooze prompt list [--json] # id, agent, due, status, cwd, prompt preview
|
|
319
|
+
unsnooze prompt remove <id> # cancel one pending/launching entry
|
|
320
|
+
unsnooze prompt clear # cancel every pending/launching entry
|
|
225
321
|
```
|
|
226
322
|
|
|
323
|
+
Modes:
|
|
324
|
+
|
|
325
|
+
- **next-reset** (default) — delivered once no future reset time is known for
|
|
326
|
+
that agent, i.e. once the current limit clears. If unsnooze holds **no
|
|
327
|
+
reset signal at all** for that agent when you add the entry, there's
|
|
328
|
+
nothing to wait on — it delivers on the very next daemon tick, and
|
|
329
|
+
`prompt add` prints a notice to that effect right away. Use `--at` if you
|
|
330
|
+
want a specific time instead.
|
|
331
|
+
- `--now` — deliver on the next daemon tick, no reset wait at all.
|
|
332
|
+
- `--at <time>` — deliver at a specific time: epoch (seconds or
|
|
333
|
+
milliseconds), an ISO-8601 timestamp, a `+2h30m`-style relative duration,
|
|
334
|
+
or a bare clock time (`9pm`, `2:05pm`, `14:30`) rolled to its next local
|
|
335
|
+
occurrence.
|
|
336
|
+
|
|
337
|
+
If a delivery attempt lands on a pane that's still limited — the reset
|
|
338
|
+
hadn't actually cleared, or a fresh `--now`/`--at` session hits the wall
|
|
339
|
+
immediately — the entry goes back to pending behind a backoff floor before
|
|
340
|
+
it's retried. That floor applies to every mode, so a failing `--now`/`--at`
|
|
341
|
+
entry can't burn through every retry in the first few seconds the way an
|
|
342
|
+
unthrottled retry loop would. Verified delivery (capped at 5 attempts,
|
|
343
|
+
same as resume) marks it `failed` and sends a notification.
|
|
344
|
+
|
|
345
|
+
`autoResume` does **not** gate prompt delivery — the queue runs independently
|
|
346
|
+
of session tracking, even with `autoResume off`.
|
|
347
|
+
|
|
348
|
+
A far-future `--at` entry is expected to keep a transient resumer process
|
|
349
|
+
alive (checking in each tick) until its delivery time arrives, however long
|
|
350
|
+
that is — this is intended, not a leak.
|
|
351
|
+
|
|
352
|
+
**Fleet:** `--host <name>` queues on a registered host instead of locally.
|
|
353
|
+
`--project` (an absolute remote path) and `--agent` are both required — there's
|
|
354
|
+
no local cwd to default to and no interactive picker over an ssh round-trip.
|
|
355
|
+
The remote host re-validates everything server-side. Delivery feedback comes
|
|
356
|
+
from `unsnooze fleet` / the dashboard's Fleet tab (per-host queued count) and
|
|
357
|
+
that host's own notifications (ntfy reaches your phone from any host, not
|
|
358
|
+
just the one you're sitting at). A host can refuse all queue traffic with
|
|
359
|
+
`remoteQueue: false` (`unsnooze config set remoteQueue off`, env
|
|
360
|
+
`UNSNOOZE_REMOTE_QUEUE=0`, **set on the host being controlled**) — the queue
|
|
361
|
+
verbs then answer a typed "disabled" instead of silently dropping; a remote
|
|
362
|
+
that predates this feature reports a clear "too old" error instead of
|
|
363
|
+
failing silently.
|
|
364
|
+
|
|
365
|
+
```sh
|
|
366
|
+
unsnooze prompt add --host gpu-box --project /home/me/repo --agent claude --now "..."
|
|
367
|
+
unsnooze prompt list --host gpu-box
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Dashboard:** the **Prompts** tab (`7`) lists queued entries; `a` opens an
|
|
371
|
+
add form (path → agent → when — with a time prompt if you pick "at" — → a
|
|
372
|
+
host step if you have hosts registered → prompt text), `d`/`x` removes the
|
|
373
|
+
selected entry. The Status tab shows a `<n> prompt(s) queued — tab 7` hint
|
|
374
|
+
whenever entries are pending or launching.
|
|
375
|
+
|
|
376
|
+
## Fleet: sessions on every machine
|
|
377
|
+
|
|
378
|
+
See and act on unsnooze sessions on other machines from one terminal — over
|
|
379
|
+
your own SSH, no new service to run.
|
|
380
|
+
|
|
381
|
+
```sh
|
|
382
|
+
unsnooze hosts add work you@work-box.local # register an ssh destination
|
|
383
|
+
unsnooze hosts list # registered hosts
|
|
384
|
+
unsnooze hosts rm work # forget a host
|
|
385
|
+
unsnooze fleet [--json] # every host's sessions, fanned out over ssh
|
|
386
|
+
unsnooze dashboard fleet # live Fleet tab — R resumes a selected stopped session (C cancels)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
A registered host needs **unsnooze installed** and **ssh key auth** (no
|
|
390
|
+
passwords) — nothing new to open or configure; fleet rides your existing
|
|
391
|
+
`~/.ssh/config`, keys, and agent. **Before adding a host, connect to it the
|
|
392
|
+
normal way once** (`ssh <host>`) so OpenSSH pins the host key itself —
|
|
393
|
+
unsnooze never weakens host-key checking to skip that step, so an unknown or
|
|
394
|
+
changed host fails fast instead of being silently trusted.
|
|
395
|
+
|
|
396
|
+
Fleet is *see and mark*, not *type remotely*: `unsnooze fleet` and the
|
|
397
|
+
dashboard's **Fleet** tab list every reachable host's tracked sessions
|
|
398
|
+
(state, reset countdown, attach hint). Selecting a stopped remote session and
|
|
399
|
+
pressing **`R`** (uppercase — lowercase `r` refreshes) only marks it due — the
|
|
400
|
+
remote's own daemon does the actual keystrokes, under the same
|
|
401
|
+
ownership/liveness/menu gates as a local session.
|
|
402
|
+
Resumed and stopped sessions print an attach hint
|
|
403
|
+
(`ssh -t <host> 'tmux new -A -s <session>'` / `zellij attach <session>`) so
|
|
404
|
+
you can hop over and watch. An unreachable or out-of-date host shows
|
|
405
|
+
`unreachable`/`skew` rather than blocking the rest of the fleet, and falls
|
|
406
|
+
back to its last-known state (marked `stale`) for up to 24h.
|
|
407
|
+
|
|
408
|
+
### Auth: keys (default) or password
|
|
409
|
+
|
|
410
|
+
Every host picks its own auth, per host — unsnooze imposes no policy:
|
|
411
|
+
|
|
412
|
+
- **`key`** (default, unchanged) — the BatchMode-hardened path above. Nothing
|
|
413
|
+
about a key host changes.
|
|
414
|
+
- **`password`, interactive** (`--source prompt`, the default source when you
|
|
415
|
+
pass `--auth password` with nothing else) — typed at run time, no-echo, on
|
|
416
|
+
whichever terminal is running `unsnooze fleet`/`hosts test`. Nothing is
|
|
417
|
+
stored. There's no terminal under the background daemon, so a `prompt`
|
|
418
|
+
host is naturally interactive-only — it shows `needs-auth` when the daemon
|
|
419
|
+
or a piped/non-TTY caller tries it.
|
|
420
|
+
- **`password`, stored** (`--source env|keychain|command`) — resolved
|
|
421
|
+
automatically, so the daemon and `unsnooze fleet` can reach it unattended.
|
|
422
|
+
|
|
423
|
+
```sh
|
|
424
|
+
unsnooze hosts add <name> <dest>
|
|
425
|
+
[--auth key|password] # default: key
|
|
426
|
+
[--source prompt|env|keychain|command] # default: prompt, once --auth password is set
|
|
427
|
+
[--env <VARNAME>] # env source: var to read (default UNSNOOZE_PW_<NAME>)
|
|
428
|
+
[--service <s> --account <a>] # keychain source (macOS only)
|
|
429
|
+
[--cmd '<command>'] # command source: program whose stdout is the password
|
|
430
|
+
unsnooze hosts test <name> # pre-flight: resolves the source (never prints the
|
|
431
|
+
# secret) then a real ssh reachability probe
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Examples:
|
|
435
|
+
|
|
436
|
+
```sh
|
|
437
|
+
unsnooze hosts add laptop me@laptop.local --auth password
|
|
438
|
+
# → prompts for a password every time it's used from a real terminal
|
|
439
|
+
|
|
440
|
+
unsnooze hosts add gpu ubuntu@gpu.example.com --auth password --source env --env UNSNOOZE_PW_GPU
|
|
441
|
+
# → export UNSNOOZE_PW_GPU=... in your shell/session first
|
|
442
|
+
|
|
443
|
+
unsnooze hosts add mac me@mac-mini.local --auth password --source keychain --service unsnooze-mac --account me
|
|
444
|
+
# → macOS only; reads via `security find-generic-password`
|
|
445
|
+
|
|
446
|
+
unsnooze hosts add ci ci@build.example.com --auth password --source command --cmd 'op read op://vault/ci/password'
|
|
447
|
+
# → any OS; runs your own secret manager and reads its stdout
|
|
448
|
+
|
|
449
|
+
unsnooze hosts test gpu # resolves the credential + probes reachability; prints "auth ok" or a hint, never the secret
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**`command` is the portable, first-class source** — plug in whatever secret
|
|
453
|
+
manager you already run. Per-OS one-liners for `--cmd`:
|
|
454
|
+
|
|
455
|
+
| OS | Example `--cmd` |
|
|
456
|
+
|---|---|
|
|
457
|
+
| macOS | `security find-generic-password -s <service> -a <account> -w` |
|
|
458
|
+
| Linux | `pass show <path>` or `secret-tool lookup <attr> <value> ...` |
|
|
459
|
+
| Windows | `powershell -Command "..."` (e.g. wrapping `Get-StoredCredential`) or `op read op://vault/item/password` |
|
|
460
|
+
| any OS | `op read op://vault/item/password` (1Password CLI), or any other manager's read command |
|
|
461
|
+
|
|
462
|
+
`keychain` is a convenience built-in for **macOS only** (`security
|
|
463
|
+
find-generic-password`) — there's no dependable dependency-free store to
|
|
464
|
+
shell out to on Windows or Linux, so those use `--source command` with the
|
|
465
|
+
recipes above instead.
|
|
466
|
+
|
|
467
|
+
**Security posture:** the password reaches `ssh` through OpenSSH's own
|
|
468
|
+
`SSH_ASKPASS` hook — it never appears on `argv`, in `ps`, or in unsnooze's
|
|
469
|
+
own environment; it flows helper-stdout → ssh, in-process, for that one ssh
|
|
470
|
+
child only. unsnooze itself **stores no plaintext**: `keychain`/`command`
|
|
471
|
+
delegate entirely to the OS store or your own manager, and `env` is
|
|
472
|
+
user-managed (readable by your own shell already — positioned as a
|
|
473
|
+
low-friction/CI convenience, not secret-manager-grade). Keys remain the
|
|
474
|
+
default and their BatchMode-hardened path is untouched by any of this.
|
|
475
|
+
|
|
476
|
+
**Windows note:** native `ssh.exe` (Windows' built-in OpenSSH client) prompts
|
|
477
|
+
fine out of the box for an **interactive** `prompt`-source host — no shim
|
|
478
|
+
needed, it's the zero-friction path. **Stored** sources (`env`/`keychain`/
|
|
479
|
+
`command`) need a non-interactive askpass helper, and native `ssh.exe`
|
|
480
|
+
requires that to be a real launchable `.exe` — it won't reliably run a `.js`
|
|
481
|
+
or npm's `.cmd` shim. Until unsnooze ships that native launcher, use a
|
|
482
|
+
**Git-for-Windows or WSL `ssh`** (both accept the script-based helper the
|
|
483
|
+
same way macOS/Linux do — common on dev machines already) for stored
|
|
484
|
+
password hosts on Windows; a plain `ssh <host>` prompt works with native
|
|
485
|
+
`ssh.exe` regardless.
|
|
486
|
+
|
|
227
487
|
## Settings
|
|
228
488
|
|
|
229
489
|
`unsnooze setup` writes `~/.unsnooze/config.json`; change anything later with
|
|
@@ -244,6 +504,9 @@ unsnooze help # full command list (also -h / --help)
|
|
|
244
504
|
| `workspaceGuard` | `inform` | Repo changed while a session slept? `inform` wakes it with a heads-up in the message; `pause` holds it (desktop notification, diff shown on `resume-now`); `off` disables. |
|
|
245
505
|
| `contextGuard` | `inform` | Big cold context at wake? Waking a session re-reads its **entire context at full uncached price** ([why](#why-did-resuming-a-big-session-eat-so-much-of-my-quota)). `inform` resumes and notifies you of the size; `pause` holds sessions above the threshold for `unsnooze resume-now`; `off` disables. Claude Code only for now. |
|
|
246
506
|
| `contextGuardTokens` | `100000` | Context-size threshold (tokens) at which `contextGuard` notifies or holds. |
|
|
507
|
+
| `usageWarn` | `notify` | Pre-wall usage warnings from the daemon (`unsnooze usage`): `notify` or `off`. Needs `notifications` on and the daemon running. Env: `UNSNOOZE_USAGE_WARN`. |
|
|
508
|
+
| `usageWarnAt` | `80,95` | Percent thresholds for usage warnings (comma-separated). Non-numeric values fall back to the default — never silently disable. Time tiers (30 / 10 min) are constants, not config keys. Env: `UNSNOOZE_USAGE_WARN_AT`. |
|
|
509
|
+
| `mouse` | `true` | Mouse support in `unsnooze dashboard`: click tabs and session rows, wheel-scroll lists/logs, clickable footer hints. Toggle live with `m`. Hold Shift (Option in iTerm2) to select text while it's on. Env: `UNSNOOZE_MOUSE`. |
|
|
247
510
|
| `reapResumed` | `false` | Opt-in: auto-close `resumed` panes idle longer than `reapIdleAfter`. Off by default — use `unsnooze reap --yes` for explicit cleanup. |
|
|
248
511
|
| `reapIdleAfter` | `604800000` (7d) | Idle age (ms) before an opt-in auto-reap closes a `resumed` pane. |
|
|
249
512
|
| `updateCheck` | `true` | Daily new-version check (a plain GET to the npm registry, nothing identifying is sent). Notices after commands + one desktop toast per version. |
|
|
@@ -251,6 +514,7 @@ unsnooze help # full command list (also -h / --help)
|
|
|
251
514
|
| `ntfyServer` | `https://ntfy.sh` | ntfy server base URL (self-hosted servers welcome). |
|
|
252
515
|
| `ntfyToken` | `""` | Optional `tk_…` access token (`Authorization: Bearer`) for reserved topics / authed servers. |
|
|
253
516
|
| `ntfyPrivacy` | `full` | `terse` keeps directory paths out of pushed bodies (titles only) — recommended on public ntfy.sh topics. |
|
|
517
|
+
| `remoteQueue` | `true` | Set **on the host being controlled**: may other hosts queue prompts on this one (`unsnooze prompt add --host <this>`)? Off = the queue verbs answer a typed `disabled` instead of silently dropping. Env: `UNSNOOZE_REMOTE_QUEUE`. |
|
|
254
518
|
|
|
255
519
|
Every setting also has a `UNSNOOZE_*` env override (see `src/settings.js`), and
|
|
256
520
|
all timings/paths are tunable via `UNSNOOZE_*` env vars (see `src/config.js`).
|
package/bin/unsnooze.js
CHANGED
|
@@ -48,12 +48,12 @@ function runAgentFallback(agentId, args) {
|
|
|
48
48
|
|
|
49
49
|
// Only human-facing commands may print update notices — never the wrapper
|
|
50
50
|
// passthrough, hooks, or daemons (their output lands in agent panes/logs).
|
|
51
|
-
const USER_FACING = new Set(['status', 'resume-now', 'cancel', 'message', 'config', 'logs', 'report', 'sessions', 'reap', 'doctor', 'preview', 'help', '-h', '--help', '--help-unsnooze']);
|
|
51
|
+
const USER_FACING = new Set(['status', 'resume-now', 'cancel', 'message', 'config', 'logs', 'report', 'sessions', 'reap', 'doctor', 'preview', 'usage', 'dashboard', 'hosts', 'fleet', 'prompt', 'help', '-h', '--help', '--help-unsnooze']);
|
|
52
52
|
|
|
53
53
|
// Every named subcommand; anything else (or no args) is an agent launch.
|
|
54
54
|
const NAMED_COMMANDS = new Set([
|
|
55
55
|
...USER_FACING, 'setup', 'install', 'uninstall', 'update', 'daemon',
|
|
56
|
-
'_hook-stopfailure', '_monitor', '_run', '_resumer', '_update-check',
|
|
56
|
+
'_hook-stopfailure', '_monitor', '_run', '_resumer', '_update-check', '_remote', '_askpass',
|
|
57
57
|
]);
|
|
58
58
|
const isLaunchPath = cmd === '_run' || cmd === undefined || !NAMED_COMMANDS.has(cmd);
|
|
59
59
|
const launchArgs = cmd === '_run' ? rest.slice(1) : (cmd === undefined ? [] : [cmd, ...rest]);
|
|
@@ -100,7 +100,7 @@ async function main() {
|
|
|
100
100
|
switch (cmd) {
|
|
101
101
|
case 'status': {
|
|
102
102
|
const { cmdStatus } = await import('../src/cli.js');
|
|
103
|
-
return cmdStatus();
|
|
103
|
+
return cmdStatus(rest);
|
|
104
104
|
}
|
|
105
105
|
case 'resume-now': {
|
|
106
106
|
const { cmdResumeNow } = await import('../src/cli.js');
|
|
@@ -146,6 +146,26 @@ async function main() {
|
|
|
146
146
|
const { cmdPreview } = await import('../src/cli.js');
|
|
147
147
|
return cmdPreview(rest);
|
|
148
148
|
}
|
|
149
|
+
case 'dashboard': {
|
|
150
|
+
const { cmdDashboard } = await import('../src/dashboard/run.js');
|
|
151
|
+
return cmdDashboard(rest);
|
|
152
|
+
}
|
|
153
|
+
case 'usage': {
|
|
154
|
+
const { cmdUsage } = await import('../src/usage.js');
|
|
155
|
+
return cmdUsage(rest);
|
|
156
|
+
}
|
|
157
|
+
case 'hosts': {
|
|
158
|
+
const { cmdHosts } = await import('../src/fleet.js');
|
|
159
|
+
return cmdHosts(rest);
|
|
160
|
+
}
|
|
161
|
+
case 'fleet': {
|
|
162
|
+
const { cmdFleet } = await import('../src/fleet.js');
|
|
163
|
+
return cmdFleet(rest);
|
|
164
|
+
}
|
|
165
|
+
case 'prompt': {
|
|
166
|
+
const { cmdPrompt } = await import('../src/prompt.js');
|
|
167
|
+
return cmdPrompt(rest);
|
|
168
|
+
}
|
|
149
169
|
case 'install': {
|
|
150
170
|
const { cmdInstall } = await import('../src/install.js');
|
|
151
171
|
return cmdInstall(rest);
|
|
@@ -176,6 +196,16 @@ async function main() {
|
|
|
176
196
|
if (!mod) return 0;
|
|
177
197
|
return mod.runResumer();
|
|
178
198
|
}
|
|
199
|
+
case '_remote': {
|
|
200
|
+
const mod = await safeImport('../src/remote.js');
|
|
201
|
+
if (!mod) return 1;
|
|
202
|
+
return mod.cmdRemote(rest);
|
|
203
|
+
}
|
|
204
|
+
case '_askpass': {
|
|
205
|
+
const mod = await safeImport('../src/askpass.js');
|
|
206
|
+
if (!mod) return 1;
|
|
207
|
+
return mod.cmdAskpass(rest);
|
|
208
|
+
}
|
|
179
209
|
case 'update': {
|
|
180
210
|
const { runSelfUpdate } = await import('../src/update-check.js');
|
|
181
211
|
return runSelfUpdate();
|
|
@@ -263,6 +293,25 @@ Usage:
|
|
|
263
293
|
unsnooze preview [id] dry-run: what WOULD happen right now, and
|
|
264
294
|
why — nothing is typed or opened (exit 2
|
|
265
295
|
when a wake is actionable, else 0)
|
|
296
|
+
unsnooze dashboard [tab] live TUI (status|usage|sessions|doctor|logs|fleet|prompts) — q to quit,
|
|
297
|
+
mouse: click/wheel (m toggles)
|
|
298
|
+
unsnooze hosts [add|rm|list] register ssh hosts for the fleet view
|
|
299
|
+
add <name> <dest> [--auth key|password]
|
|
300
|
+
[--source prompt|env|keychain|command]
|
|
301
|
+
[--env VAR] [--service s --account a]
|
|
302
|
+
[--cmd '<command>']
|
|
303
|
+
unsnooze hosts test <name> pre-flight a host: resolves its credential
|
|
304
|
+
and probes reachability (secret never shown)
|
|
305
|
+
unsnooze fleet [--json] all hosts' sessions (hosts add <name> first)
|
|
306
|
+
unsnooze prompt add [text...] queue a prompt to start a NEW session in a
|
|
307
|
+
project when the limit resets ([--agent id]
|
|
308
|
+
[--project path] [--at time|--now])
|
|
309
|
+
unsnooze prompt list [--json] list queued prompts
|
|
310
|
+
unsnooze prompt remove <id> cancel a queued prompt
|
|
311
|
+
unsnooze prompt clear cancel all pending queued prompts
|
|
312
|
+
unsnooze usage [--json] account burn rate & time-to-limit forecast
|
|
313
|
+
(--install-statusline for exact Claude %,
|
|
314
|
+
--uninstall-statusline to remove it)
|
|
266
315
|
unsnooze logs [-f] show (or follow) the unsnooze log
|
|
267
316
|
unsnooze update update unsnooze itself to the latest version
|
|
268
317
|
unsnooze daemon persistent watcher for GUI sessions (VS Code
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unsnooze",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Auto-resume Claude Code, Codex CLI, Grok, Qwen Code, Kimi CLI, OpenCode & Antigravity when the 5-hour/weekly usage limit resets
|
|
3
|
+
"version": "1.14.0",
|
|
4
|
+
"description": "Auto-resume Claude Code, Codex CLI, Grok, Qwen Code, Kimi CLI, OpenCode & Antigravity when the 5-hour/weekly usage limit resets \u2014 tmux, Zellij, VS Code & desktop apps. Wakes every limit-stopped AI coding session automatically.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
7
7
|
"claude-code",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"bugs": {
|
|
50
50
|
"url": "https://github.com/saaranshM/unsnooze/issues"
|
|
51
51
|
},
|
|
52
|
-
"homepage": "https://
|
|
52
|
+
"homepage": "https://unsnooze.combustortech.in",
|
|
53
53
|
"type": "module",
|
|
54
54
|
"bin": {
|
|
55
55
|
"unsnooze": "bin/unsnooze.js"
|
|
@@ -68,6 +68,9 @@
|
|
|
68
68
|
"node": ">=20"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@clack/prompts": "^1.7.0"
|
|
71
|
+
"@clack/prompts": "^1.7.0",
|
|
72
|
+
"ink": "^7.1.1",
|
|
73
|
+
"picocolors": "^1.1.1",
|
|
74
|
+
"react": "^19.2.7"
|
|
72
75
|
}
|
|
73
76
|
}
|
package/src/agents/agy.js
CHANGED
|
@@ -93,6 +93,8 @@ export default {
|
|
|
93
93
|
resumeArgs(sessionId) {
|
|
94
94
|
return { args: sessionId ? [`--conversation=${sessionId}`] : ['--continue'], messageViaPane: true };
|
|
95
95
|
},
|
|
96
|
+
// v1: every agent launches the bare TUI and gets the prompt typed once idle.
|
|
97
|
+
launchArgs(message) { return { args: [], messageViaPane: true }; },
|
|
96
98
|
latestSessionId,
|
|
97
99
|
isForegroundCommand(cmd) {
|
|
98
100
|
return cmd === 'agy' || cmd === 'node' || cmd === 'unsnooze';
|
package/src/agents/claude.js
CHANGED
|
@@ -92,6 +92,8 @@ export default {
|
|
|
92
92
|
resumeArgs(sessionId) {
|
|
93
93
|
return { args: sessionId ? ['--resume', sessionId] : ['-c'], messageViaPane: true };
|
|
94
94
|
},
|
|
95
|
+
// v1: every agent launches the bare TUI and gets the prompt typed once idle.
|
|
96
|
+
launchArgs(message) { return { args: [], messageViaPane: true }; },
|
|
95
97
|
latestSessionId,
|
|
96
98
|
// Estimated tokens the API re-reads on a cold wake (prompt cache long
|
|
97
99
|
// expired). null → unknown, contextGuard skips. Adapters without this
|
package/src/agents/codex.js
CHANGED
|
@@ -123,6 +123,8 @@ export default {
|
|
|
123
123
|
messageViaPane: false,
|
|
124
124
|
};
|
|
125
125
|
},
|
|
126
|
+
// v1: every agent launches the bare TUI and gets the prompt typed once idle.
|
|
127
|
+
launchArgs(message) { return { args: [], messageViaPane: true }; },
|
|
126
128
|
latestSessionId,
|
|
127
129
|
isForegroundCommand(cmd) {
|
|
128
130
|
return cmd === 'codex' || cmd === 'node' || cmd === 'unsnooze';
|
package/src/agents/grok.js
CHANGED
|
@@ -87,6 +87,8 @@ export default {
|
|
|
87
87
|
resumeArgs(sessionId) {
|
|
88
88
|
return { args: sessionId ? ['--resume', sessionId] : ['-c'], messageViaPane: true };
|
|
89
89
|
},
|
|
90
|
+
// v1: every agent launches the bare TUI and gets the prompt typed once idle.
|
|
91
|
+
launchArgs(message) { return { args: [], messageViaPane: true }; },
|
|
90
92
|
// Session file format is undocumented; null keeps it conservative — the
|
|
91
93
|
// reopen path then uses `grok -c`, which Grok itself scopes to the cwd.
|
|
92
94
|
latestSessionId() {
|
package/src/agents/index.js
CHANGED
|
@@ -11,8 +11,14 @@ import agy from './agy.js';
|
|
|
11
11
|
|
|
12
12
|
const REGISTRY = { claude, codex, grok, qwen, kimi, opencode, agy };
|
|
13
13
|
|
|
14
|
+
// v1: every agent launches the bare TUI and gets the prompt typed once idle.
|
|
15
|
+
function defaultLaunchArgs(message) { return { args: [], messageViaPane: true }; }
|
|
16
|
+
|
|
14
17
|
export function getAgent(id) {
|
|
15
|
-
|
|
18
|
+
const agent = REGISTRY[id] || claude;
|
|
19
|
+
// Third-party/partial adapters may lack launchArgs — synthesize the same
|
|
20
|
+
// default so callers (dispatch, tests) never have to null-check it.
|
|
21
|
+
return typeof agent.launchArgs === 'function' ? agent : { ...agent, launchArgs: defaultLaunchArgs };
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
export function listAgents() {
|
package/src/agents/kimi.js
CHANGED
|
@@ -111,6 +111,8 @@ export default {
|
|
|
111
111
|
messageViaPane: false,
|
|
112
112
|
};
|
|
113
113
|
},
|
|
114
|
+
// v1: every agent launches the bare TUI and gets the prompt typed once idle.
|
|
115
|
+
launchArgs(message) { return { args: [], messageViaPane: true }; },
|
|
114
116
|
latestSessionId,
|
|
115
117
|
isForegroundCommand(cmd) {
|
|
116
118
|
// /i: macOS framework builds report "Python" (verified via tmux
|
package/src/agents/opencode.js
CHANGED
|
@@ -96,6 +96,8 @@ export default {
|
|
|
96
96
|
resumeArgs(sessionId) {
|
|
97
97
|
return { args: sessionId ? ['-s', sessionId] : ['--continue'], messageViaPane: true };
|
|
98
98
|
},
|
|
99
|
+
// v1: every agent launches the bare TUI and gets the prompt typed once idle.
|
|
100
|
+
launchArgs(message) { return { args: [], messageViaPane: true }; },
|
|
99
101
|
latestSessionId,
|
|
100
102
|
isForegroundCommand(cmd) {
|
|
101
103
|
// The npm-shipped bun binary reports pane_current_command "opencode.exe"
|