@phnx-labs/agents-cli 1.22.21 → 1.22.23

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 (105) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/README.md +8 -2
  3. package/dist/commands/doctor.js +15 -7
  4. package/dist/commands/exec.js +20 -6
  5. package/dist/commands/focus.d.ts +76 -4
  6. package/dist/commands/focus.js +219 -40
  7. package/dist/commands/fork.d.ts +20 -2
  8. package/dist/commands/fork.js +91 -64
  9. package/dist/commands/go.d.ts +25 -0
  10. package/dist/commands/go.js +63 -2
  11. package/dist/commands/harness-wizard.d.ts +206 -0
  12. package/dist/commands/harness-wizard.js +403 -0
  13. package/dist/commands/harness.d.ts +12 -0
  14. package/dist/commands/harness.js +97 -101
  15. package/dist/commands/insights.d.ts +32 -0
  16. package/dist/commands/insights.js +478 -0
  17. package/dist/commands/resume.js +10 -3
  18. package/dist/commands/secrets.js +25 -30
  19. package/dist/commands/sessions-picker.d.ts +2 -0
  20. package/dist/commands/sessions-picker.js +1 -0
  21. package/dist/commands/sessions-resume.d.ts +21 -3
  22. package/dist/commands/sessions-resume.js +50 -11
  23. package/dist/commands/sessions.d.ts +81 -5
  24. package/dist/commands/sessions.js +331 -66
  25. package/dist/commands/watchdog.js +13 -2
  26. package/dist/index.js +3 -1
  27. package/dist/lib/agents.d.ts +1 -1
  28. package/dist/lib/agents.js +132 -0
  29. package/dist/lib/codex-policy.d.ts +17 -0
  30. package/dist/lib/codex-policy.js +48 -0
  31. package/dist/lib/crabbox/lease.d.ts +25 -0
  32. package/dist/lib/crabbox/lease.js +62 -0
  33. package/dist/lib/daemon.js +70 -0
  34. package/dist/lib/exec.d.ts +4 -0
  35. package/dist/lib/exec.js +88 -54
  36. package/dist/lib/feed-broadcast.d.ts +1 -20
  37. package/dist/lib/feed-broadcast.js +31 -1
  38. package/dist/lib/hooks.js +12 -2
  39. package/dist/lib/hosts/passthrough.js +1 -0
  40. package/dist/lib/mcp.js +44 -0
  41. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +1 -5
  42. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  43. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -15
  44. package/dist/lib/models.d.ts +0 -5
  45. package/dist/lib/models.js +48 -0
  46. package/dist/lib/picker.d.ts +45 -0
  47. package/dist/lib/picker.js +75 -6
  48. package/dist/lib/plugin-marketplace.js +9 -0
  49. package/dist/lib/pricing/prices.json +119 -92
  50. package/dist/lib/pricing/table.js +13 -0
  51. package/dist/lib/remote-agents-json.d.ts +29 -1
  52. package/dist/lib/remote-agents-json.js +47 -10
  53. package/dist/lib/resources/mcp.js +2 -0
  54. package/dist/lib/resources/permissions.js +3 -0
  55. package/dist/lib/resources/types.d.ts +2 -1
  56. package/dist/lib/runner.js +28 -19
  57. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  58. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
  59. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  60. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
  61. package/dist/lib/secrets/index.d.ts +15 -1
  62. package/dist/lib/secrets/index.js +118 -49
  63. package/dist/lib/secrets/reaper.d.ts +87 -0
  64. package/dist/lib/secrets/reaper.js +184 -0
  65. package/dist/lib/secrets/remote.d.ts +29 -0
  66. package/dist/lib/secrets/remote.js +37 -1
  67. package/dist/lib/session/active.d.ts +36 -1
  68. package/dist/lib/session/active.js +60 -19
  69. package/dist/lib/session/actor-sidecar.d.ts +14 -0
  70. package/dist/lib/session/actor-sidecar.js +67 -4
  71. package/dist/lib/session/db.d.ts +35 -1
  72. package/dist/lib/session/db.js +132 -1
  73. package/dist/lib/session/digest.d.ts +3 -0
  74. package/dist/lib/session/digest.js +3 -3
  75. package/dist/lib/session/discover.js +168 -0
  76. package/dist/lib/session/insights.d.ts +126 -0
  77. package/dist/lib/session/insights.js +330 -0
  78. package/dist/lib/session/parse.d.ts +29 -2
  79. package/dist/lib/session/parse.js +111 -5
  80. package/dist/lib/session/remote-list.d.ts +10 -1
  81. package/dist/lib/session/remote-list.js +2 -8
  82. package/dist/lib/session/remote.d.ts +57 -3
  83. package/dist/lib/session/remote.js +90 -26
  84. package/dist/lib/session/resume-command.d.ts +6 -0
  85. package/dist/lib/session/resume-command.js +8 -0
  86. package/dist/lib/session/session-cache.d.ts +173 -0
  87. package/dist/lib/session/session-cache.js +399 -0
  88. package/dist/lib/session/types.d.ts +2 -2
  89. package/dist/lib/session/types.js +1 -1
  90. package/dist/lib/session/width.d.ts +1 -1
  91. package/dist/lib/session/width.js +12 -2
  92. package/dist/lib/shims.d.ts +2 -2
  93. package/dist/lib/shims.js +40 -5
  94. package/dist/lib/staleness/writers/hooks.js +1 -1
  95. package/dist/lib/startup/command-registry.d.ts +1 -0
  96. package/dist/lib/startup/command-registry.js +2 -0
  97. package/dist/lib/types.d.ts +1 -1
  98. package/dist/lib/usage.d.ts +13 -0
  99. package/dist/lib/usage.js +254 -3
  100. package/dist/lib/versions.js +13 -2
  101. package/package.json +1 -1
  102. package/dist/bin/agents +0 -0
  103. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  104. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  105. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
@@ -17,12 +17,47 @@ import chalk from 'chalk';
17
17
  import { stripVTControlCharacters } from 'node:util';
18
18
  const DEFAULT_TERMINAL_ROWS = 24;
19
19
  const DEFAULT_TERMINAL_WIDTH = 80;
20
+ /**
21
+ * Rows the detail preview is guaranteed when it is open and a row is selected.
22
+ * The list page is capped so this floor always fits the viewport — without it a
23
+ * long list (PICKER_RECENT_COUNT = 15) consumes the whole default 24-row
24
+ * terminal, `availablePreviewRows` goes <= 0, and the preview silently collapses
25
+ * to empty (RUSH-2198).
26
+ */
27
+ export const PREVIEW_MIN_ROWS = 6;
28
+ /** Floor for the visible list page, so a short terminal still shows a few rows. */
29
+ export const PICKER_MIN_LIST_ROWS = 3;
20
30
  function terminalWidth() {
21
31
  return Math.max(1, process.stdout.columns || DEFAULT_TERMINAL_WIDTH);
22
32
  }
23
33
  function terminalRows() {
24
34
  return Math.max(1, process.stdout.rows || DEFAULT_TERMINAL_ROWS);
25
35
  }
36
+ /**
37
+ * Cap the visible list page so an open preview keeps a guaranteed floor of rows.
38
+ *
39
+ * The picker renders header + list page + separator + preview + help. When the
40
+ * requested page size (e.g. 15) is large enough to fill the terminal on its own,
41
+ * the preview has no room left and collapses. This reserves
42
+ * {@link PREVIEW_MIN_ROWS} (plus its separator) for the preview and hands the
43
+ * list whatever remains, never below {@link PICKER_MIN_LIST_ROWS}.
44
+ *
45
+ * `chromeRows` counts the fixed non-list, non-preview lines (header, subtitle,
46
+ * help, any flash). `linesAbovePrompt` counts lines the caller printed above the
47
+ * Inquirer prompt that have scrolled the viewport but the picker cannot measure —
48
+ * today the session picker passes the hidden-session footer; subtracting it keeps
49
+ * that notice on screen alongside the preview. (The fleet browser folds its
50
+ * unreachable-peer warning into the header instead, so it needs no reserve here.)
51
+ */
52
+ export function pickerPageSize(opts) {
53
+ const previewMinRows = opts.previewMinRows ?? PREVIEW_MIN_ROWS;
54
+ const minListRows = opts.minListRows ?? PICKER_MIN_LIST_ROWS;
55
+ const linesAbove = Math.max(0, opts.linesAbovePrompt ?? 0);
56
+ // The separator line rides with the preview only when it is open.
57
+ const previewReserve = opts.previewOpen ? previewMinRows + 1 : 0;
58
+ const budget = opts.terminalRows - linesAbove - opts.chromeRows - previewReserve;
59
+ return Math.max(minListRows, Math.min(opts.requestedPageSize, budget));
60
+ }
26
61
  function renderedRows(text, width) {
27
62
  const normalizedWidth = Math.max(1, width);
28
63
  return text.split('\n').reduce((rows, line) => {
@@ -157,6 +192,17 @@ export function itemPicker(config) {
157
192
  : '(type to filter)';
158
193
  const searchStr = searchTerm ? chalk.cyan(searchTerm) : chalk.gray(placeholder);
159
194
  const header = [prefix, message, searchStr].filter(Boolean).join(' ');
195
+ // Cap the list page so an open preview keeps a guaranteed floor of rows.
196
+ // chrome = header + optional subtitle + help; the preview separator is
197
+ // reserved inside pickerPageSize.
198
+ const chromeRows = 1 + (cfg.subtitle ? 1 : 0) + 1;
199
+ const effectivePageSize = pickerPageSize({
200
+ requestedPageSize: cfg.pageSize ?? 10,
201
+ terminalRows: terminalRows(),
202
+ chromeRows,
203
+ previewOpen: previewOpen && Boolean(cfg.buildPreview),
204
+ linesAbovePrompt: cfg.linesAbovePrompt,
205
+ });
160
206
  const page = usePagination({
161
207
  items: results,
162
208
  active,
@@ -167,7 +213,7 @@ export function itemPicker(config) {
167
213
  const row = isActive ? chalk.bold(item.label) : item.label;
168
214
  return `${cursor} ${row}`;
169
215
  },
170
- pageSize: cfg.pageSize ?? 10,
216
+ pageSize: effectivePageSize,
171
217
  loop: false,
172
218
  });
173
219
  const enter = cfg.enterHint ?? 'select';
@@ -188,7 +234,7 @@ export function itemPicker(config) {
188
234
  renderedRows(parts.slice(1).join('\n'), width) +
189
235
  renderedRows(separator, width) +
190
236
  renderedRows(help, width);
191
- const availablePreviewRows = terminalRows() - fixedRows;
237
+ const availablePreviewRows = terminalRows() - Math.max(0, cfg.linesAbovePrompt ?? 0) - fixedRows;
192
238
  const preview = limitPreviewHeight(cfg.buildPreview(selected.value), availablePreviewRows, width);
193
239
  if (preview) {
194
240
  parts.push(separator);
@@ -283,6 +329,17 @@ export function multiItemPicker(config) {
283
329
  const placeholder = '(type to filter · space to toggle · enter to resume)';
284
330
  const searchStr = searchTerm ? chalk.cyan(searchTerm) : chalk.gray(placeholder);
285
331
  const header = [prefix, message, searchStr].filter(Boolean).join(' ');
332
+ // Cap the list page so an open preview keeps a guaranteed floor of rows.
333
+ // chrome = header + help; the preview separator is reserved inside
334
+ // pickerPageSize.
335
+ const chromeRows = 2;
336
+ const effectivePageSize = pickerPageSize({
337
+ requestedPageSize: cfg.pageSize ?? 10,
338
+ terminalRows: terminalRows(),
339
+ chromeRows,
340
+ previewOpen: previewOpen && Boolean(cfg.buildPreview),
341
+ linesAbovePrompt: cfg.linesAbovePrompt,
342
+ });
286
343
  const page = usePagination({
287
344
  items: results,
288
345
  active,
@@ -295,7 +352,7 @@ export function multiItemPicker(config) {
295
352
  const row = isActive ? chalk.bold(item.label) : item.label;
296
353
  return `${cursor} ${box} ${row}`;
297
354
  },
298
- pageSize: cfg.pageSize ?? 10,
355
+ pageSize: effectivePageSize,
299
356
  loop: false,
300
357
  });
301
358
  const enter = cfg.enterHint ?? 'resume';
@@ -312,7 +369,7 @@ export function multiItemPicker(config) {
312
369
  renderedRows(parts.slice(1).join('\n'), width) +
313
370
  renderedRows(separator, width) +
314
371
  renderedRows(help, width);
315
- const availablePreviewRows = terminalRows() - fixedRows;
372
+ const availablePreviewRows = terminalRows() - Math.max(0, cfg.linesAbovePrompt ?? 0) - fixedRows;
316
373
  const preview = limitPreviewHeight(cfg.buildPreview(selected.value), availablePreviewRows, width);
317
374
  if (preview) {
318
375
  parts.push(separator);
@@ -553,6 +610,18 @@ export function dynamicPicker(config) {
553
610
  headerBits.push(chalk.cyan('/' + query));
554
611
  }
555
612
  const header = headerBits.filter(Boolean).join(' ');
613
+ // Cap the list page so an open preview keeps a guaranteed floor of rows.
614
+ // chrome = header + help + optional flash line; the preview separator is
615
+ // reserved inside pickerPageSize. Only the loaded list steals viewport, so
616
+ // skip the cap while the loading placeholder is showing.
617
+ const chromeRows = 2 + (flash ? renderedRows(flash, terminalWidth()) : 0);
618
+ const effectivePageSize = pickerPageSize({
619
+ requestedPageSize: cfg.pageSize ?? 12,
620
+ terminalRows: terminalRows(),
621
+ chromeRows,
622
+ previewOpen: previewOpen && Boolean(cfg.buildPreview) && !loading,
623
+ linesAbovePrompt: cfg.linesAbovePrompt,
624
+ });
556
625
  const page = usePagination({
557
626
  items: results,
558
627
  active,
@@ -563,7 +632,7 @@ export function dynamicPicker(config) {
563
632
  const row = isActive ? chalk.bold(item.label) : item.label;
564
633
  return `${cursor} ${row}`;
565
634
  },
566
- pageSize: cfg.pageSize ?? 12,
635
+ pageSize: effectivePageSize,
567
636
  loop: false,
568
637
  });
569
638
  const help = chalk.gray(cfg.helpFor
@@ -590,7 +659,7 @@ export function dynamicPicker(config) {
590
659
  renderedRows(separator, width) +
591
660
  renderedRows(help, width) +
592
661
  flashRows;
593
- const availablePreviewRows = terminalRows() - fixedRows;
662
+ const availablePreviewRows = terminalRows() - Math.max(0, cfg.linesAbovePrompt ?? 0) - fixedRows;
594
663
  const preview = limitPreviewHeight(cfg.buildPreview(selected.value), availablePreviewRows, width);
595
664
  if (preview) {
596
665
  parts.push(separator);
@@ -121,6 +121,11 @@ function dirExists(p) {
121
121
  }
122
122
  // ─── Per-version paths ────────────────────────────────────────────────────────
123
123
  function pluginsRootForVersion(agent, versionHome) {
124
+ // Muse Code keeps its plugin store under XDG data (`~/.local/share/muse/plugins`),
125
+ // not under the config dir. Under HOME isolation the version home is $HOME.
126
+ if (agent === 'muse') {
127
+ return path.join(versionHome, '.local', 'share', 'muse', 'plugins');
128
+ }
124
129
  return path.join(versionHome, agentConfigDirName(agent), 'plugins');
125
130
  }
126
131
  export function marketplaceRoot(specOrName, agent, versionHome) {
@@ -678,6 +683,10 @@ export function addPluginToSettings(pluginName, marketplaceName, agent, versionH
678
683
  settings = {};
679
684
  }
680
685
  }
686
+ // Muse refuses to start without schema_version: 1.
687
+ if (agent === 'muse' && settings.schema_version === undefined) {
688
+ settings.schema_version = 1;
689
+ }
681
690
  if (!settings.enabledPlugins || typeof settings.enabledPlugins !== 'object') {
682
691
  settings.enabledPlugins = {};
683
692
  }
@@ -1,153 +1,180 @@
1
1
  {
2
- "version": "2026-08-03",
2
+ "version": "2026-08-05",
3
3
  "models": {
4
4
  "claude-opus-4": {
5
+ "inputPerToken": 5e-06,
6
+ "outputPerToken": 2.5e-05,
7
+ "cacheReadPerToken": 5e-07,
8
+ "cacheWritePerToken": 6.25e-06
9
+ },
10
+ "claude-sonnet-4": {
11
+ "inputPerToken": 3e-06,
12
+ "outputPerToken": 1.5e-05,
13
+ "cacheReadPerToken": 3e-07,
14
+ "cacheWritePerToken": 3.75e-06
15
+ },
16
+ "claude-haiku-4": {
17
+ "inputPerToken": 1e-06,
18
+ "outputPerToken": 5e-06,
19
+ "cacheReadPerToken": 1e-07,
20
+ "cacheWritePerToken": 1.25e-06
21
+ },
22
+ "claude-opus-5": {
5
23
  "inputPerToken": 0.000005,
6
24
  "outputPerToken": 0.000025,
7
25
  "cacheReadPerToken": 0.0000005,
8
26
  "cacheWritePerToken": 0.00000625
9
27
  },
10
- "claude-sonnet-4": {
11
- "inputPerToken": 0.000003,
12
- "outputPerToken": 0.000015,
13
- "cacheReadPerToken": 0.0000003,
14
- "cacheWritePerToken": 0.00000375
15
- },
16
- "claude-haiku-4": {
17
- "inputPerToken": 0.000001,
18
- "outputPerToken": 0.000005,
19
- "cacheReadPerToken": 0.0000001,
20
- "cacheWritePerToken": 0.00000125
28
+ "claude-sonnet-5": {
29
+ "inputPerToken": 0.000002,
30
+ "outputPerToken": 0.00001,
31
+ "cacheReadPerToken": 0.0000002,
32
+ "cacheWritePerToken": 0.0000025
21
33
  },
22
34
  "claude-fable-5": {
23
- "inputPerToken": 0.00001,
24
- "outputPerToken": 0.00005,
25
- "cacheReadPerToken": 0.000001,
26
- "cacheWritePerToken": 0.0000125
35
+ "inputPerToken": 1e-05,
36
+ "outputPerToken": 5e-05,
37
+ "cacheReadPerToken": 1e-06,
38
+ "cacheWritePerToken": 1.25e-05
27
39
  },
28
40
  "claude-mythos-5": {
29
- "inputPerToken": 0.00001,
30
- "outputPerToken": 0.00005,
31
- "cacheReadPerToken": 0.000001,
32
- "cacheWritePerToken": 0.0000125
41
+ "inputPerToken": 1e-05,
42
+ "outputPerToken": 5e-05,
43
+ "cacheReadPerToken": 1e-06,
44
+ "cacheWritePerToken": 1.25e-05
33
45
  },
34
46
  "claude-3-5-sonnet": {
35
- "inputPerToken": 0.000003,
36
- "outputPerToken": 0.000015,
37
- "cacheReadPerToken": 0.0000003,
38
- "cacheWritePerToken": 0.00000375
47
+ "inputPerToken": 3e-06,
48
+ "outputPerToken": 1.5e-05,
49
+ "cacheReadPerToken": 3e-07,
50
+ "cacheWritePerToken": 3.75e-06
39
51
  },
40
52
  "claude-3-5-haiku": {
41
- "inputPerToken": 0.0000008,
42
- "outputPerToken": 0.000004,
43
- "cacheReadPerToken": 0.00000008,
44
- "cacheWritePerToken": 0.000001
53
+ "inputPerToken": 8e-07,
54
+ "outputPerToken": 4e-06,
55
+ "cacheReadPerToken": 8e-08,
56
+ "cacheWritePerToken": 1e-06
45
57
  },
46
58
  "claude-3-opus": {
47
- "inputPerToken": 0.000015,
48
- "outputPerToken": 0.000075,
49
- "cacheReadPerToken": 0.0000015,
50
- "cacheWritePerToken": 0.00001875
59
+ "inputPerToken": 1.5e-05,
60
+ "outputPerToken": 7.5e-05,
61
+ "cacheReadPerToken": 1.5e-06,
62
+ "cacheWritePerToken": 1.875e-05
51
63
  },
52
64
  "gpt-5.6-sol": {
53
- "inputPerToken": 0.000005,
54
- "outputPerToken": 0.00003,
55
- "cacheReadPerToken": 0.0000005
65
+ "inputPerToken": 5e-06,
66
+ "outputPerToken": 3e-05,
67
+ "cacheReadPerToken": 5e-07
56
68
  },
57
69
  "gpt-5.6-terra": {
58
- "inputPerToken": 0.0000025,
59
- "outputPerToken": 0.000015,
60
- "cacheReadPerToken": 0.00000025
70
+ "inputPerToken": 2.5e-06,
71
+ "outputPerToken": 1.5e-05,
72
+ "cacheReadPerToken": 2.5e-07
61
73
  },
62
74
  "gpt-5.6-luna": {
63
- "inputPerToken": 0.000001,
64
- "outputPerToken": 0.000006,
65
- "cacheReadPerToken": 0.0000001
75
+ "inputPerToken": 1e-06,
76
+ "outputPerToken": 6e-06,
77
+ "cacheReadPerToken": 1e-07
66
78
  },
67
79
  "gpt-5.5": {
68
- "inputPerToken": 0.000005,
69
- "outputPerToken": 0.00003,
70
- "cacheReadPerToken": 0.0000005
80
+ "inputPerToken": 5e-06,
81
+ "outputPerToken": 3e-05,
82
+ "cacheReadPerToken": 5e-07
71
83
  },
72
84
  "gpt-5.4": {
73
- "inputPerToken": 0.0000025,
74
- "outputPerToken": 0.000015,
75
- "cacheReadPerToken": 0.00000025
85
+ "inputPerToken": 2.5e-06,
86
+ "outputPerToken": 1.5e-05,
87
+ "cacheReadPerToken": 2.5e-07
76
88
  },
77
89
  "gpt-5.4-mini": {
78
- "inputPerToken": 0.00000075,
79
- "outputPerToken": 0.0000045,
80
- "cacheReadPerToken": 0.000000075
90
+ "inputPerToken": 7.5e-07,
91
+ "outputPerToken": 4.5e-06,
92
+ "cacheReadPerToken": 7.5e-08
81
93
  },
82
94
  "gpt-5.4-nano": {
83
- "inputPerToken": 0.0000002,
84
- "outputPerToken": 0.00000125,
85
- "cacheReadPerToken": 0.00000002
95
+ "inputPerToken": 2e-07,
96
+ "outputPerToken": 1.25e-06,
97
+ "cacheReadPerToken": 2e-08
86
98
  },
87
99
  "gpt-5": {
88
- "inputPerToken": 0.00000125,
89
- "outputPerToken": 0.00001,
90
- "cacheReadPerToken": 0.000000125
100
+ "inputPerToken": 1.25e-06,
101
+ "outputPerToken": 1e-05,
102
+ "cacheReadPerToken": 1.25e-07
91
103
  },
92
104
  "gpt-4.1": {
93
- "inputPerToken": 0.000002,
94
- "outputPerToken": 0.000008,
95
- "cacheReadPerToken": 0.0000005
105
+ "inputPerToken": 2e-06,
106
+ "outputPerToken": 8e-06,
107
+ "cacheReadPerToken": 5e-07
96
108
  },
97
109
  "gpt-4.1-mini": {
98
- "inputPerToken": 0.0000004,
99
- "outputPerToken": 0.0000016,
100
- "cacheReadPerToken": 0.0000001
110
+ "inputPerToken": 4e-07,
111
+ "outputPerToken": 1.6e-06,
112
+ "cacheReadPerToken": 1e-07
101
113
  },
102
114
  "gpt-4.1-nano": {
103
- "inputPerToken": 0.0000001,
104
- "outputPerToken": 0.0000004,
105
- "cacheReadPerToken": 0.000000025
115
+ "inputPerToken": 1e-07,
116
+ "outputPerToken": 4e-07,
117
+ "cacheReadPerToken": 2.5e-08
106
118
  },
107
119
  "gpt-4o": {
108
- "inputPerToken": 0.0000025,
109
- "outputPerToken": 0.00001,
110
- "cacheReadPerToken": 0.00000125
120
+ "inputPerToken": 2.5e-06,
121
+ "outputPerToken": 1e-05,
122
+ "cacheReadPerToken": 1.25e-06
111
123
  },
112
124
  "gpt-4o-mini": {
113
- "inputPerToken": 0.00000015,
114
- "outputPerToken": 0.0000006,
115
- "cacheReadPerToken": 0.000000075
125
+ "inputPerToken": 1.5e-07,
126
+ "outputPerToken": 6e-07,
127
+ "cacheReadPerToken": 7.5e-08
116
128
  },
117
129
  "o3": {
118
- "inputPerToken": 0.000002,
119
- "outputPerToken": 0.000008,
120
- "cacheReadPerToken": 0.0000005
130
+ "inputPerToken": 2e-06,
131
+ "outputPerToken": 8e-06,
132
+ "cacheReadPerToken": 5e-07
121
133
  },
122
134
  "o4-mini": {
123
- "inputPerToken": 0.0000011,
124
- "outputPerToken": 0.0000044,
125
- "cacheReadPerToken": 0.000000275
135
+ "inputPerToken": 1.1e-06,
136
+ "outputPerToken": 4.4e-06,
137
+ "cacheReadPerToken": 2.75e-07
126
138
  },
127
139
  "gemini-2.5-pro": {
128
- "inputPerToken": 0.00000125,
129
- "outputPerToken": 0.00001,
130
- "cacheReadPerToken": 0.0000003125
140
+ "inputPerToken": 1.25e-06,
141
+ "outputPerToken": 1e-05,
142
+ "cacheReadPerToken": 3.125e-07
131
143
  },
132
144
  "gemini-2.5-flash": {
133
- "inputPerToken": 0.0000003,
134
- "outputPerToken": 0.0000025,
135
- "cacheReadPerToken": 0.000000075
145
+ "inputPerToken": 3e-07,
146
+ "outputPerToken": 2.5e-06,
147
+ "cacheReadPerToken": 7.5e-08
136
148
  },
137
149
  "gemini-2.5-flash-lite": {
138
- "inputPerToken": 0.0000001,
139
- "outputPerToken": 0.0000004,
140
- "cacheReadPerToken": 0.000000025
150
+ "inputPerToken": 1e-07,
151
+ "outputPerToken": 4e-07,
152
+ "cacheReadPerToken": 2.5e-08
141
153
  },
142
154
  "gemini-1.5-pro": {
143
- "inputPerToken": 0.00000125,
144
- "outputPerToken": 0.000005,
145
- "cacheReadPerToken": 0.0000003125
155
+ "inputPerToken": 1.25e-06,
156
+ "outputPerToken": 5e-06,
157
+ "cacheReadPerToken": 3.125e-07
146
158
  },
147
159
  "gemini-1.5-flash": {
148
- "inputPerToken": 0.000000075,
149
- "outputPerToken": 0.0000003,
150
- "cacheReadPerToken": 0.00000001875
160
+ "inputPerToken": 7.5e-08,
161
+ "outputPerToken": 3e-07,
162
+ "cacheReadPerToken": 1.875e-08
163
+ },
164
+ "muse-spark-1.2": {
165
+ "inputPerToken": 1.25e-06,
166
+ "outputPerToken": 4.25e-06,
167
+ "cacheReadPerToken": 1.5e-07
168
+ },
169
+ "muse-spark-1.1": {
170
+ "inputPerToken": 1.25e-06,
171
+ "outputPerToken": 4.25e-06,
172
+ "cacheReadPerToken": 1.5e-07
173
+ },
174
+ "muse-spark-1.2-contributor": {
175
+ "inputPerToken": 1e-07,
176
+ "outputPerToken": 2e-07,
177
+ "cacheReadPerToken": 2e-09
151
178
  }
152
179
  }
153
180
  }
@@ -5,6 +5,19 @@
5
5
  * and is imported with a `type: json` attribute so it survives `tsc` emit AND
6
6
  * Node ESM's import-attribute requirement at runtime (the package is ESM).
7
7
  *
8
+ * ## Keeping it current
9
+ *
10
+ * A model missing from `prices.json` prices to NOTHING — `getModelPricing` returns
11
+ * null and the session contributes $0, silently. That is how 478 `claude-opus-5`
12
+ * sessions read as free: matching is dash-bounded, so `claude-opus-5` cannot fall back
13
+ * to the `claude-opus-4` entry. When a new model ships, add it here in the same change.
14
+ *
15
+ * **Dated rate: Claude Sonnet 5 is priced at its introductory $2/$10 per MTok, which
16
+ * ends 2026-08-31.** From 2026-09-01 the standard rate is $3/$15 (cache write $3.75,
17
+ * cache read $0.30). This table has no notion of an effective date — every row is a
18
+ * single current rate — so that entry MUST be updated then, or Sonnet 5 spend will
19
+ * read ~33% low. Source: https://platform.claude.com/docs/en/about-claude/pricing
20
+ *
8
21
  * `getModelPricing` is prefix/suffix-tolerant: real model identifiers carry
9
22
  * vendor prefixes (`us.anthropic.`), version dashes (`claude-opus-4-8`), and
10
23
  * date suffixes (`-20250514`), none of which appear in the canonical keys. We
@@ -12,6 +12,34 @@ export interface RemoteAgentsJsonOptions<T> {
12
12
  quiet?: boolean;
13
13
  /** Per-peer deadline. Long-running maintenance commands override 12 seconds. */
14
14
  timeoutMs?: number;
15
+ /**
16
+ * Opt-in early-exit for a globally-unique lookup (a full session UUID). When a
17
+ * peer returns an item that satisfies {@link isDefinitive}, the fan-out
18
+ * resolves immediately and SIGTERMs every still-outstanding peer instead of
19
+ * waiting for the slowest one to hit {@link REMOTE_TIMEOUT_MS}.
20
+ *
21
+ * OMITTED BY DEFAULT — tool-search, program-count, and the default session
22
+ * listing keep the all-settle behavior (ambiguity, or a label/prefix conflict,
23
+ * is only known once every peer has answered). Only the full-UUID resolve path
24
+ * opts in, since only a UUID guarantees the first hit is the only hit.
25
+ */
26
+ earlyExit?: {
27
+ isDefinitive: (item: T, machine: string) => boolean;
28
+ };
29
+ }
30
+ /**
31
+ * The SSH boundary as a swappable dependency so the fan-out logic is testable
32
+ * without a live tailnet. Production wires this to the real {@link sshCapture}.
33
+ */
34
+ export type SshCaptureFn = (target: string, remoteCmd: string, opts: {
35
+ timeoutMs: number;
36
+ signal?: AbortSignal;
37
+ }) => Promise<{
38
+ code: number | null;
39
+ stdout: string;
40
+ }>;
41
+ export interface GatherRemoteAgentsJsonDeps {
42
+ capture?: SshCaptureFn;
15
43
  }
16
44
  export interface RemoteAgentsJsonParseResult<T> {
17
45
  items: T[];
@@ -35,4 +63,4 @@ export declare function parseRemoteAgentsJsonPayload<T>(stdout: string, machine:
35
63
  /** Build the command one peer runs, with a guard that prevents recursive fan-out. */
36
64
  export declare function remoteAgentsJsonCommand(args: string[], noFanoutEnv: string, os?: string): string;
37
65
  /** Query explicit hosts, or every registered online peer when hosts is omitted. */
38
- export declare function gatherRemoteAgentsJson<T>(options: RemoteAgentsJsonOptions<T>): Promise<RemoteAgentsJsonResult<T>>;
66
+ export declare function gatherRemoteAgentsJson<T>(options: RemoteAgentsJsonOptions<T>, deps?: GatherRemoteAgentsJsonDeps): Promise<RemoteAgentsJsonResult<T>>;
@@ -7,6 +7,7 @@
7
7
  * healthy results from the rest of the fleet.
8
8
  */
9
9
  import { spawn } from 'child_process';
10
+ import { setMaxListeners } from 'node:events';
10
11
  import chalk from 'chalk';
11
12
  import { SSH_OPTS, controlOpts, assertValidSshTarget, shellQuote } from './ssh-exec.js';
12
13
  import { sshTargetFor } from './devices/connect.js';
@@ -32,9 +33,13 @@ export function remoteAgentsJsonCommand(args, noFanoutEnv, os) {
32
33
  const inner = `${noFanoutEnv}=1 agents ${args.map(shellQuote).join(' ')}`;
33
34
  return `bash -lc ${shellQuote(inner)}`;
34
35
  }
35
- function sshCapture(target, remoteCmd, timeoutMs) {
36
+ const sshCapture = (target, remoteCmd, { timeoutMs, signal }) => {
36
37
  assertValidSshTarget(target);
37
38
  return new Promise((resolve) => {
39
+ if (signal?.aborted) {
40
+ resolve({ code: null, stdout: '' });
41
+ return;
42
+ }
38
43
  const args = [...SSH_OPTS, ...controlOpts(), target, remoteCmd];
39
44
  const child = spawn('ssh', args, { stdio: ['ignore', 'pipe', 'ignore'] });
40
45
  let stdout = '';
@@ -44,19 +49,25 @@ function sshCapture(target, remoteCmd, timeoutMs) {
44
49
  return;
45
50
  settled = true;
46
51
  clearTimeout(timer);
52
+ signal?.removeEventListener('abort', onAbort);
47
53
  resolve({ code, stdout });
48
54
  };
49
55
  const timer = setTimeout(() => {
50
56
  child.kill('SIGKILL');
51
57
  done(null);
52
58
  }, timeoutMs);
59
+ // A definitive hit on a fast peer cancels the rest: SIGTERM the child so an
60
+ // unreachable peer is not left running to its full timeout.
61
+ const onAbort = () => { child.kill('SIGTERM'); done(null); };
62
+ signal?.addEventListener('abort', onAbort, { once: true });
53
63
  child.stdout.on('data', (data) => { stdout += data.toString(); });
54
64
  child.on('error', () => done(null));
55
65
  child.on('close', (code) => done(code));
56
66
  });
57
- }
67
+ };
58
68
  /** Query explicit hosts, or every registered online peer when hosts is omitted. */
59
- export async function gatherRemoteAgentsJson(options) {
69
+ export async function gatherRemoteAgentsJson(options, deps = {}) {
70
+ const capture = deps.capture ?? sshCapture;
60
71
  const self = machineId();
61
72
  const targets = [];
62
73
  if (options.hosts && options.hosts.length > 0) {
@@ -100,24 +111,50 @@ export async function gatherRemoteAgentsJson(options) {
100
111
  }
101
112
  const skipped = [];
102
113
  const parseFailed = [];
114
+ // The controller exists ONLY for early-exit: the first definitive hit aborts
115
+ // every still-outstanding peer. When earlyExit is not requested there is no
116
+ // controller and no per-peer abort listener, so the default path is a plain
117
+ // all-settle Promise.all, byte-identical to before (tool-search, program-count).
118
+ const controller = options.earlyExit ? new AbortController() : undefined;
119
+ // A fleet can hold more peers than Node's default 10-listener cap; each peer's
120
+ // capture adds one abort listener, so lift the cap to avoid a spurious warning.
121
+ if (controller)
122
+ setMaxListeners(targets.length + 1, controller.signal);
123
+ let earlyResolved = false;
103
124
  const results = await Promise.all(targets.map(async (target) => {
104
125
  const command = remoteAgentsJsonCommand(options.args, options.noFanoutEnv, target.os);
105
- const result = await sshCapture(target.target, command, options.timeoutMs ?? REMOTE_TIMEOUT_MS);
126
+ const result = await capture(target.target, command, {
127
+ timeoutMs: options.timeoutMs ?? REMOTE_TIMEOUT_MS,
128
+ signal: controller?.signal,
129
+ });
130
+ // A peer we deliberately cancelled is neither a hit nor a failure — it never
131
+ // got to answer, so it must not pollute skipped/parseFailed (which drive the
132
+ // "unreachable" listing and the fail-closed partial-resolution gate).
133
+ const cancelled = controller?.signal.aborted ?? false;
106
134
  if (result.code !== 0) {
107
- skipped.push(target.name);
108
- if (!options.quiet) {
109
- process.stderr.write(chalk.gray(` ${target.name}: unreachable or no agents CLI — skipped\n`));
135
+ if (!cancelled) {
136
+ skipped.push(target.name);
137
+ if (!options.quiet) {
138
+ process.stderr.write(chalk.gray(` ${target.name}: unreachable or no agents CLI — skipped\n`));
139
+ }
110
140
  }
111
141
  return [];
112
142
  }
113
143
  const parsed = parseRemoteAgentsJsonPayload(result.stdout, target.machine, options.parse);
114
144
  if (parsed.parseFailed) {
115
- parseFailed.push(target.name);
116
- if (!options.quiet) {
117
- process.stderr.write(chalk.gray(` ${target.name}: unreachable or no agents CLI — skipped\n`));
145
+ if (!cancelled) {
146
+ parseFailed.push(target.name);
147
+ if (!options.quiet) {
148
+ process.stderr.write(chalk.gray(` ${target.name}: unreachable or no agents CLI — skipped\n`));
149
+ }
118
150
  }
119
151
  return [];
120
152
  }
153
+ if (controller && options.earlyExit && !earlyResolved
154
+ && parsed.items.some((item) => options.earlyExit.isDefinitive(item, target.machine))) {
155
+ earlyResolved = true;
156
+ controller.abort(); // SIGTERM the remaining peers; they settle fast below.
157
+ }
121
158
  return parsed.items;
122
159
  }));
123
160
  return { items: results.flat(), deviceCount: targets.length, skipped, parseFailed, discoveryFailed: false };
@@ -127,6 +127,8 @@ export function getMcpConfigPath(agent, versionHome) {
127
127
  case 'pi':
128
128
  // omp reads user-scope MCP from ~/.omp/agent/.mcp.json (Claude schema).
129
129
  return path.join(versionHome, '.omp', 'agent', '.mcp.json');
130
+ case 'muse':
131
+ return path.join(versionHome, '.config', 'muse', 'settings.json');
130
132
  default:
131
133
  return null;
132
134
  }
@@ -81,6 +81,9 @@ function getAgentConfigPath(agent, versionHome) {
81
81
  return path.join(versionHome, '.hermes', 'config.yaml');
82
82
  case 'goose':
83
83
  return path.join(versionHome, '.config', 'goose', 'permission.yaml');
84
+ case 'muse':
85
+ // Muse approval defaults live in settings.json; no separate permissions file.
86
+ return path.join(versionHome, '.config', 'muse', 'settings.json');
84
87
  default:
85
88
  return null;
86
89
  }
@@ -5,7 +5,8 @@
5
5
  * - Union: All resources from all layers are combined
6
6
  * - Override on name conflict: Higher layer wins (project > user > system)
7
7
  */
8
- export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid' | 'hermes' | 'pi';
8
+ import type { AgentId } from '../types.js';
9
+ export type { AgentId };
9
10
  /** Resource origin. Precedence (highest first): project > user > plugin > system. */
10
11
  export type Layer = 'system' | 'user' | 'project' | 'plugin';
11
12
  export type ResourceKind = 'command' | 'hook' | 'skill' | 'rule' | 'mcp' | 'permission' | 'subagent' | 'workflow' | 'memory';