@phnx-labs/agents-cli 1.22.55 → 1.22.57

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +4 -4
  3. package/dist/bootstrap.js +3 -1
  4. package/dist/cli/command-registry.d.ts +0 -1
  5. package/dist/cli/command-registry.js +0 -3
  6. package/dist/commands/exec.js +1 -1
  7. package/dist/commands/hooks.js +4 -4
  8. package/dist/commands/insights.d.ts +7 -5
  9. package/dist/commands/insights.js +16 -9
  10. package/dist/commands/perf.d.ts +16 -7
  11. package/dist/commands/perf.js +29 -20
  12. package/dist/commands/rules.js +1 -1
  13. package/dist/commands/share.js +7 -6
  14. package/dist/commands/ssh.js +24 -14
  15. package/dist/commands/trash.d.ts +2 -2
  16. package/dist/commands/trash.js +2 -6
  17. package/dist/commands/versions.d.ts +2 -2
  18. package/dist/commands/versions.js +1 -10
  19. package/dist/commands/view.d.ts +2 -2
  20. package/dist/commands/view.js +7 -6
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.js +9 -0
  23. package/dist/lib/accounting/usage-ingest.d.ts +1 -0
  24. package/dist/lib/accounting/usage-ingest.js +75 -0
  25. package/dist/lib/accounting/usage-sync.d.ts +69 -0
  26. package/dist/lib/accounting/usage-sync.js +129 -0
  27. package/dist/lib/accounting/usage.d.ts +48 -2
  28. package/dist/lib/accounting/usage.js +72 -1
  29. package/dist/lib/agent-spec/agents.js +1 -1
  30. package/dist/lib/analytics/mix-commands.d.ts +8 -7
  31. package/dist/lib/analytics/mix-commands.js +50 -73
  32. package/dist/lib/daemon/daemon.js +5 -0
  33. package/dist/lib/daemon/runner.js +9 -8
  34. package/dist/lib/daemon/usage-sync-service.d.ts +21 -0
  35. package/dist/lib/daemon/usage-sync-service.js +36 -0
  36. package/dist/lib/daemon-services.d.ts +1 -1
  37. package/dist/lib/daemon-services.js +5 -0
  38. package/dist/lib/device-config.d.ts +17 -6
  39. package/dist/lib/device-config.js +25 -11
  40. package/dist/lib/devices/pool.d.ts +4 -3
  41. package/dist/lib/devices/pool.js +13 -5
  42. package/dist/lib/exec.d.ts +6 -41
  43. package/dist/lib/exec.js +6 -41
  44. package/dist/lib/git.d.ts +13 -1
  45. package/dist/lib/git.js +36 -7
  46. package/dist/lib/harness/adapter.d.ts +7 -7
  47. package/dist/lib/harness/adapters/claude.js +3 -2
  48. package/dist/lib/hosts/remote-cmd.d.ts +9 -0
  49. package/dist/lib/hosts/remote-cmd.js +22 -0
  50. package/dist/lib/overdue.js +7 -37
  51. package/dist/lib/perf/db.d.ts +1 -1
  52. package/dist/lib/perf/db.js +1 -1
  53. package/dist/lib/scheduler.d.ts +21 -2
  54. package/dist/lib/scheduler.js +28 -5
  55. package/dist/lib/scheduling/routines.d.ts +21 -0
  56. package/dist/lib/scheduling/routines.js +56 -0
  57. package/dist/lib/session/active.d.ts +3 -31
  58. package/dist/lib/session/active.js +8 -68
  59. package/dist/lib/session/db.d.ts +4 -35
  60. package/dist/lib/session/db.js +4 -35
  61. package/dist/lib/session/discover.d.ts +6 -58
  62. package/dist/lib/session/discover.js +5 -43
  63. package/dist/lib/session/parse.d.ts +1 -19
  64. package/dist/lib/session/parse.js +2 -15
  65. package/dist/lib/share/provision.d.ts +3 -2
  66. package/dist/lib/share/provision.js +9 -4
  67. package/dist/lib/share/worker-template.d.ts +24 -1
  68. package/dist/lib/share/worker-template.js +65 -84
  69. package/dist/lib/startup/command-registry.d.ts +8 -2
  70. package/dist/lib/startup/command-registry.js +12 -4
  71. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.57
4
+
5
+ - **Removed two duplicate commands: `agents list` and `agents trash restore` (PHNX-3391).** `agents list` was a long-deprecated full duplicate of `agents view` (it already printed "agents list is now agents view" before delegating) — `agents view` is now the one version-listing surface. `agents trash restore <target>` was an exact duplicate of the top-level `agents restore <target>` (both call `restoreVersion`); `agents restore` stays, and `agents trash` keeps `trash list`. Both removed names now error as retired top-level commands rather than auto-correcting. Source: `cli/src/commands/versions.ts`, `cli/src/commands/trash.ts`.
6
+
7
+ - **`agents perf` moved to `agents insights perf` (PHNX-3391).** Performance metrics are an insight, not a top-level noun — latency now sits beside `agents insights cost` / `output` under the one `insights` group. Every subcommand is unchanged (`agents insights perf [hooks|commands|run|friction]`), the disposable warehouse at `~/.agents/.cache/perf/perf.db` is untouched, and bare `agents perf` now errors (retired top-level name) rather than auto-correcting. Source: `cli/src/commands/perf.ts`, `cli/src/commands/insights.ts`, `cli/src/cli/command-registry.ts`.
8
+
9
+ - **`agents insights mix` is now the one counter surface — the eight per-recipe shortcut commands, `recipes`, and `trends` are gone (PHNX-3391).** `agents insights mix` already printed the whole recipe board, yet each section was *also* a standalone command (`agents insights harness-mix`, `model-mix`, …), *also* reachable as `mix <recipe>`, *also* listed by `agents insights recipes`, and the board was *also* aliased by `agents insights trends` — one query engine wearing five surfaces (each registered twice, under `insights` and `sessions insights`). It collapses to one: `agents insights mix` (board), `agents insights mix <recipe>` (one section, e.g. `mix harness-mix`), and `agents insights mix --list` (the recipe ids). The removed spellings now error and point at `mix`. This is surface removal, not hiding — 20 command registrations deleted. Source: `cli/src/lib/analytics/mix-commands.ts`.
10
+
11
+ - **Worker boxes now show real Claude usage (S:/W:) bars in `agents view` (PHNX-3392).** A rate limit is metered per account, so the number is identical on every box — but only a headed device (`personal`/`desktop`) can read it, because the usage endpoint needs the `user:profile` scope only the interactive login carries. Headless workers, which have just the `user:inference` setup-token, got a 403 and showed blank bars. A new `usage-sync` daemon service closes the gap: each personal/desktop daemon pushes its per-account usage snapshot to worker peers, which merge it newest-wins (via the hidden `agents __usage-ingest` verb). Role-gated (personal/desktop publish, workers consume), single-executor per destination, and idempotent. A synced bar reads as last-seen, never a live fetch. Source: `cli/src/lib/accounting/usage-sync.ts`, `cli/src/lib/daemon/usage-sync-service.ts`.
12
+
13
+ - **`agents devices` gains a `desktop` role and stops double-labeling your interactive box (PHNX-3392).** `agents devices role <name>` now accepts a third value — `desktop` — for a headed always-on box (the release/credential home, e.g. a Mac mini): it is excluded from `--device auto` like `personal` (agents never auto-land there), but authenticates from its own interactive login like `personal` rather than a worker setup-token. And `agents devices list` no longer prints both `★ interactive` and `personal` on the same row — a `personal` box IS your interactive seat, so the star is folded into the role (it still shows for a non-personal box pinned as `interactive.host`). Source: `cli/src/lib/device-config.ts`, `cli/src/commands/ssh.ts`.
14
+
15
+ - **The system DotAgents repo `phnx-labs/.agents-system` was renamed to `phnx-labs/.agents` on GitHub, and both names are now recognized as the system origin (PHNX-3394).** The `-system` suffix was redundant once the layering itself is the role. The clone target stays `gh:phnx-labs/.agents-system` (GitHub's rename redirect makes that resolve fine, so there is no forced cutover), but a checkout under either name is recognized as the system origin across all transport forms (ssh / https / scp-style), via a `RENAMED_REMOTE_ALIASES` fold in `canonicalGitRemote` and the pure `isSystemRepoRemote` behind `isSystemRepoOrigin`. The companion extras repo `phnx-labs/.agents-extras` keeps its name but is now private and opt-in (`agents repo add`, not cloned by default). Source: `cli/src/lib/git.ts`.
16
+
17
+ ## 1.22.56
18
+
19
+ - **Scheduled routines claim a durable per-occurrence slot, so a duplicate delivery or a catch-up of the same slot can't double-launch (PHNX-3215).** The forward-timer path keyed its single-fire claim on croner's `currentRun()`, which is the jittered wall-clock fire instant, not the aligned schedule boundary — so two timer callbacks for one occurrence minted different run ids and both launched, and a live fire never collided with its catch-up twin. The scheduler now floors the fire to the aligned `(routine, scheduledFor)` boundary (`fireSlot`/`alignedSlotForFire`), the same derivation catch-up uses, making the atomic slot claim a structural guarantee (SING-15/16, self-overlap SING-13). Source: `cli/src/lib/scheduler.ts`, `cli/src/lib/scheduling/routines.ts`, `cli/src/lib/overdue.ts`.
20
+
21
+ - **Managed-share Open Graph cards render in Cloudflare instead of failing on every new publish (PHNX-2835).** The Worker upload now carries Yoga and resvg as compiled WebAssembly modules alongside the bundled JavaScript and fonts. The previous single-file bundle decoded the WASM into byte arrays and compiled it at request time; Node accepted that in tests, but Cloudflare workerd forbids runtime WASM code generation, so a new share's lazy `<slug>.png` render failed. A real-workerd regression test now renders and validates the PNG, and a genuine renderer failure returns a diagnostic `500` instead of falling through as a missing cover. Source: `cli/src/lib/share/{worker-template,provision}.ts`.
22
+
3
23
  ## 1.22.55
4
24
 
5
25
  - **Managed shares generate their Open Graph cover server-side (PHNX-2835).** Publishing HTML to `share.agents-cli.sh` no longer launches local Chromium. The Worker lazily renders a deterministic 1200×630 AGI card with Satori and resvg-wasm, bundles Inter and JetBrains Mono, inherits the canonical page's visibility gate, and caches the PNG in R2. BYO endpoints keep their local screenshot fallback. An explicit missing `AGENTS_SHARE_BROWSER` or `PUPPETEER_EXECUTABLE_PATH` now fails loudly instead of silently falling through. Source: `cli/src/lib/share/{capture,publish,worker-template}.ts`.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  <a href="https://github.com/phnx-labs/agi-cli"><img src="https://img.shields.io/badge/github-phnx--labs%2Fagi--cli-blue?style=flat-square" alt="github" /></a>
12
12
  </p>
13
13
 
14
- **A framework for running a distributed agent factory.** Dispatch Claude, Codex, Antigravity, Grok, and more across your own machines, in parallel, on your existing subscriptions. Measure every run with `agents perf` / `agents insights`, fold what you learn back into `AGENTS.md` and skills, then put the loop on a schedule with routines and monitors. Spawn parallel teams in isolated terminals or dispatch to the cloud for a PR. Watch live state across the fleet, nudge stalled runs, and message agents mid-flight. Store secrets behind Touch ID, drive real browsers and Electron apps, and steer the whole fleet from a menu bar — all from one CLI.
14
+ **A framework for running a distributed agent factory.** Dispatch Claude, Codex, Antigravity, Grok, and more across your own machines, in parallel, on your existing subscriptions. Measure every run with `agents insights` (latency lives at `agents insights perf`), fold what you learn back into `AGENTS.md` and skills, then put the loop on a schedule with routines and monitors. Spawn parallel teams in isolated terminals or dispatch to the cloud for a PR. Watch live state across the fleet, nudge stalled runs, and message agents mid-flight. Store secrets behind Touch ID, drive real browsers and Electron apps, and steer the whole fleet from a menu bar — all from one CLI.
15
15
 
16
16
  <p align="center">
17
17
  <a href="https://github.com/anthropics/claude-code" title="Claude Code"><img src="assets/harnesses/anthropic.svg" height="32" alt="Claude Code" /></a>
@@ -110,7 +110,7 @@ agents teams add checkout codex "Write tests for the new code" --name qa --afte
110
110
  agents teams start checkout --watch
111
111
 
112
112
  # Measure what happened -- latency, friction, dead-weight skills
113
- agents perf commands --days 7 # slowest CLI entrypoints
113
+ agents insights perf commands --days 7 # slowest CLI entrypoints
114
114
  agents insights --since 30d # friction, harness comparison, ranked actions
115
115
 
116
116
  # Fold the lesson back into the harness -- every agent picks it up next run
@@ -125,7 +125,7 @@ agents routines add nightly-payments-audit \
125
125
  agents menubar setup
126
126
  ```
127
127
 
128
- `agents perf` reads a disposable warehouse at `~/.agents/.cache/perf/perf.db` -- hook, command, and run timing rollups, deletable any time. `agents insights` (alias `agents sessions insights`) is deterministic and offline: it caches per-session facets, compares harnesses, and ranks actions by evidence count -- no model call unless you pass `--narrative`. Routines put any of this on a cron ([Routines](#routines)); monitors fire it on a change instead of a clock ([Monitors](#monitors)); the menu bar is the always-on control surface for the fleet these commands drive ([Menu bar](#menu-bar)).
128
+ `agents insights perf` reads a disposable warehouse at `~/.agents/.cache/perf/perf.db` -- hook, command, and run timing rollups, deletable any time. `agents insights` (alias `agents sessions insights`) is deterministic and offline: it caches per-session facets, compares harnesses, and ranks actions by evidence count -- no model call unless you pass `--narrative`. Routines put any of this on a cron ([Routines](#routines)); monitors fire it on a change instead of a clock ([Monitors](#monitors)); the menu bar is the always-on control surface for the fleet these commands drive ([Menu bar](#menu-bar)).
129
129
 
130
130
  ---
131
131
 
@@ -1407,7 +1407,7 @@ Two repos with the same shape, different roles:
1407
1407
 
1408
1408
  | Repo | Role | Owner |
1409
1409
  |---|---|---|
1410
- | `~/.agents-system/` | **System repo** — core/built-in skills, commands, hooks, rules, MCP configs, permissions, and profiles that ship with `agi-cli`. The defaults every install gets. | Maintained upstream at [phnx-labs/.agents-system](https://github.com/phnx-labs/.agents-system) |
1410
+ | `~/.agents-system/` | **System repo** — core/built-in skills, commands, hooks, rules, MCP configs, permissions, and profiles that ship with `agi-cli`. The defaults every install gets. | Maintained upstream at [phnx-labs/.agents](https://github.com/phnx-labs/.agents) (formerly `.agents-system`) |
1411
1411
  | `~/.agents/` | **User repo** — your personal additions and overrides. This is what `agents repo push`/`pull` syncs. | You |
1412
1412
 
1413
1413
  **Version pinning:** `agents.yaml` at project root pins which agent version to use (like `.nvmrc` for Node).
package/dist/bootstrap.js CHANGED
@@ -156,7 +156,9 @@ program.hook('postAction', (_thisCommand, actionCommand) => {
156
156
  }).catch(() => { });
157
157
  }
158
158
  // Disposable perf warehouse — fail-soft spool append (no SQLite on this path).
159
- if (durationMs !== undefined && parts[0] !== 'perf') {
159
+ // Skip the perf reader itself (now `agents insights perf`, PHNX-3391) so it
160
+ // never records its own latency into the board it prints.
161
+ if (durationMs !== undefined && !(parts[0] === 'insights' && parts[1] === 'perf')) {
160
162
  // sessionId/agent are resolvable here the same way emit() resolves them
161
163
  // for command.start/command.end above (the shared provenance floor,
162
164
  // event-provenance.ts) — without this, every command.end perf sample
@@ -65,7 +65,6 @@ export declare const loadRefreshRules: ModuleLoader;
65
65
  export declare const loadFactory: ModuleLoader;
66
66
  export declare const loadInsights: ModuleLoader;
67
67
  export declare const loadTrace: ModuleLoader;
68
- export declare const loadPerf: ModuleLoader;
69
68
  export declare const loadPty: ModuleLoader;
70
69
  export declare const loadTmux: ModuleLoader;
71
70
  export declare const loadWatchdog: ModuleLoader;
@@ -67,7 +67,6 @@ export const loadRefreshRules = async () => (await import('../commands/refresh-r
67
67
  export const loadFactory = async () => (await import('../commands/factory.js')).registerFactoryCommands;
68
68
  export const loadInsights = async () => (await import('../commands/insights.js')).registerInsightsCommand;
69
69
  export const loadTrace = async () => (await import('../commands/sessions-trace.js')).registerTraceCommand;
70
- export const loadPerf = async () => (await import('../commands/perf.js')).registerPerfCommand;
71
70
  export const loadPty = async () => (await import('../commands/pty.js')).registerPtyCommands;
72
71
  export const loadTmux = async () => (await import('../commands/tmux.js')).registerTmuxCommands;
73
72
  export const loadWatchdog = async () => (await import('../commands/watchdog.js')).registerWatchdogCommand;
@@ -142,7 +141,6 @@ export const COMMAND_LOADERS = {
142
141
  workflows: [loadWorkflows],
143
142
  add: [loadVersions],
144
143
  use: [loadVersions],
145
- list: [loadVersions],
146
144
  remove: [loadVersions],
147
145
  rm: [loadVersions],
148
146
  purge: [loadVersions],
@@ -177,7 +175,6 @@ export const COMMAND_LOADERS = {
177
175
  // `agents trace` is a top-level alias of `agents sessions trace` (precedent:
178
176
  // `agents insights` aliases `agents sessions insights`). One implementation.
179
177
  trace: [loadTrace],
180
- perf: [loadPerf],
181
178
  pty: [loadPty],
182
179
  tmux: [loadTmux],
183
180
  watchdog: [loadWatchdog],
@@ -431,7 +431,7 @@ async function handleTerminalHandoff(agentSpec, options, prompt) {
431
431
  if (!knownAgent) {
432
432
  const probeCwd = options.cwd ?? process.cwd();
433
433
  if (!hasProfile && !resolveWorkflowRef(rawTarget, probeCwd)) {
434
- console.error(chalk.red(`Unknown agent, profile, or workflow: ${rawTarget}. See \`agents list\` for the installed harnesses.`));
434
+ console.error(chalk.red(`Unknown agent, profile, or workflow: ${rawTarget}. See \`agents view\` for the installed harnesses.`));
435
435
  process.exit(1);
436
436
  }
437
437
  }
@@ -594,22 +594,22 @@ Examples:
594
594
  .addHelpText('after', `
595
595
  Shows aggregated stats for every hook that fired through a generated shim.
596
596
  Primary source: disposable SQLite warehouse ~/.agents/.cache/perf/perf.db
597
- (same data as \`agents perf hooks\`). Falls back to the legacy daily JSONL under
597
+ (same data as \`agents insights perf hooks\`). Falls back to the legacy daily JSONL under
598
598
  ~/.agents/.cache/logs/ when the warehouse is empty.
599
599
 
600
600
  Examples:
601
601
  agents hooks profile # last 7 days, table form
602
602
  agents hooks profile --days 30 # roll up the full month
603
603
  agents hooks profile --json | jq # pipe somewhere
604
- agents perf hooks # same rollup under the perf surface
604
+ agents insights perf hooks # same rollup under the perf surface
605
605
 
606
606
  A hook whose p99 exceeds --warn-ms gets flagged in the cache column. Add
607
607
  'cache: 5m' or 'cache: 5m-bg' to its hooks.yaml entry to fix it.
608
608
  `)
609
- .option('--project <key>', 'Scope to one project (see agents perf --help)')
609
+ .option('--project <key>', 'Scope to one project (see agents insights perf --help)')
610
610
  .action(async (options) => {
611
611
  const { DEFAULT_SLOW_HOOK_WARN_MS } = await import('../lib/hooks/profile.js');
612
- // Same rollup as `agents perf hooks` — this command predates the `perf`
612
+ // Same rollup as `agents insights perf hooks` — this command predates the `perf`
613
613
  // surface and is kept as a documented alias; delegate instead of
614
614
  // duplicating the SQLite-vs-legacy-JSONL fallback and table rendering.
615
615
  const { loadHookProfile, renderHookTable } = await import('./perf.js');
@@ -7,20 +7,21 @@
7
7
  * rhythm, edits) — split by Claude account by default
8
8
  * agents insights mix COUNTERS (sessions index + usage.db recipes:
9
9
  * harness/model mix, token ratios, secrets, browser)
10
- * agents insights <recipe> One baked mix recipe (harness-mix, tools-per-session, …)
10
+ * agents insights mix <recipe> One baked mix recipe (harness-mix, tools-per-session, …)
11
11
  * agents insights query Raw usage.db rows
12
12
  *
13
- * Sibling observe verbs (stay separate — different questions):
13
+ * Sibling observe verbs under this same group (different questions):
14
14
  *
15
15
  * agents insights cost what you spent ($ and duration)
16
16
  * agents insights output what shipped (burn vs PRs and commits)
17
+ * agents insights perf latency (hooks, CLI commands, agent.run) — not popularity
17
18
  * agents view live quota headroom (per account, with auth state)
18
- * agents perf latency (hooks, CLI commands, agent.run) — not popularity
19
19
  * agents sessions stats which skills/slash-commands were explicitly invoked
20
20
  *
21
21
  * Why mix lives here (not a second top-level `trends`): two abstract "analytics"
22
22
  * nouns taught agents and humans to guess. One verb, two engines — cheap SQL mix
23
- * vs transcript facets. Latency stays on `perf` so it is never confused with mix.
23
+ * vs transcript facets. `perf` (latency) is also nested here now (PHNX-3391) —
24
+ * performance is an insight, kept a distinct sub-verb so it is never confused with mix.
24
25
  *
25
26
  * Modelled on Claude Code's `/insights`, with the difference that motivated it: that
26
27
  * command reads one account's directory, while `balanced` rotation sprays sessions
@@ -31,7 +32,8 @@
31
32
  * the coaching prose by piping the AGGREGATE (never raw transcripts) through a headless
32
33
  * `claude -p`.
33
34
  *
34
- * Former top-level `agents trends` is `agents insights mix` / `agents insights trends`.
35
+ * Former top-level `agents trends` is `agents insights mix` (one section:
36
+ * `agents insights mix <recipe>`; `--list` names them).
35
37
  */
36
38
  import type { Command } from 'commander';
37
39
  export declare function registerInsightsCommand(program: Command): void;
@@ -7,20 +7,21 @@
7
7
  * rhythm, edits) — split by Claude account by default
8
8
  * agents insights mix COUNTERS (sessions index + usage.db recipes:
9
9
  * harness/model mix, token ratios, secrets, browser)
10
- * agents insights <recipe> One baked mix recipe (harness-mix, tools-per-session, …)
10
+ * agents insights mix <recipe> One baked mix recipe (harness-mix, tools-per-session, …)
11
11
  * agents insights query Raw usage.db rows
12
12
  *
13
- * Sibling observe verbs (stay separate — different questions):
13
+ * Sibling observe verbs under this same group (different questions):
14
14
  *
15
15
  * agents insights cost what you spent ($ and duration)
16
16
  * agents insights output what shipped (burn vs PRs and commits)
17
+ * agents insights perf latency (hooks, CLI commands, agent.run) — not popularity
17
18
  * agents view live quota headroom (per account, with auth state)
18
- * agents perf latency (hooks, CLI commands, agent.run) — not popularity
19
19
  * agents sessions stats which skills/slash-commands were explicitly invoked
20
20
  *
21
21
  * Why mix lives here (not a second top-level `trends`): two abstract "analytics"
22
22
  * nouns taught agents and humans to guess. One verb, two engines — cheap SQL mix
23
- * vs transcript facets. Latency stays on `perf` so it is never confused with mix.
23
+ * vs transcript facets. `perf` (latency) is also nested here now (PHNX-3391) —
24
+ * performance is an insight, kept a distinct sub-verb so it is never confused with mix.
24
25
  *
25
26
  * Modelled on Claude Code's `/insights`, with the difference that motivated it: that
26
27
  * command reads one account's directory, while `balanced` rotation sprays sessions
@@ -31,7 +32,8 @@
31
32
  * the coaching prose by piping the AGGREGATE (never raw transcripts) through a headless
32
33
  * `claude -p`.
33
34
  *
34
- * Former top-level `agents trends` is `agents insights mix` / `agents insights trends`.
35
+ * Former top-level `agents trends` is `agents insights mix` (one section:
36
+ * `agents insights mix <recipe>`; `--list` names them).
35
37
  */
36
38
  import * as fs from 'fs';
37
39
  import chalk from 'chalk';
@@ -47,6 +49,7 @@ import { formatUsd } from '../lib/pricing/index.js';
47
49
  import { formatDuration } from '../lib/session/render.js';
48
50
  import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
49
51
  import { registerMixCommands } from '../lib/analytics/mix-commands.js';
52
+ import { registerPerfSubcommand } from './perf.js';
50
53
  import { registerCostCommand } from './cost.js';
51
54
  import { registerOutputCommand } from './output.js';
52
55
  const execFileAsync = promisify(execFile);
@@ -570,7 +573,7 @@ function configureInsightsCommand(cmd) {
570
573
  # Counter mix board (harness/model/token/secrets recipes) — former agents trends
571
574
  agents insights mix
572
575
  agents insights mix --days 30
573
- agents insights harness-mix --json
576
+ agents insights mix harness-mix --json
574
577
  agents insights query --kind secret --days 7
575
578
 
576
579
  # One account only, all of its history
@@ -586,10 +589,10 @@ function configureInsightsCommand(cmd) {
586
589
  Two paths under one verb:
587
590
  bare \`agents insights\` — transcript behaviour (tools, friction, rhythm, by account)
588
591
  \`agents insights mix\` — cheap counters from sessions.db + usage.db
589
- Latency is \`agents perf\` (not mix). Quota is \`agents view\`. Spend is
592
+ Latency is \`agents insights perf\` (not mix). Quota is \`agents view\`. Spend is
590
593
  \`agents insights cost\`; shipped output is \`agents insights output\`. Skill/slash popularity
591
- is \`agents sessions stats\`. Former top-level \`agents trends\` is \`agents insights mix\`
592
- (also \`agents insights trends\`).
594
+ is \`agents sessions stats\`. Former top-level \`agents trends\` is \`agents insights mix\`.
595
+ One recipe is \`agents insights mix <recipe>\` (e.g. \`harness-mix\`); \`--list\` names them.
593
596
 
594
597
  The behavioural report parses in-scope transcripts once and caches facets; later runs
595
598
  re-read only files that changed. \`--refresh\` forces a full re-read.
@@ -606,6 +609,10 @@ export function registerInsightsCommand(program) {
606
609
  configureInsightsCommand(cmd);
607
610
  registerCostCommand(cmd);
608
611
  registerOutputCommand(cmd);
612
+ // Latency rollups (former top-level `agents perf` → `agents insights perf`,
613
+ // PHNX-3391). A sibling observe verb of cost/output, so it lives on the
614
+ // top-level insights group beside them, not under `sessions insights`.
615
+ registerPerfSubcommand(cmd);
609
616
  }
610
617
  export function registerSessionsInsightsCommand(sessions) {
611
618
  configureInsightsCommand(sessions.command('insights'));
@@ -1,12 +1,13 @@
1
1
  /**
2
- * `agents perf` — latency rollups over the disposable perf SQLite warehouse.
2
+ * `agents insights perf` — latency rollups over the disposable perf SQLite
3
+ * warehouse (former top-level `agents perf`, moved under insights in PHNX-3391).
3
4
  *
4
5
  * Subcommands:
5
- * agents perf multi-section summary (commands + hooks + runs)
6
- * agents perf hooks per-hook p50/p99 + cache hit rates
7
- * agents perf commands slowest CLI command paths (from command.end)
8
- * agents perf run agent.run / perf.timing labels
9
- * agents perf friction sessions stuck repeatedly hitting the same guard
6
+ * agents insights perf multi-section summary (commands + hooks + runs)
7
+ * agents insights perf hooks per-hook p50/p99 + cache hit rates
8
+ * agents insights perf commands slowest CLI command paths (from command.end)
9
+ * agents insights perf run agent.run / perf.timing labels
10
+ * agents insights perf friction sessions stuck repeatedly hitting the same guard
10
11
  *
11
12
  * Soft-joins sessions.db via shared string keys (session_id, agent, machine) —
12
13
  * no foreign keys. Warehouse lives at ~/.agents/.cache/perf/perf.db (safe to wipe).
@@ -49,5 +50,13 @@ export declare function loadHookProfile(days: number, project?: string): HookPro
49
50
  * exit 2 — see lib/friction-heuristics.ts for the grouping.
50
51
  */
51
52
  export declare function frictionAction(opts: PerfGlobalOpts): void;
52
- export declare function registerPerfCommand(program: Command): void;
53
+ /**
54
+ * Attach the `perf` latency-rollup command under a parent (the top-level
55
+ * `insights` group). Performance is an insight, not a top-level noun — `agents
56
+ * perf` was retired in favour of `agents insights perf` (PHNX-3391). It is a
57
+ * sibling of `agents insights cost` / `output`, registered in
58
+ * `registerInsightsCommand` beside them (top-level insights only, not
59
+ * `sessions insights`).
60
+ */
61
+ export declare function registerPerfSubcommand(parent: Command): void;
53
62
  export {};
@@ -1,12 +1,13 @@
1
1
  /**
2
- * `agents perf` — latency rollups over the disposable perf SQLite warehouse.
2
+ * `agents insights perf` — latency rollups over the disposable perf SQLite
3
+ * warehouse (former top-level `agents perf`, moved under insights in PHNX-3391).
3
4
  *
4
5
  * Subcommands:
5
- * agents perf multi-section summary (commands + hooks + runs)
6
- * agents perf hooks per-hook p50/p99 + cache hit rates
7
- * agents perf commands slowest CLI command paths (from command.end)
8
- * agents perf run agent.run / perf.timing labels
9
- * agents perf friction sessions stuck repeatedly hitting the same guard
6
+ * agents insights perf multi-section summary (commands + hooks + runs)
7
+ * agents insights perf hooks per-hook p50/p99 + cache hit rates
8
+ * agents insights perf commands slowest CLI command paths (from command.end)
9
+ * agents insights perf run agent.run / perf.timing labels
10
+ * agents insights perf friction sessions stuck repeatedly hitting the same guard
10
11
  *
11
12
  * Soft-joins sessions.db via shared string keys (session_id, agent, machine) —
12
13
  * no foreign keys. Warehouse lives at ~/.agents/.cache/perf/perf.db (safe to wipe).
@@ -190,7 +191,7 @@ export function frictionAction(opts) {
190
191
  // --cwd flag — so silently accepting the flag here would look like it
191
192
  // filtered when it did nothing. Fail loud instead of no-op.
192
193
  if (opts.project) {
193
- console.error(chalk.red("agents perf friction does not support --project yet — friction events carry no cwd to filter on."));
194
+ console.error(chalk.red("agents insights perf friction does not support --project yet — friction events carry no cwd to filter on."));
194
195
  process.exitCode = 1;
195
196
  return;
196
197
  }
@@ -224,7 +225,7 @@ function summaryAction(opts) {
224
225
  }, null, 2));
225
226
  return;
226
227
  }
227
- console.log(chalk.bold(`agents perf — last ${days} day${days === 1 ? '' : 's'}${project ? ` — project ${project}` : ''}`));
228
+ console.log(chalk.bold(`agents insights perf — last ${days} day${days === 1 ? '' : 's'}${project ? ` — project ${project}` : ''}`));
228
229
  console.log(chalk.gray(`warehouse: ${perfDbPath()} (disposable; soft-join sessions via session_id/agent/machine)`));
229
230
  console.log('');
230
231
  console.log(chalk.bold('Commands (slowest by p99)'));
@@ -246,7 +247,7 @@ function attachSharedOptions(cmd) {
246
247
  }
247
248
  /**
248
249
  * Commander binds a flag declared on both parent and child to the *parent*.
249
- * Merge so `agents perf commands --json` still sees json:true on the leaf.
250
+ * Merge so `agents insights perf commands --json` still sees json:true on the leaf.
250
251
  */
251
252
  function leafOpts(cmd) {
252
253
  const parent = cmd.parent && typeof cmd.parent.opts === 'function'
@@ -254,8 +255,16 @@ function leafOpts(cmd) {
254
255
  : {};
255
256
  return { ...parent, ...cmd.opts() };
256
257
  }
257
- export function registerPerfCommand(program) {
258
- const perf = program
258
+ /**
259
+ * Attach the `perf` latency-rollup command under a parent (the top-level
260
+ * `insights` group). Performance is an insight, not a top-level noun — `agents
261
+ * perf` was retired in favour of `agents insights perf` (PHNX-3391). It is a
262
+ * sibling of `agents insights cost` / `output`, registered in
263
+ * `registerInsightsCommand` beside them (top-level insights only, not
264
+ * `sessions insights`).
265
+ */
266
+ export function registerPerfSubcommand(parent) {
267
+ const perf = parent
259
268
  .command('perf')
260
269
  .description('Latency rollups from the disposable perf warehouse (hooks, commands, runs)')
261
270
  .addHelpText('after', `
@@ -264,16 +273,16 @@ Identity columns reuse sessions/events string shapes (session_id, agent, machine
264
273
  for soft cross-reference; there are no foreign keys.
265
274
 
266
275
  Examples:
267
- agents perf # summary: commands + hooks + runs
268
- agents perf hooks # per-hook p50/p95/p99 + cache hit rate
269
- agents perf commands --days 30 # slowest CLI entrypoints
270
- agents perf run --json # agent.run timings as JSON
271
- agents perf hooks --warn-ms 500
272
- agents perf hooks --project agents-cli # scope to one repo's samples
273
- agents perf friction # sessions stuck retrying the same guard block
276
+ agents insights perf # summary: commands + hooks + runs
277
+ agents insights perf hooks # per-hook p50/p95/p99 + cache hit rate
278
+ agents insights perf commands --days 30 # slowest CLI entrypoints
279
+ agents insights perf run --json # agent.run timings as JSON
280
+ agents insights perf hooks --warn-ms 500
281
+ agents insights perf hooks --project agents-cli # scope to one repo's samples
282
+ agents insights perf friction # sessions stuck retrying the same guard block
274
283
  `);
275
- // Options live on the parent so `agents perf --json` and
276
- // `agents perf commands --json` both work (see leafOpts).
284
+ // Options live on the parent so `agents insights perf --json` and
285
+ // `agents insights perf commands --json` both work (see leafOpts).
277
286
  attachSharedOptions(perf).action(function summary() {
278
287
  summaryAction(this.opts());
279
288
  });
@@ -538,7 +538,7 @@ Examples:
538
538
  }
539
539
  const version = resolveVersionAlias(agentId, rawVersion);
540
540
  if (!version) {
541
- console.log(chalk.red(`Pass a version: ${agentId}@<version>. Try 'default' or 'agents list ${agentId}'.`));
541
+ console.log(chalk.red(`Pass a version: ${agentId}@<version>. Try 'default' or 'agents view ${agentId}'.`));
542
542
  process.exit(1);
543
543
  }
544
544
  const installed = listInstalledVersions(agentId);
@@ -13,7 +13,7 @@ import { DEFAULT_BUCKET_NAME, DEFAULT_CF_BUNDLE, DEFAULT_SHARE_DOMAIN, DEFAULT_W
13
13
  import { addCustomDomain, configureBucketLifecycle, createBucket, deployWorker, enableWorkersDev, findZoneId, hashWorkerScript, putWorkerSecret, updateWorker, WORKER_PHOENIX_ID_BASE_SECRET, setWorkerSecret, } from '../lib/share/provision.js';
14
14
  import { publishFile, resolveShareUsername, parseMetaEntries, sanitizeLabel, resolveShareVisibility, scanShareContent, formatSensitiveContentError, SHARE_VISIBILITY_LEVELS, } from '../lib/share/publish.js';
15
15
  import { deleteShare, resolveDeleteTarget } from '../lib/share/delete.js';
16
- import { renderWorkerScript } from '../lib/share/worker-template.js';
16
+ import { renderWorkerBundle } from '../lib/share/worker-template.js';
17
17
  import { analyticsEnabled } from '../lib/share/analytics.js';
18
18
  import { phoenixIdBaseForDeploy, resolveShareBackend, } from '../lib/share/backend.js';
19
19
  import { resolveGitHubUsername } from '../lib/git.js';
@@ -54,7 +54,7 @@ export function formatSharePublishResult(result, json = false) {
54
54
  export function shareTemplateStatus(cfg) {
55
55
  if (!cfg.templateHash)
56
56
  return 'unknown';
57
- return cfg.templateHash === hashWorkerScript(renderWorkerScript()) ? 'current' : 'outdated';
57
+ return cfg.templateHash === hashWorkerScript(renderWorkerBundle().script) ? 'current' : 'outdated';
58
58
  }
59
59
  export async function runShareEdit(target, opts) {
60
60
  // Same public-listing gate as publish: label + every metadata value are
@@ -1155,7 +1155,8 @@ export async function runShareProvision(opts) {
1155
1155
  const bucketName = opts.bucket;
1156
1156
  const token = generateWriteToken();
1157
1157
  const requestedDomain = cleanHostname(opts.domain) ?? DEFAULT_SHARE_DOMAIN;
1158
- const script = renderWorkerScript();
1158
+ const worker = renderWorkerBundle();
1159
+ const script = worker.script;
1159
1160
  const spin = ora('Provisioning on Cloudflare…').start();
1160
1161
  try {
1161
1162
  const provisionOpts = opts.request ? { request: opts.request } : {};
@@ -1163,7 +1164,7 @@ export async function runShareProvision(opts) {
1163
1164
  spin.text = `R2 bucket '${bucketName}' ready`;
1164
1165
  await configureBucketLifecycle(apiToken, accountId, bucketName, provisionOpts);
1165
1166
  spin.text = `R2 bucket '${bucketName}' lifecycle ready`;
1166
- await deployWorker(apiToken, accountId, workerName, script, bucketName, provisionOpts);
1167
+ await deployWorker(apiToken, accountId, workerName, worker, bucketName, provisionOpts);
1167
1168
  spin.text = `Worker '${workerName}' deployed`;
1168
1169
  await setWorkerSecret(apiToken, accountId, workerName, token, provisionOpts);
1169
1170
  spin.text = `Worker '${workerName}' write token set`;
@@ -1230,14 +1231,14 @@ export async function runShareUpdate(opts = {}) {
1230
1231
  throw new Error("Share endpoint has no Cloudflare account id — `agents artifacts share update` cannot call the API. Pass --account <id>, or re-run 'agents artifacts share join'.");
1231
1232
  }
1232
1233
  const writeToken = readWriteToken();
1233
- const script = renderWorkerScript();
1234
+ const worker = renderWorkerBundle();
1234
1235
  const phoenixIdBase = phoenixIdBaseForDeploy({ managed: opts.managed }, cfg);
1235
1236
  const provisionOpts = {
1236
1237
  ...(opts.request ? { request: opts.request } : {}),
1237
1238
  force: opts.force,
1238
1239
  ...(phoenixIdBase !== undefined ? { phoenixIdBase } : {}),
1239
1240
  };
1240
- const result = await updateWorker(apiToken, accountId, cfg.workerName, cfg.bucketName, script, writeToken, cfg.templateHash, provisionOpts);
1241
+ const result = await updateWorker(apiToken, accountId, cfg.workerName, cfg.bucketName, worker, writeToken, cfg.templateHash, provisionOpts);
1241
1242
  if (!result.skipped) {
1242
1243
  writeShareConfig({ ...cfg, accountId, templateHash: result.templateHash });
1243
1244
  }
@@ -79,10 +79,14 @@ function deviceSummary(d, isSelf = false, stats, isInteractive = false, roles) {
79
79
  const marker = isSelf ? chalk.cyan('▸ ') : ' ';
80
80
  const name = isSelf ? chalk.bold.cyan(d.name.padEnd(16)) : chalk.bold(d.name.padEnd(16));
81
81
  const here = isSelf ? chalk.cyan(' ← this machine') : '';
82
- const interactive = isInteractive ? chalk.yellow(' ★ interactive') : '';
83
82
  // `roles` defaults to a single-device roster so a fleet-wide `role` default
84
83
  // still reaches this device even when it has no per-device doc of its own.
85
- const role = roleTag(d.name, roles ?? listConfiguredDeviceRoles([d.name]));
84
+ const roleMap = roles ?? listConfiguredDeviceRoles([d.name]);
85
+ // `personal` already means "the interactive box you sit at", so the star would
86
+ // just repeat it — show it only when the interactive host is NOT a personal box
87
+ // (e.g. an unmarked or worker box deliberately pinned as the artifact target).
88
+ const interactive = isInteractive && roleMap[d.name] !== 'personal' ? chalk.yellow(' ★ interactive') : '';
89
+ const role = roleTag(d.name, roleMap);
86
90
  return `${marker}${name} ${String(d.platform).padEnd(8)} ${(d.user ? d.user + '@' : '') + addr} ${online}${reach}${here}${interactive}${role}`;
87
91
  }
88
92
  /** The fleet-wide role mark, rendered for a device row. Empty when unmarked —
@@ -95,7 +99,9 @@ function roleTag(name, roles) {
95
99
  return chalk.green(' worker');
96
100
  if (role === 'personal')
97
101
  return chalk.yellow(' personal');
98
- return chalk.gray(' control');
102
+ if (role === 'desktop')
103
+ return chalk.cyan(' desktop');
104
+ return '';
99
105
  }
100
106
  const HEADROOM_BADGE = {
101
107
  idle: chalk.green('○ idle'),
@@ -257,7 +263,9 @@ export function renderDeviceTable(reg, names, self, statsMap, full = false, inte
257
263
  : '';
258
264
  const badge = HEADROOM_BADGE[headroom(stats)];
259
265
  const here = isSelf ? chalk.cyan(' ← this machine') : '';
260
- const interactive = name === interactiveHost ? chalk.yellow(' ★ interactive') : '';
266
+ // A `personal` role already conveys "your interactive seat"; don't double-
267
+ // render the star on it. Keep it for a non-personal pinned interactive host.
268
+ const interactive = name === interactiveHost && deviceRoles[name] !== 'personal' ? chalk.yellow(' ★ interactive') : '';
261
269
  lines.push(fitDeviceRow(`${marker}${label}${plat} ${specCell(stats, specWidth)}${load}${mem}${disk}${freeTotal} ${badge}${relay}${here}${interactive}`, role, desc, width));
262
270
  }
263
271
  // Fleet capacity summary — total cores, how much RAM is free right now, and
@@ -1449,12 +1457,12 @@ function registerDevicesCommands(program) {
1449
1457
  *
1450
1458
  * A role written here lands in that device's tracked per-device doc
1451
1459
  * (`devices/<name>/agents.yaml` `config.role`) and syncs with repo
1452
- * push/pull. The vocabulary is deliberately `worker | personal` only.
1460
+ * push/pull. The vocabulary is `worker | personal | desktop`.
1453
1461
  */
1454
1462
  const runDevicesRole = async (name, role, opts) => {
1455
1463
  if (!name) {
1456
1464
  if (role)
1457
- throw new Error('Name a device: agents devices role <name> <worker|personal>');
1465
+ throw new Error('Name a device: agents devices role <name> <worker|personal|desktop>');
1458
1466
  const reg = await loadDevices();
1459
1467
  // The full registered roster, not just online — a fleet-wide `role`
1460
1468
  // default must reach a registered device even when it has no per-device
@@ -1475,14 +1483,14 @@ function registerDevicesCommands(program) {
1475
1483
  }
1476
1484
  else {
1477
1485
  for (const [device, r] of marked) {
1478
- const tint = r === 'worker' ? chalk.green : r === 'personal' ? chalk.yellow : chalk.gray;
1486
+ const tint = r === 'worker' ? chalk.green : r === 'personal' ? chalk.yellow : r === 'desktop' ? chalk.cyan : chalk.gray;
1479
1487
  console.log(` ${device.padEnd(20)} ${tint(r)}`);
1480
1488
  }
1481
1489
  }
1482
1490
  console.log();
1483
1491
  console.log(chalk.bold('--device auto picks from: ') + (pool.length > 0 ? pool.join(', ') : chalk.red('nothing — no eligible device')));
1484
1492
  if (mode === 'all')
1485
- console.log(chalk.gray('auto.pool=all — worker marks are ignored (a personal device is still excluded).'));
1493
+ console.log(chalk.gray('auto.pool=all — worker marks are ignored (personal and desktop devices are still excluded).'));
1486
1494
  return;
1487
1495
  }
1488
1496
  await mustGetDevice(name);
@@ -1669,7 +1677,8 @@ function registerDevicesCommands(program) {
1669
1677
  });
1670
1678
  const roleCmd = devicesCmd
1671
1679
  .command('role [name] [role]')
1672
- .description('Show or set what a device is for: worker (agents run here) or personal (you sit here — never picked automatically). ' +
1680
+ .description('Show or set what a device is for: worker (agents run here), personal (you sit here), or desktop (a headed ' +
1681
+ 'always-on box — the release/credential home). Personal and desktop are never picked automatically. ' +
1673
1682
  'Marking any device worker makes `--device auto` an allowlist over the marked workers.')
1674
1683
  .option('--clear', 'remove the mark, returning the device to unmarked')
1675
1684
  .option('--json', 'output machine-readable JSON')
@@ -1688,6 +1697,7 @@ function registerDevicesCommands(program) {
1688
1697
  agents devices role yosemite-s0 worker # agents spin up here
1689
1698
  agents devices role yosemite-s1 worker # …and here; auto now rotates over these two only
1690
1699
  agents devices role zion personal # your laptop — keep automatic placement off it
1700
+ agents devices role mac-mini desktop # headed always-on box — also kept out of auto placement
1691
1701
  agents devices role yosemite-s0 --clear # unmark
1692
1702
  agents devices role --json # machine-readable
1693
1703
  `,
@@ -1699,12 +1709,12 @@ function registerDevicesCommands(program) {
1699
1709
 
1700
1710
  Effect on '--device auto' (agents run, teams, agents ssh auto, and the AGI
1701
1711
  EXT launch commands, which all resolve placement through the CLI):
1702
- no device marked -> every online device, as before
1703
- any worker marked -> ONLY the marked workers
1704
- personal -> never picked, under either state
1712
+ no device marked -> every online device, as before
1713
+ any worker marked -> ONLY the marked workers
1714
+ personal / desktop -> never picked, under either state
1705
1715
 
1706
- Turn the allowlist off with 'agents config set auto.pool all'; a personal
1707
- box stays excluded, since that is what the mark is for.
1716
+ Turn the allowlist off with 'agents config set auto.pool all'; personal and
1717
+ desktop boxes stay excluded, since that is what the mark is for.
1708
1718
  `,
1709
1719
  });
1710
1720
  const describeCmd = devicesCmd
@@ -9,13 +9,13 @@
9
9
  import type { Command } from 'commander';
10
10
  /**
11
11
  * Restore a soft-deleted version back into ~/.agents/.history/versions/.
12
- * Shared by `agents trash restore` and the top-level `agents restore` alias.
12
+ * Backs the top-level `agents restore` command.
13
13
  * Exits the process with a non-zero code on any failure.
14
14
  */
15
15
  export declare function restoreVersion(target: string): void;
16
16
  /**
17
17
  * Register the top-level `agents restore` command — a shorthand for
18
- * `agents trash restore` so users can undo a `remove`/`prune` directly.
18
+ * `agents restore` so users can undo a `remove`/`prune` directly.
19
19
  */
20
20
  export declare function registerRestoreCommand(program: Command): void;
21
21
  export declare function registerTrashCommands(program: Command): void;
@@ -112,7 +112,7 @@ function humanSize(bytes) {
112
112
  }
113
113
  /**
114
114
  * Restore a soft-deleted version back into ~/.agents/.history/versions/.
115
- * Shared by `agents trash restore` and the top-level `agents restore` alias.
115
+ * Backs the top-level `agents restore` command.
116
116
  * Exits the process with a non-zero code on any failure.
117
117
  */
118
118
  export function restoreVersion(target) {
@@ -157,7 +157,7 @@ export function restoreVersion(target) {
157
157
  }
158
158
  /**
159
159
  * Register the top-level `agents restore` command — a shorthand for
160
- * `agents trash restore` so users can undo a `remove`/`prune` directly.
160
+ * `agents restore` so users can undo a `remove`/`prune` directly.
161
161
  */
162
162
  export function registerRestoreCommand(program) {
163
163
  program
@@ -196,8 +196,4 @@ export function registerTrashCommands(program) {
196
196
  console.log();
197
197
  console.log(chalk.gray('Restore with: agents restore <agent>@<version>'));
198
198
  });
199
- trash
200
- .command('restore <target>')
201
- .description('Restore a soft-deleted version (e.g. "claude@2.1.110") back to ~/.agents/.history/versions/')
202
- .action((target) => restoreVersion(target));
203
199
  }