@phnx-labs/agents-cli 1.22.41 → 1.22.42

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 (167) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +9 -3
  3. package/dist/bin/agents +0 -0
  4. package/dist/cli/command-registry.d.ts +2 -0
  5. package/dist/cli/command-registry.js +4 -0
  6. package/dist/commands/accounts.d.ts +29 -0
  7. package/dist/commands/accounts.js +220 -29
  8. package/dist/commands/alias.js +1 -1
  9. package/dist/commands/auth.d.ts +9 -0
  10. package/dist/commands/auth.js +104 -0
  11. package/dist/commands/browser-sessions-picker.js +42 -62
  12. package/dist/commands/browser.js +30 -38
  13. package/dist/commands/computer-actions.d.ts +1 -1
  14. package/dist/commands/computer-actions.js +1 -1
  15. package/dist/commands/computer-sessions-picker.js +32 -55
  16. package/dist/commands/computer.d.ts +1 -1
  17. package/dist/commands/computer.js +2 -2
  18. package/dist/commands/doctor.d.ts +1 -1
  19. package/dist/commands/doctor.js +3 -3
  20. package/dist/commands/feed.js +1 -1
  21. package/dist/commands/hooks.js +1 -1
  22. package/dist/commands/import.js +1 -1
  23. package/dist/commands/insights.js +163 -85
  24. package/dist/commands/inspect.js +2 -2
  25. package/dist/commands/mine.js +1 -1
  26. package/dist/commands/org.d.ts +6 -0
  27. package/dist/commands/org.js +174 -0
  28. package/dist/commands/packages.js +1 -1
  29. package/dist/commands/prune.js +1 -1
  30. package/dist/commands/routines.js +1 -1
  31. package/dist/commands/run-account-picker.d.ts +19 -0
  32. package/dist/commands/run-account-picker.js +81 -0
  33. package/dist/commands/sessions-picker-factory.d.ts +40 -0
  34. package/dist/commands/sessions-picker-factory.js +67 -0
  35. package/dist/commands/setup-browser.js +4 -4
  36. package/dist/commands/setup-mine.js +1 -1
  37. package/dist/commands/setup-preferences.js +2 -2
  38. package/dist/commands/setup.js +5 -3
  39. package/dist/commands/sync.js +1 -1
  40. package/dist/commands/versions.js +1 -1
  41. package/dist/commands/view.js +1 -1
  42. package/dist/lib/account-capabilities.d.ts +10 -0
  43. package/dist/lib/account-capabilities.js +34 -0
  44. package/dist/lib/agent-spec/agents.d.ts +584 -0
  45. package/dist/lib/agent-spec/agents.js +3127 -0
  46. package/dist/lib/agent-spec/provider.js +1 -1
  47. package/dist/lib/agents.d.ts +2 -584
  48. package/dist/lib/agents.js +2 -3127
  49. package/dist/lib/browser/drivers/local.d.ts +8 -2
  50. package/dist/lib/browser/drivers/local.js +8 -2
  51. package/dist/lib/browser/drivers/ssh.d.ts +8 -2
  52. package/dist/lib/browser/drivers/ssh.js +10 -4
  53. package/dist/lib/browser/hygiene.d.ts +1 -1
  54. package/dist/lib/browser/hygiene.js +5 -1
  55. package/dist/lib/browser/profiles.d.ts +74 -1
  56. package/dist/lib/browser/profiles.js +114 -5
  57. package/dist/lib/browser/runtime-state.d.ts +11 -5
  58. package/dist/lib/browser/runtime-state.js +13 -9
  59. package/dist/lib/browser/service.d.ts +68 -30
  60. package/dist/lib/browser/service.js +305 -194
  61. package/dist/lib/browser/types.d.ts +95 -3
  62. package/dist/lib/browser/types.js +51 -0
  63. package/dist/lib/catchup.js +1 -1
  64. package/dist/lib/computer/actions.d.ts +1 -1
  65. package/dist/lib/computer/actions.js +1 -1
  66. package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
  67. package/dist/lib/computer/dispatch.d.ts +1 -1
  68. package/dist/lib/computer/download.d.ts +1 -1
  69. package/dist/lib/computer/download.js +3 -3
  70. package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
  71. package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
  72. package/dist/lib/daemon/daemon.js +1 -1
  73. package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +10 -10
  74. package/dist/lib/{runner.js → daemon/runner.js} +40 -114
  75. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  76. package/dist/lib/devices/doctor-findings.js +3 -3
  77. package/dist/lib/devices/fleet-inventory.js +1 -1
  78. package/dist/lib/devices/harness-inventory.js +1 -1
  79. package/dist/lib/devices/registry.js +4 -22
  80. package/dist/lib/doctor-diff.d.ts +1 -1
  81. package/dist/lib/doctor-diff.js +1 -1
  82. package/dist/lib/drift.js +1 -1
  83. package/dist/lib/entitlement.d.ts +31 -0
  84. package/dist/lib/entitlement.js +137 -0
  85. package/dist/lib/exec.js +39 -202
  86. package/dist/lib/feed/feed.js +2 -8
  87. package/dist/lib/fs-atomic.d.ts +7 -0
  88. package/dist/lib/fs-atomic.js +9 -0
  89. package/dist/lib/harness/adapter.d.ts +160 -0
  90. package/dist/lib/harness/adapter.js +47 -0
  91. package/dist/lib/harness/adapters/claude.d.ts +2 -0
  92. package/dist/lib/harness/adapters/claude.js +110 -0
  93. package/dist/lib/harness/adapters/codex.d.ts +2 -0
  94. package/dist/lib/harness/adapters/codex.js +83 -0
  95. package/dist/lib/harness/adapters/copilot.d.ts +2 -0
  96. package/dist/lib/harness/adapters/copilot.js +23 -0
  97. package/dist/lib/harness/adapters/cursor.d.ts +2 -0
  98. package/dist/lib/harness/adapters/cursor.js +38 -0
  99. package/dist/lib/harness/adapters/droid.d.ts +2 -0
  100. package/dist/lib/harness/adapters/droid.js +17 -0
  101. package/dist/lib/harness/adapters/grok.d.ts +2 -0
  102. package/dist/lib/harness/adapters/grok.js +14 -0
  103. package/dist/lib/harness/adapters/kimi.d.ts +2 -0
  104. package/dist/lib/harness/adapters/kimi.js +49 -0
  105. package/dist/lib/harness/adapters/muse.d.ts +2 -0
  106. package/dist/lib/harness/adapters/muse.js +44 -0
  107. package/dist/lib/harness/adapters/opencode.d.ts +2 -0
  108. package/dist/lib/harness/adapters/opencode.js +14 -0
  109. package/dist/lib/harness/exec-config-version.d.ts +8 -0
  110. package/dist/lib/harness/exec-config-version.js +22 -0
  111. package/dist/lib/harness/index.d.ts +1 -0
  112. package/dist/lib/harness/index.js +34 -0
  113. package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
  114. package/dist/lib/{hooks.js → hooks/install.js} +11 -11
  115. package/dist/lib/import.js +1 -1
  116. package/dist/lib/installations/index.d.ts +4 -4
  117. package/dist/lib/installations/index.js +3 -4
  118. package/dist/lib/installations/migrate.js +6 -6
  119. package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
  120. package/dist/lib/{shims.js → installations/shims.js} +12 -114
  121. package/dist/lib/installations/store.d.ts +141 -3
  122. package/dist/lib/installations/store.js +475 -5
  123. package/dist/lib/installations/versions.d.ts +1 -126
  124. package/dist/lib/installations/versions.js +5 -465
  125. package/dist/lib/isolation-boundary-report.d.ts +1 -1
  126. package/dist/lib/isolation-boundary-report.js +1 -1
  127. package/dist/lib/mailbox-gc.js +2 -3
  128. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  129. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  130. package/dist/lib/monitors/dispatch.d.ts +1 -1
  131. package/dist/lib/monitors/dispatch.js +2 -2
  132. package/dist/lib/monitors/state.d.ts +1 -1
  133. package/dist/lib/plugins/plugins.js +1 -1
  134. package/dist/lib/plugins/skills.js +1 -1
  135. package/dist/lib/prix-account.d.ts +158 -0
  136. package/dist/lib/prix-account.js +214 -0
  137. package/dist/lib/project-key.js +2 -2
  138. package/dist/lib/project-launch.js +1 -1
  139. package/dist/lib/refresh.js +2 -2
  140. package/dist/lib/resource-inventory.d.ts +1 -1
  141. package/dist/lib/resource-inventory.js +1 -1
  142. package/dist/lib/rules/rules.js +1 -1
  143. package/dist/lib/scheduling/routines.js +1 -1
  144. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  145. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  146. package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
  147. package/dist/lib/self-heal/checks/path.js +1 -1
  148. package/dist/lib/self-heal/checks/shadowing.js +1 -1
  149. package/dist/lib/self-heal/checks/shims.js +1 -1
  150. package/dist/lib/session/discover.d.ts +39 -0
  151. package/dist/lib/session/discover.js +3 -3
  152. package/dist/lib/session/parse.js +24 -42
  153. package/dist/lib/session/recovery.js +1 -1
  154. package/dist/lib/staleness/prune.d.ts +1 -1
  155. package/dist/lib/staleness/prune.js +1 -1
  156. package/dist/lib/staleness/writers/hooks.js +1 -1
  157. package/dist/lib/startup/command-registry.js +1 -1
  158. package/dist/lib/teams/agents.js +2 -19
  159. package/dist/lib/teams/parsers.js +20 -26
  160. package/dist/lib/teams/registry.js +2 -21
  161. package/dist/lib/triggers/handlers.js +1 -1
  162. package/dist/lib/triggers/webhook.js +1 -1
  163. package/dist/lib/uninstall.js +1 -1
  164. package/dist/lib/version.d.ts +9 -2
  165. package/dist/lib/version.js +11 -9
  166. package/package.json +1 -1
  167. /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
@@ -49,7 +49,24 @@ import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/
49
49
  import { registerMixCommands } from '../lib/analytics/mix-commands.js';
50
50
  import { registerCostCommand } from './cost.js';
51
51
  import { registerOutputCommand } from './output.js';
52
+ import { getTier } from '../lib/entitlement.js';
52
53
  const execFileAsync = promisify(execFile);
54
+ /**
55
+ * Plan-tier gate for the behavioural report (RUSH-2424). Free keeps top-line
56
+ * counts and the harness mix (and `insights mix` / `agents perf`, which never
57
+ * enter this file's gating since they're separate command trees); the
58
+ * friction/correction-signal sections, grouping `--by account`, and
59
+ * `--narrative` are paid. `insights mix`/`cost`/`output` are unaffected — this
60
+ * gate applies only to the default behavioural report.
61
+ */
62
+ const PAID_PLAN_NOTICE = 'Friction and account-split analysis are on the paid plan.';
63
+ function resolveInsightsGate(tier, dim) {
64
+ return {
65
+ tier,
66
+ groupGated: !tier.isPaid && dim === 'account',
67
+ frictionGated: !tier.isPaid,
68
+ };
69
+ }
53
70
  function collectAgent(value, previous) {
54
71
  return [...previous, value];
55
72
  }
@@ -208,7 +225,7 @@ function renderHours(hours, out) {
208
225
  out.push(` ${chalk.cyan(spark)}`);
209
226
  out.push(` ${chalk.gray('0h'.padEnd(6))}${chalk.gray('6h'.padEnd(6))}${chalk.gray('12h'.padEnd(6))}${chalk.gray('18h'.padEnd(5))}${chalk.gray('23h')}`);
210
227
  }
211
- function renderReport(groups, dim, meta, actions, harnesses) {
228
+ function renderReport(groups, dim, meta, actions, harnesses, gate) {
212
229
  const out = [];
213
230
  const scope = meta.since ? `last ${meta.since}` : 'all time';
214
231
  out.push(chalk.bold('Insights') + chalk.gray(` ${scope} · ${meta.analyzed} of ${meta.scanned} sessions`));
@@ -218,28 +235,44 @@ function renderReport(groups, dim, meta, actions, harnesses) {
218
235
  console.log(out.join('\n'));
219
236
  return;
220
237
  }
238
+ let noticePrinted = false;
239
+ const printPlanNotice = () => {
240
+ if (noticePrinted)
241
+ return;
242
+ noticePrinted = true;
243
+ out.push('');
244
+ out.push(chalk.gray(` ${PAID_PLAN_NOTICE}`));
245
+ };
221
246
  // Per-group table — the headline, and the thing no sibling command produces.
222
247
  // Includes silent-stall counts so harness/account laziness is visible without --json.
223
- out.push('');
224
- out.push(chalk.bold(`By ${dim}`));
225
- const labelW = Math.min(Math.max(...groups.map((g) => stringWidth(g.label)), 5), Math.max(16, terminalWidth() - 58));
226
- const sessW = Math.max(...groups.map((g) => String(g.sessions).length), 3);
227
- const stallOf = (g) => Object.entries(g.facets.frictionSignals)
228
- .filter(([k]) => k.startsWith('silent stall:'))
229
- .reduce((n, [, c]) => n + c, 0);
230
- const resumeOf = (g) => g.facets.correctionSignals['resume after silent stall'] ?? 0;
231
- const stallW = Math.max(...groups.map((g) => String(stallOf(g)).length), 5);
232
- out.push(chalk.gray(` ${padToWidth('', labelW)} ${''.padStart(sessW)} ` +
233
- `${''.padStart(9)} ${''.padStart(8)} ${'stalls'.padStart(stallW)} resume`));
234
- for (const g of groups) {
235
- const cost = g.costUsd > 0 ? formatUsd(g.costUsd) : '—';
236
- const dur = g.durationMs > 0 ? formatDuration(g.durationMs) : '—';
237
- const stalls = stallOf(g);
238
- const resumes = resumeOf(g);
239
- out.push(` ${padToWidth(truncateToWidth(g.label, labelW), labelW)} ` +
240
- `${chalk.gray(String(g.sessions).padStart(sessW))} ${chalk.gray('sess')} ` +
241
- `${chalk.green(padToWidth(cost, 9))} ${chalk.gray(padToWidth(dur, 8))} ` +
242
- `${chalk.cyan(String(stalls).padStart(stallW))} ${chalk.cyan(String(resumes))}`);
248
+ // Gated on the free plan when grouped `--by account` (the default) — see resolveInsightsGate.
249
+ if (gate.groupGated) {
250
+ printPlanNotice();
251
+ }
252
+ else {
253
+ out.push('');
254
+ out.push(chalk.bold(`By ${dim}`));
255
+ const labelW = Math.min(Math.max(...groups.map((g) => stringWidth(g.label)), 5), Math.max(16, terminalWidth() - 58));
256
+ const sessW = Math.max(...groups.map((g) => String(g.sessions).length), 3);
257
+ // Friction-derived — never shown on the free plan, even when grouping by
258
+ // something other than account (gate.groupGated only covers `--by account`).
259
+ const stallOf = (g) => Object.entries(g.facets.frictionSignals)
260
+ .filter(([k]) => k.startsWith('silent stall:'))
261
+ .reduce((n, [, c]) => n + c, 0);
262
+ const resumeOf = (g) => g.facets.correctionSignals['resume after silent stall'] ?? 0;
263
+ const stallW = gate.frictionGated ? 6 : Math.max(...groups.map((g) => String(stallOf(g)).length), 5);
264
+ out.push(chalk.gray(` ${padToWidth('', labelW)} ${''.padStart(sessW)} ` +
265
+ `${''.padStart(9)} ${''.padStart(8)} ${gate.frictionGated ? ''.padStart(stallW) : 'stalls'.padStart(stallW)} ${gate.frictionGated ? '' : 'resume'}`));
266
+ for (const g of groups) {
267
+ const cost = g.costUsd > 0 ? formatUsd(g.costUsd) : '—';
268
+ const dur = g.durationMs > 0 ? formatDuration(g.durationMs) : '—';
269
+ const stalls = gate.frictionGated ? '—' : String(stallOf(g));
270
+ const resumes = gate.frictionGated ? '' : String(resumeOf(g));
271
+ out.push(` ${padToWidth(truncateToWidth(g.label, labelW), labelW)} ` +
272
+ `${chalk.gray(String(g.sessions).padStart(sessW))} ${chalk.gray('sess')} ` +
273
+ `${chalk.green(padToWidth(cost, 9))} ${chalk.gray(padToWidth(dur, 8))} ` +
274
+ `${chalk.cyan(stalls.padStart(stallW))} ${chalk.cyan(resumes)}`);
275
+ }
243
276
  }
244
277
  // Everything below is the whole scope folded together; per-group detail is in --json.
245
278
  const all = newFacetAccumulator();
@@ -248,52 +281,64 @@ function renderReport(groups, dim, meta, actions, harnesses) {
248
281
  renderCounts('Top tools', topEntries(all.toolCounts, 8), out);
249
282
  renderCounts('Languages', topEntries(all.languages, 6), out);
250
283
  renderCounts('Models', topEntries(all.models, 6), out);
251
- renderCounts('Silent stalls by model', topEntries(all.silentStallsByModel ?? {}, 8), out);
252
- // Friction the section that earns the command.
253
- const gaps = all.responseGaps;
254
- const silentStalls = Object.entries(all.frictionSignals)
255
- .filter(([k]) => k.startsWith('silent stall:'))
256
- .reduce((n, [, c]) => n + c, 0);
257
- const resumeNudges = all.correctionSignals['resume after silent stall'] ?? 0;
258
- out.push('');
259
- out.push(chalk.bold('Friction'));
260
- out.push(` ${padToWidth('interruptions', 18)} ${chalk.cyan(String(all.interruptions))}` +
261
- chalk.gray(' turns you cut short'));
262
- out.push(` ${padToWidth('tool errors', 18)} ${chalk.cyan(String(all.errorCount))}`);
263
- if (gaps.length > 0) {
264
- // Same timestamps as silent stalls; this line is the distribution. Silent
265
- // stalls (below) are the agent-attributed long gaps after the model stopped.
266
- out.push(` ${padToWidth('gap until next msg', 18)} ` +
267
- chalk.cyan(`p50 ${Math.round(percentile(gaps, 50))}s`) + chalk.gray(` · p90 ${Math.round(percentile(gaps, 90))}s`) +
268
- chalk.gray(' after assistant last spoke'));
269
- }
270
- if (silentStalls > 0) {
271
- out.push(` ${padToWidth('silent stalls', 18)} ${chalk.cyan(String(silentStalls))}` +
272
- chalk.gray(' agent idle ≥5m until you resumed (also in By ' + dim + ' table)'));
284
+ // Friction the section that earns the command. Paid plan only (RUSH-2424);
285
+ // top-line counts and harness mix above/below stay free.
286
+ if (gate.frictionGated) {
287
+ printPlanNotice();
273
288
  }
274
- if (resumeNudges > 0) {
275
- out.push(` ${padToWidth('resume nudges', 18)} ${chalk.cyan(String(resumeNudges))}` +
276
- chalk.gray(' "continue"/"keep going" after a silent stall'));
277
- }
278
- const errs = topEntries(all.errorCategories, 6);
279
- if (errs.length > 0) {
280
- for (const e of errs)
281
- out.push(` ${chalk.gray('·')} ${padToWidth(e.name, 16)} ${chalk.gray(String(e.count))}`);
289
+ else {
290
+ renderCounts('Silent stalls by model', topEntries(all.silentStallsByModel ?? {}, 8), out);
291
+ const gaps = all.responseGaps;
292
+ const silentStalls = Object.entries(all.frictionSignals)
293
+ .filter(([k]) => k.startsWith('silent stall:'))
294
+ .reduce((n, [, c]) => n + c, 0);
295
+ const resumeNudges = all.correctionSignals['resume after silent stall'] ?? 0;
296
+ out.push('');
297
+ out.push(chalk.bold('Friction'));
298
+ out.push(` ${padToWidth('interruptions', 18)} ${chalk.cyan(String(all.interruptions))}` +
299
+ chalk.gray(' turns you cut short'));
300
+ out.push(` ${padToWidth('tool errors', 18)} ${chalk.cyan(String(all.errorCount))}`);
301
+ if (gaps.length > 0) {
302
+ // Same timestamps as silent stalls; this line is the distribution. Silent
303
+ // stalls (below) are the agent-attributed long gaps after the model stopped.
304
+ out.push(` ${padToWidth('gap until next msg', 18)} ` +
305
+ chalk.cyan(`p50 ${Math.round(percentile(gaps, 50))}s`) + chalk.gray(` · p90 ${Math.round(percentile(gaps, 90))}s`) +
306
+ chalk.gray(' after assistant last spoke'));
307
+ }
308
+ if (silentStalls > 0) {
309
+ out.push(` ${padToWidth('silent stalls', 18)} ${chalk.cyan(String(silentStalls))}` +
310
+ chalk.gray(' agent idle ≥5m until you resumed (also in By ' + dim + ' table)'));
311
+ }
312
+ if (resumeNudges > 0) {
313
+ out.push(` ${padToWidth('resume nudges', 18)} ${chalk.cyan(String(resumeNudges))}` +
314
+ chalk.gray(' "continue"/"keep going" after a silent stall'));
315
+ }
316
+ const errs = topEntries(all.errorCategories, 6);
317
+ if (errs.length > 0) {
318
+ for (const e of errs)
319
+ out.push(` ${chalk.gray('·')} ${padToWidth(e.name, 16)} ${chalk.gray(String(e.count))}`);
320
+ }
321
+ renderCounts('Friction / thrash', topEntries(all.frictionSignals, 10), out);
322
+ renderCounts('Dissatisfaction / corrections', topEntries(all.correctionSignals, 10), out);
282
323
  }
283
- renderCounts('Friction / thrash', topEntries(all.frictionSignals, 10), out);
284
- renderCounts('Dissatisfaction / corrections', topEntries(all.correctionSignals, 10), out);
285
324
  renderCounts('Automatable repeats', topEntries(all.automationSignals, 10), out);
286
325
  renderCounts('Harness split', harnesses, out);
287
- out.push('');
288
- out.push(chalk.bold('Actions'));
289
- if (actions.length === 0) {
290
- out.push(chalk.gray(' No repeated action pattern met the evidence threshold in this window.'));
291
- }
292
- else {
293
- out.push(chalk.gray(' pri category evidence sample sessions action'));
294
- for (const action of actions.slice(0, 12)) {
295
- out.push(` ${padToWidth(action.priority, 7)} ${padToWidth(action.category, 11)} ` +
296
- `${String(action.evidenceCount).padStart(8)} ${padToWidth(action.sampleSessionIds.join(', '), 25)} ${action.action}`);
326
+ // Actions are built from frictionSignals/correctionSignals/automationSignals
327
+ // together (buildInsightActions) — evidence counts, sample session ids, and
328
+ // the action text itself describe the same paid friction/correction
329
+ // categories gated above, so the whole section is paid too (RUSH-2424).
330
+ if (!gate.frictionGated) {
331
+ out.push('');
332
+ out.push(chalk.bold('Actions'));
333
+ if (actions.length === 0) {
334
+ out.push(chalk.gray(' No repeated action pattern met the evidence threshold in this window.'));
335
+ }
336
+ else {
337
+ out.push(chalk.gray(' pri category evidence sample sessions action'));
338
+ for (const action of actions.slice(0, 12)) {
339
+ out.push(` ${padToWidth(action.priority, 7)} ${padToWidth(action.category, 11)} ` +
340
+ `${String(action.evidenceCount).padStart(8)} ${padToWidth(action.sampleSessionIds.join(', '), 25)} ${action.action}`);
341
+ }
297
342
  }
298
343
  }
299
344
  // Output
@@ -349,7 +394,9 @@ function renderReport(groups, dim, meta, actions, harnesses) {
349
394
  out.push('');
350
395
  out.push(chalk.yellow(` ${meta.unreadable} transcripts could not be read; their behaviour is missing from these totals.`));
351
396
  }
352
- if (all.gapsOverCeiling > 0) {
397
+ // Friction-derived (gapsOverCeiling is a PAID_FACET_KEYS entry) and names
398
+ // "silent stall" outright — must not render on the free plan.
399
+ if (!gate.frictionGated && all.gapsOverCeiling > 0) {
353
400
  out.push(chalk.gray(` ${all.gapsOverCeiling} gaps over an hour excluded from p50/p90 (still counted as silent stall: 1h+ when the assistant last spoke).`));
354
401
  }
355
402
  out.push('');
@@ -404,8 +451,23 @@ async function renderNarrative(payload) {
404
451
  process.exitCode = 1;
405
452
  }
406
453
  }
454
+ /** Facet keys that belong to the paid friction/correction sections — stripped from `--json` on free (RUSH-2424). */
455
+ const PAID_FACET_KEYS = new Set([
456
+ 'frictionSignals', 'correctionSignals', 'silentStallsByModel',
457
+ 'interruptions', 'errorCount', 'errorCategories', 'responseGaps', 'gapsOverCeiling',
458
+ ]);
459
+ function freeFacetSubset(facets) {
460
+ const out = {};
461
+ for (const [key, value] of Object.entries(facets)) {
462
+ if (!PAID_FACET_KEYS.has(key))
463
+ out[key] = value;
464
+ }
465
+ return out;
466
+ }
407
467
  async function insightsAction(options) {
408
468
  const dim = resolveGroup(options.by);
469
+ const tier = await getTier();
470
+ const gate = resolveInsightsGate(tier, dim);
409
471
  const minMessages = Number.parseInt(options.minMessages ?? '2', 10);
410
472
  if (!Number.isFinite(minMessages) || minMessages < 0) {
411
473
  console.error(chalk.red('error: --min-messages must be a non-negative integer'));
@@ -471,27 +533,37 @@ async function insightsAction(options) {
471
533
  unreadable,
472
534
  minMessages,
473
535
  by: dim,
536
+ plan: { tierName: tier.tierName, isPaid: tier.isPaid },
537
+ ...(gate.groupGated || gate.frictionGated ? { notice: PAID_PLAN_NOTICE } : {}),
474
538
  overlap,
475
- actions,
539
+ // Built from frictionSignals/correctionSignals/automationSignals together
540
+ // (buildInsightActions) — same paid friction/correction data as above.
541
+ actions: gate.frictionGated ? null : actions,
476
542
  harnesses,
477
- groups: groups.map((g) => ({
543
+ groups: gate.groupGated ? null : groups.map((g) => ({
478
544
  key: g.key,
479
545
  label: g.label,
480
546
  sessions: g.sessions,
481
547
  costUsd: g.costUsd,
482
548
  durationMs: g.durationMs,
483
549
  outputTokens: g.outputTokens,
484
- ...g.facets,
485
- responseGapP50: Math.round(percentile(g.facets.responseGaps, 50)),
486
- responseGapP90: Math.round(percentile(g.facets.responseGaps, 90)),
487
- responseGapBuckets: bucketGaps(g.facets.responseGaps),
488
- // The raw sample is large and uninteresting once bucketed.
489
- responseGaps: undefined,
550
+ ...(gate.frictionGated ? freeFacetSubset(g.facets) : {
551
+ ...g.facets,
552
+ responseGapP50: Math.round(percentile(g.facets.responseGaps, 50)),
553
+ responseGapP90: Math.round(percentile(g.facets.responseGaps, 90)),
554
+ responseGapBuckets: bucketGaps(g.facets.responseGaps),
555
+ // The raw sample is large and uninteresting once bucketed.
556
+ responseGaps: undefined,
557
+ }),
490
558
  })),
491
559
  };
492
560
  console.log(JSON.stringify(payload, null, 2));
493
- if (options.narrative)
494
- await renderNarrative(payload);
561
+ if (options.narrative) {
562
+ if (!tier.isPaid)
563
+ console.error(chalk.gray(` ${PAID_PLAN_NOTICE}`));
564
+ else
565
+ await renderNarrative(payload);
566
+ }
495
567
  return;
496
568
  }
497
569
  renderReport(groups, dim, {
@@ -502,18 +574,24 @@ async function insightsAction(options) {
502
574
  unreadable,
503
575
  minMessages,
504
576
  overlap,
505
- }, actions, harnesses);
577
+ }, actions, harnesses, gate);
506
578
  if (options.narrative) {
507
- await renderNarrative(groups.map((g) => ({
508
- account: g.label, sessions: g.sessions, costUsd: g.costUsd,
509
- topTools: topEntries(g.facets.toolCounts, 8),
510
- languages: topEntries(g.facets.languages, 6),
511
- errorCategories: topEntries(g.facets.errorCategories, 6),
512
- interruptions: g.facets.interruptions,
513
- linesTouchedAfter: g.facets.linesTouchedAfter, linesTouchedBefore: g.facets.linesTouchedBefore,
514
- gitCommits: g.facets.gitCommits,
515
- replyP50s: Math.round(percentile(g.facets.responseGaps, 50)),
516
- })));
579
+ if (!tier.isPaid) {
580
+ console.log('');
581
+ console.log(chalk.gray(` ${PAID_PLAN_NOTICE}`));
582
+ }
583
+ else {
584
+ await renderNarrative(groups.map((g) => ({
585
+ account: g.label, sessions: g.sessions, costUsd: g.costUsd,
586
+ topTools: topEntries(g.facets.toolCounts, 8),
587
+ languages: topEntries(g.facets.languages, 6),
588
+ errorCategories: topEntries(g.facets.errorCategories, 6),
589
+ interruptions: g.facets.interruptions,
590
+ linesTouchedAfter: g.facets.linesTouchedAfter, linesTouchedBefore: g.facets.linesTouchedBefore,
591
+ gitCommits: g.facets.gitCommits,
592
+ replyP50s: Math.round(percentile(g.facets.responseGaps, 50)),
593
+ })));
594
+ }
517
595
  }
518
596
  }
519
597
  function configureInsightsCommand(cmd) {
@@ -26,9 +26,9 @@ import { getAgentModesCatalog } from '../lib/agent-modes.js';
26
26
  import { resolveSingleAgentTarget, AgentSpecError } from '../lib/agent-spec/index.js';
27
27
  import { readMeta, getUserAgentsDir, getSystemAgentsDir, getProjectAgentsDir, getEnabledExtraRepos, } from '../lib/state.js';
28
28
  import { getVersionHomePath, isVersionIsolated, getIsolatedDefault, } from '../lib/installations/versions.js';
29
- import { getShimsDir, getVersionedAliasPath } from '../lib/shims.js';
29
+ import { getShimsDir, getVersionedAliasPath } from '../lib/installations/shims.js';
30
30
  import { getAgentResources, isDirectoryDoc, listResources, } from '../lib/resources.js';
31
- import { listHookEntriesFromDir } from '../lib/hooks.js';
31
+ import { listHookEntriesFromDir } from '../lib/hooks/install.js';
32
32
  import { getResourceInventory } from '../lib/resource-inventory.js';
33
33
  import { listMcpServerConfigs, discoverMcpConfigsFromRepo } from '../lib/mcp.js';
34
34
  import { discoverPlugins, discoverPluginsInDir, pluginResourceGroups, inspectPluginCapabilities, pluginCapabilityLabels } from '../lib/plugins/plugins.js';
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { validateBrandName, listBrands, getBrandConfig, upsertBrand, removeBrand, brandPresetName, } from '../lib/brand.js';
3
- import { createBrandShim, removeBrandShim, isShimsInPath } from '../lib/shims.js';
3
+ import { createBrandShim, removeBrandShim, isShimsInPath } from '../lib/installations/shims.js';
4
4
  import { updateMeta } from '../lib/state.js';
5
5
  import { KNOWN_TOP_LEVEL_COMMANDS } from '../lib/startup/command-registry.js';
6
6
  /** Built-in top-level command names, for validating `--disable <cmd>`. */
@@ -0,0 +1,6 @@
1
+ import type { Command } from 'commander';
2
+ /** `--role` defaults to member when omitted; anything present must be a real role — never silently coerced. */
3
+ export declare function resolveInviteRole(raw: string | undefined): 'admin' | 'member';
4
+ /** Validate a role argument before any network call — same rule the backend enforces server-side. */
5
+ export declare function parseRole(raw: string): 'admin' | 'member';
6
+ export declare function registerOrgCommand(program: Command): void;
@@ -0,0 +1,174 @@
1
+ import chalk from 'chalk';
2
+ import { setHelpSections } from '../lib/help.js';
3
+ import { createSpace, createSpaceInvite, fetchWhoAmI, listSpaceMembers, listSpaces, removeSpaceMember, resolveMemberFromList, resolvePrixToken, resolveSpaceFromList, slugify, updateSpaceMemberRole, } from '../lib/prix-account.js';
4
+ /** Every `agents org` subcommand needs a token up front — one place to say so. */
5
+ function requireToken() {
6
+ if (!resolvePrixToken())
7
+ throw new Error("Not signed in. Run 'agents auth login' first.");
8
+ }
9
+ async function resolveSpace(explicit) {
10
+ const spaces = await listSpaces();
11
+ return resolveSpaceFromList(spaces, explicit);
12
+ }
13
+ function printSpace(space) {
14
+ console.log(`${chalk.bold(space.name)} ${chalk.gray(space.slug)} ${chalk.gray(space.id)}`);
15
+ console.log(` role: ${space.user_role}${space.organization_id ? ` org: ${space.organization_id}` : ''}`);
16
+ }
17
+ function printMembers(members) {
18
+ if (!members.length) {
19
+ console.log(chalk.gray(' No members.'));
20
+ return;
21
+ }
22
+ for (const m of members)
23
+ console.log(` ${chalk.cyan(m.email)} ${m.role} ${chalk.gray(m.user_id)}`);
24
+ }
25
+ async function runCreate(name, o) {
26
+ requireToken();
27
+ const space = await createSpace({ name, slug: o.slug ?? slugify(name), description: o.description });
28
+ if (o.json) {
29
+ console.log(JSON.stringify(space, null, 2));
30
+ return;
31
+ }
32
+ console.log(chalk.green(`Created space '${space.name}' (${space.slug}).`));
33
+ printSpace(space);
34
+ }
35
+ async function runList(o) {
36
+ requireToken();
37
+ const spaces = await listSpaces();
38
+ if (o.json) {
39
+ console.log(JSON.stringify(spaces, null, 2));
40
+ return;
41
+ }
42
+ if (!spaces.length) {
43
+ console.log(chalk.gray("No spaces. Create one with 'agents org create <name>'."));
44
+ return;
45
+ }
46
+ for (const space of spaces)
47
+ printSpace(space);
48
+ }
49
+ async function runView(spaceArg, o) {
50
+ requireToken();
51
+ const space = await resolveSpace(spaceArg);
52
+ if (o.json) {
53
+ console.log(JSON.stringify(space, null, 2));
54
+ return;
55
+ }
56
+ printSpace(space);
57
+ }
58
+ /** `--role` defaults to member when omitted; anything present must be a real role — never silently coerced. */
59
+ export function resolveInviteRole(raw) {
60
+ return raw === undefined ? 'member' : parseRole(raw);
61
+ }
62
+ async function runInvite(email, o) {
63
+ requireToken();
64
+ const role = resolveInviteRole(o.role);
65
+ const space = await resolveSpace(o.space);
66
+ const result = await createSpaceInvite(space.id, email, role);
67
+ if (o.json) {
68
+ console.log(JSON.stringify(result, null, 2));
69
+ return;
70
+ }
71
+ if (result.member_added)
72
+ console.log(chalk.green(`Added ${email} to '${space.name}' as ${role}.`));
73
+ else
74
+ console.log(chalk.green(`Invited ${email} to '${space.name}' as ${role}. They'll get an email.`));
75
+ }
76
+ async function runMembers(spaceArg, o) {
77
+ requireToken();
78
+ const space = await resolveSpace(spaceArg);
79
+ const members = await listSpaceMembers(space.id);
80
+ if (o.json) {
81
+ console.log(JSON.stringify(members, null, 2));
82
+ return;
83
+ }
84
+ console.log(chalk.bold(`${space.name} (${members.length} member${members.length === 1 ? '' : 's'})`));
85
+ printMembers(members);
86
+ }
87
+ /** Validate a role argument before any network call — same rule the backend enforces server-side. */
88
+ export function parseRole(raw) {
89
+ if (raw === 'admin' || raw === 'member')
90
+ return raw;
91
+ throw new Error(`role must be 'admin' or 'member', got '${raw}'.`);
92
+ }
93
+ async function runRole(email, roleRaw, o) {
94
+ requireToken();
95
+ const role = parseRole(roleRaw);
96
+ const space = await resolveSpace(o.space);
97
+ const members = await listSpaceMembers(space.id);
98
+ const member = resolveMemberFromList(members, email);
99
+ const result = await updateSpaceMemberRole(space.id, member.user_id, role);
100
+ if (o.json) {
101
+ console.log(JSON.stringify(result, null, 2));
102
+ return;
103
+ }
104
+ console.log(chalk.green(`${email} is now ${role} in '${space.name}'.`));
105
+ }
106
+ async function runRemove(email, o) {
107
+ requireToken();
108
+ const space = await resolveSpace(o.space);
109
+ const members = await listSpaceMembers(space.id);
110
+ const member = resolveMemberFromList(members, email);
111
+ await removeSpaceMember(space.id, member.user_id);
112
+ if (o.json) {
113
+ console.log(JSON.stringify({ removed: email, space: space.slug }, null, 2));
114
+ return;
115
+ }
116
+ console.log(chalk.green(`Removed ${email} from '${space.name}'.`));
117
+ }
118
+ async function runLeave(spaceArg, o) {
119
+ requireToken();
120
+ const resolved = resolvePrixToken();
121
+ if (!resolved)
122
+ throw new Error("Not signed in. Run 'agents auth login' first.");
123
+ const who = await fetchWhoAmI(resolved.token);
124
+ const space = await resolveSpace(spaceArg);
125
+ if (space.owner_user_id === who.userId) {
126
+ throw new Error(`You own '${space.name}'. Transfer ownership or delete the space instead of leaving it.`);
127
+ }
128
+ await removeSpaceMember(space.id, who.userId);
129
+ if (o.json) {
130
+ console.log(JSON.stringify({ left: space.slug }, null, 2));
131
+ return;
132
+ }
133
+ console.log(chalk.green(`Left '${space.name}'.`));
134
+ }
135
+ export function registerOrgCommand(program) {
136
+ const org = program.command('org').description('Create and manage a team (a Rush "space") you can share with `agents auth login` collaborators');
137
+ org.command('create <name>').description('Create a space (free tier: 1 owned space)')
138
+ .option('--slug <slug>', 'Override the derived slug')
139
+ .option('--description <text>', 'Optional description')
140
+ .option('--json', 'Machine-readable output')
141
+ .action((name, o, command) => runCreate(name, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
142
+ org.command('list').description('List spaces you own or belong to').option('--json', 'Machine-readable output')
143
+ .action((o, command) => runList({ json: !!(o.json || command.optsWithGlobals().json) }));
144
+ org.command('view [space]').description('Show one space (defaults to your only space)').option('--json', 'Machine-readable output')
145
+ .action((space, o, command) => runView(space, { json: !!(o.json || command.optsWithGlobals().json) }));
146
+ org.command('invite <email>').description('Invite (or directly add) a member')
147
+ .option('--role <role>', 'admin | member', 'member')
148
+ .option('--space <id-or-slug>', 'Space to invite into (defaults to your only space)')
149
+ .option('--json', 'Machine-readable output')
150
+ .action((email, o, command) => runInvite(email, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
151
+ org.command('members [space]').description('List a space\'s members').option('--json', 'Machine-readable output')
152
+ .action((space, o, command) => runMembers(space, { json: !!(o.json || command.optsWithGlobals().json) }));
153
+ org.command('role <email> <role>').description('Change a member\'s role (owner-only for admin)')
154
+ .option('--space <id-or-slug>', 'Space to change (defaults to your only space)')
155
+ .option('--json', 'Machine-readable output')
156
+ .action((email, role, o, command) => runRole(email, role, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
157
+ org.command('remove <email>').description('Remove a member from a space')
158
+ .option('--space <id-or-slug>', 'Space to remove from (defaults to your only space)')
159
+ .option('--json', 'Machine-readable output')
160
+ .action((email, o, command) => runRemove(email, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
161
+ org.command('leave [space]').description('Leave a space you do not own').option('--json', 'Machine-readable output')
162
+ .action((space, o, command) => runLeave(space, { json: !!(o.json || command.optsWithGlobals().json) }));
163
+ setHelpSections(org, {
164
+ examples: `agents org create acme-team
165
+ agents org list
166
+ agents org view acme-team
167
+ agents org invite dev@example.com --role admin
168
+ agents org members
169
+ agents org role dev@example.com admin
170
+ agents org remove dev@example.com
171
+ agents org leave acme-team`,
172
+ notes: 'Maps to the Rush backend\'s /api/v1/spaces (the free-tier team primitive), not /api/v1/orgs. Free tier: 1 owned space, 3 members per space. Every command needs `agents auth login` (or a `rush login` session) first. `--space` is only needed once you belong to more than one space.',
173
+ });
174
+ }
@@ -15,7 +15,7 @@ import { getRegistries, setRegistry, removeRegistry, search as searchRegistries,
15
15
  import { cloneRepo } from '../lib/git.js';
16
16
  import { discoverCommands, resolveCommandSource, installCommand, installCommandCentrally, } from '../lib/commands.js';
17
17
  import { discoverSkillsFromRepo, installSkill, installSkillCentrally, } from '../lib/plugins/skills.js';
18
- import { discoverHooksFromRepo, installHooks, installHooksCentrally, } from '../lib/hooks.js';
18
+ import { discoverHooksFromRepo, installHooks, installHooksCentrally, } from '../lib/hooks/install.js';
19
19
  import { discoverWorkflowsFromRepo, installWorkflowCentrally, } from '../lib/workflows.js';
20
20
  import { discoverSubagentsFromRepo, installSubagentCentrally, } from '../lib/subagents.js';
21
21
  import { discoverPermissionsFromRepo, installPermissionSet, } from '../lib/permissions.js';
@@ -27,7 +27,7 @@ import { formatBytes } from '../lib/format.js';
27
27
  import { confirm } from '@inquirer/prompts';
28
28
  import { diffVersionCommands, iterCommandsCapableVersions, removeCommandFromVersion, } from '../lib/commands.js';
29
29
  import { diffVersionSkills, iterSkillsCapableVersions, removeSkillFromVersion, } from '../lib/plugins/skills.js';
30
- import { listUnmanagedHooksInVersionHome, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks.js';
30
+ import { listUnmanagedHooksInVersionHome, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks/install.js';
31
31
  import { diffVersionPlugins, iterPluginsCapableVersions, removePluginSkillFromVersion, } from '../lib/plugins/plugins.js';
32
32
  import { diffVersionSubagents, iterSubagentsCapableVersions, removeSubagentFromVersion, } from '../lib/subagents.js';
33
33
  import { getGlobalDefault } from '../lib/installations/versions.js';
@@ -24,7 +24,7 @@ import { fireWebhookJobs, matchJobsToWebhook } from '../lib/triggers/webhook.js'
24
24
  import { getRoutinesDir } from '../lib/state.js';
25
25
  import { IS_WINDOWS } from '../lib/platform/index.js';
26
26
  import { safeJoin } from '../lib/paths.js';
27
- import { executeJob, monitorRunningJobs } from '../lib/runner.js';
27
+ import { executeJob, monitorRunningJobs } from '../lib/daemon/runner.js';
28
28
  import { JobScheduler } from '../lib/scheduler.js';
29
29
  import { detectOverdueJobs } from '../lib/overdue.js';
30
30
  import { runCatchup } from '../lib/catchup.js';
@@ -12,10 +12,29 @@ export interface RunAccountChoice {
12
12
  */
13
13
  signInRequired: boolean;
14
14
  }
15
+ /** One named account row for `agents accounts switch` (reuses this picker's layout). */
16
+ export interface SwitchAccountRow {
17
+ accountName: string;
18
+ kind: 'provider' | 'native';
19
+ detail: string;
20
+ current: boolean;
21
+ candidate: RotateCandidate | null;
22
+ }
15
23
  /** Human-readable remaining capacity for every window the provider exposes. */
16
24
  export declare function formatAccountLimits(candidate: RotateCandidate): string;
17
25
  /** Build aligned picker rows with usable accounts first and unsafe rows disabled. */
18
26
  export declare function buildRunAccountChoices(candidates: RotateCandidate[], globalDefault: string | null): RunAccountChoice[];
27
+ /**
28
+ * Aligned picker rows for `accounts switch`. Same columns as the run picker
29
+ * (identity, status, limits) but the value is the named account to set-default.
30
+ * Rows stay selectable: setting a default is not a launch.
31
+ */
32
+ export declare function buildSwitchAccountChoices(rows: SwitchAccountRow[]): RunAccountChoice[];
33
+ /**
34
+ * Prompt for the named account that becomes this harness's default.
35
+ * A cancelled picker writes nothing.
36
+ */
37
+ export declare function pickSwitchAccount(agent: AgentId, rows: SwitchAccountRow[]): Promise<string | null>;
19
38
  /**
20
39
  * Whether a zero-healthy run may recover by launching for a login, or must keep
21
40
  * failing loud. Three inputs, all of which have to hold: