@phnx-labs/agents-cli 1.22.40 → 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 (201) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +14 -3
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.js +0 -1
  5. package/dist/cli/command-registry.d.ts +2 -0
  6. package/dist/cli/command-registry.js +5 -2
  7. package/dist/commands/accounts.d.ts +29 -0
  8. package/dist/commands/accounts.js +220 -29
  9. package/dist/commands/alias.js +1 -1
  10. package/dist/commands/auth.d.ts +9 -0
  11. package/dist/commands/auth.js +104 -0
  12. package/dist/commands/browser-sessions-picker.js +42 -62
  13. package/dist/commands/browser.d.ts +15 -0
  14. package/dist/commands/browser.js +145 -78
  15. package/dist/commands/computer-actions.d.ts +1 -1
  16. package/dist/commands/computer-actions.js +1 -1
  17. package/dist/commands/computer-sessions-picker.js +32 -55
  18. package/dist/commands/computer.d.ts +1 -1
  19. package/dist/commands/computer.js +2 -2
  20. package/dist/commands/doctor.d.ts +1 -1
  21. package/dist/commands/doctor.js +3 -3
  22. package/dist/commands/feed.js +4 -12
  23. package/dist/commands/hooks.js +1 -1
  24. package/dist/commands/import.js +1 -1
  25. package/dist/commands/insights.js +163 -85
  26. package/dist/commands/inspect.js +2 -2
  27. package/dist/commands/mine.js +1 -1
  28. package/dist/commands/org.d.ts +6 -0
  29. package/dist/commands/org.js +174 -0
  30. package/dist/commands/packages.js +1 -1
  31. package/dist/commands/prune.js +1 -1
  32. package/dist/commands/routines.js +1 -1
  33. package/dist/commands/run-account-picker.d.ts +19 -0
  34. package/dist/commands/run-account-picker.js +81 -0
  35. package/dist/commands/secrets.js +87 -97
  36. package/dist/commands/sessions-picker-factory.d.ts +40 -0
  37. package/dist/commands/sessions-picker-factory.js +67 -0
  38. package/dist/commands/sessions-picker.d.ts +1 -0
  39. package/dist/commands/sessions-picker.js +27 -5
  40. package/dist/commands/sessions-share.d.ts +25 -0
  41. package/dist/commands/sessions-share.js +166 -0
  42. package/dist/commands/sessions.js +2 -0
  43. package/dist/commands/setup-browser.js +5 -5
  44. package/dist/commands/setup-mine.js +1 -1
  45. package/dist/commands/setup-preferences.js +4 -4
  46. package/dist/commands/setup.js +5 -3
  47. package/dist/commands/sync.js +1 -1
  48. package/dist/commands/versions.js +1 -1
  49. package/dist/commands/view.js +1 -1
  50. package/dist/commands/webhook.js +14 -9
  51. package/dist/lib/account-capabilities.d.ts +10 -0
  52. package/dist/lib/account-capabilities.js +34 -0
  53. package/dist/lib/agent-spec/agents.d.ts +584 -0
  54. package/dist/lib/agent-spec/agents.js +3127 -0
  55. package/dist/lib/agent-spec/provider.js +1 -1
  56. package/dist/lib/agents.d.ts +2 -584
  57. package/dist/lib/agents.js +2 -3127
  58. package/dist/lib/browser/cdp.js +4 -0
  59. package/dist/lib/browser/chrome.js +12 -1
  60. package/dist/lib/browser/drivers/local.d.ts +8 -2
  61. package/dist/lib/browser/drivers/local.js +8 -2
  62. package/dist/lib/browser/drivers/ssh.d.ts +8 -2
  63. package/dist/lib/browser/drivers/ssh.js +11 -4
  64. package/dist/lib/browser/hygiene.d.ts +1 -1
  65. package/dist/lib/browser/hygiene.js +5 -1
  66. package/dist/lib/browser/profiles.d.ts +77 -4
  67. package/dist/lib/browser/profiles.js +121 -12
  68. package/dist/lib/browser/runtime-state.d.ts +11 -5
  69. package/dist/lib/browser/runtime-state.js +13 -9
  70. package/dist/lib/browser/service.d.ts +91 -30
  71. package/dist/lib/browser/service.js +342 -207
  72. package/dist/lib/browser/types.d.ts +96 -4
  73. package/dist/lib/browser/types.js +51 -0
  74. package/dist/lib/catchup.js +1 -1
  75. package/dist/lib/computer/actions.d.ts +1 -1
  76. package/dist/lib/computer/actions.js +1 -1
  77. package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
  78. package/dist/lib/computer/dispatch.d.ts +1 -1
  79. package/dist/lib/computer/download.d.ts +1 -1
  80. package/dist/lib/computer/download.js +3 -3
  81. package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
  82. package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
  83. package/dist/lib/daemon/daemon.js +1 -1
  84. package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +10 -10
  85. package/dist/lib/{runner.js → daemon/runner.js} +59 -118
  86. package/dist/lib/daemon-webhooks.d.ts +3 -1
  87. package/dist/lib/daemon-webhooks.js +12 -7
  88. package/dist/lib/device-config.js +1 -1
  89. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  90. package/dist/lib/devices/doctor-findings.js +3 -3
  91. package/dist/lib/devices/fleet-inventory.js +1 -1
  92. package/dist/lib/devices/harness-inventory.js +1 -1
  93. package/dist/lib/devices/registry.js +4 -22
  94. package/dist/lib/doctor-diff.d.ts +1 -1
  95. package/dist/lib/doctor-diff.js +1 -1
  96. package/dist/lib/drift.js +1 -1
  97. package/dist/lib/entitlement.d.ts +31 -0
  98. package/dist/lib/entitlement.js +137 -0
  99. package/dist/lib/exec.js +39 -202
  100. package/dist/lib/feed/feed.js +2 -8
  101. package/dist/lib/fs-atomic.d.ts +7 -0
  102. package/dist/lib/fs-atomic.js +9 -0
  103. package/dist/lib/harness/adapter.d.ts +160 -0
  104. package/dist/lib/harness/adapter.js +47 -0
  105. package/dist/lib/harness/adapters/claude.d.ts +2 -0
  106. package/dist/lib/harness/adapters/claude.js +110 -0
  107. package/dist/lib/harness/adapters/codex.d.ts +2 -0
  108. package/dist/lib/harness/adapters/codex.js +83 -0
  109. package/dist/lib/harness/adapters/copilot.d.ts +2 -0
  110. package/dist/lib/harness/adapters/copilot.js +23 -0
  111. package/dist/lib/harness/adapters/cursor.d.ts +2 -0
  112. package/dist/lib/harness/adapters/cursor.js +38 -0
  113. package/dist/lib/harness/adapters/droid.d.ts +2 -0
  114. package/dist/lib/harness/adapters/droid.js +17 -0
  115. package/dist/lib/harness/adapters/grok.d.ts +2 -0
  116. package/dist/lib/harness/adapters/grok.js +14 -0
  117. package/dist/lib/harness/adapters/kimi.d.ts +2 -0
  118. package/dist/lib/harness/adapters/kimi.js +49 -0
  119. package/dist/lib/harness/adapters/muse.d.ts +2 -0
  120. package/dist/lib/harness/adapters/muse.js +44 -0
  121. package/dist/lib/harness/adapters/opencode.d.ts +2 -0
  122. package/dist/lib/harness/adapters/opencode.js +14 -0
  123. package/dist/lib/harness/exec-config-version.d.ts +8 -0
  124. package/dist/lib/harness/exec-config-version.js +22 -0
  125. package/dist/lib/harness/index.d.ts +1 -0
  126. package/dist/lib/harness/index.js +34 -0
  127. package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
  128. package/dist/lib/{hooks.js → hooks/install.js} +11 -11
  129. package/dist/lib/import.js +1 -1
  130. package/dist/lib/installations/index.d.ts +4 -4
  131. package/dist/lib/installations/index.js +3 -4
  132. package/dist/lib/installations/migrate.js +6 -6
  133. package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
  134. package/dist/lib/{shims.js → installations/shims.js} +12 -114
  135. package/dist/lib/installations/store.d.ts +141 -3
  136. package/dist/lib/installations/store.js +475 -5
  137. package/dist/lib/installations/versions.d.ts +1 -126
  138. package/dist/lib/installations/versions.js +5 -465
  139. package/dist/lib/isolation-boundary-report.d.ts +1 -1
  140. package/dist/lib/isolation-boundary-report.js +1 -1
  141. package/dist/lib/mailbox-gc.js +2 -3
  142. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  143. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  144. package/dist/lib/monitors/dispatch.d.ts +1 -1
  145. package/dist/lib/monitors/dispatch.js +2 -2
  146. package/dist/lib/monitors/state.d.ts +1 -1
  147. package/dist/lib/observe-aliases.d.ts +2 -2
  148. package/dist/lib/observe-aliases.js +2 -11
  149. package/dist/lib/plugins/plugins.js +1 -1
  150. package/dist/lib/plugins/skills.js +1 -1
  151. package/dist/lib/prix-account.d.ts +158 -0
  152. package/dist/lib/prix-account.js +214 -0
  153. package/dist/lib/project-key.js +7 -0
  154. package/dist/lib/project-launch.js +1 -1
  155. package/dist/lib/refresh.js +2 -2
  156. package/dist/lib/resource-inventory.d.ts +1 -1
  157. package/dist/lib/resource-inventory.js +1 -1
  158. package/dist/lib/rules/rules.js +1 -1
  159. package/dist/lib/scheduling/routines.js +1 -1
  160. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  161. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  162. package/dist/lib/secrets/bundles.d.ts +1 -1
  163. package/dist/lib/secrets/bundles.js +1 -1
  164. package/dist/lib/secrets/headless.d.ts +16 -0
  165. package/dist/lib/secrets/headless.js +21 -0
  166. package/dist/lib/secrets/remote.d.ts +9 -7
  167. package/dist/lib/secrets/remote.js +18 -9
  168. package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
  169. package/dist/lib/self-heal/checks/path.js +1 -1
  170. package/dist/lib/self-heal/checks/shadowing.js +1 -1
  171. package/dist/lib/self-heal/checks/shims.js +1 -1
  172. package/dist/lib/session/discover.d.ts +39 -0
  173. package/dist/lib/session/discover.js +3 -3
  174. package/dist/lib/session/parse.js +24 -42
  175. package/dist/lib/session/recovery.js +1 -1
  176. package/dist/lib/session/share-html.d.ts +55 -0
  177. package/dist/lib/session/share-html.js +319 -0
  178. package/dist/lib/settings-manifest.d.ts +2 -0
  179. package/dist/lib/settings-manifest.js +81 -3
  180. package/dist/lib/share/publish.d.ts +38 -0
  181. package/dist/lib/share/publish.js +81 -8
  182. package/dist/lib/staleness/prune.d.ts +1 -1
  183. package/dist/lib/staleness/prune.js +1 -1
  184. package/dist/lib/staleness/writers/hooks.js +1 -1
  185. package/dist/lib/startup/command-registry.d.ts +2 -1
  186. package/dist/lib/startup/command-registry.js +5 -3
  187. package/dist/lib/teams/agents.js +2 -19
  188. package/dist/lib/teams/parsers.js +20 -26
  189. package/dist/lib/teams/registry.js +2 -21
  190. package/dist/lib/triggers/handlers.d.ts +37 -2
  191. package/dist/lib/triggers/handlers.js +57 -6
  192. package/dist/lib/triggers/webhook.d.ts +80 -6
  193. package/dist/lib/triggers/webhook.js +128 -4
  194. package/dist/lib/types.d.ts +1 -1
  195. package/dist/lib/uninstall.js +1 -1
  196. package/dist/lib/version.d.ts +9 -2
  197. package/dist/lib/version.js +11 -9
  198. package/dist/lib/wrap.d.ts +33 -0
  199. package/dist/lib/wrap.js +70 -0
  200. package/package.json +1 -1
  201. /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
@@ -13,39 +13,14 @@
13
13
  * computer run has no on-disk artifact of its own to open (see
14
14
  * `lib/computer/sessions-list.ts`'s module docblock), so `enter` prints the
15
15
  * run's full action list rather than opening a file, and the picker keeps
16
- * browsing afterward instead of exiting.
16
+ * browsing afterward instead of exiting. Interactive routing and the browse
17
+ * loop live in `sessions-picker-factory.ts` (shared with the browser twin).
17
18
  */
18
19
  import chalk from 'chalk';
19
- import { itemPicker } from '../lib/picker.js';
20
- import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
21
20
  import { buildPreview } from './sessions-picker.js';
21
+ import { createSessionsPickerCommand } from './sessions-picker-factory.js';
22
22
  import { runComputerSessions, printComputerSessionRows, buildComputerSessionRows, matchesComputerSessionRow, formatRowActions, } from '../lib/computer/sessions-list.js';
23
23
  import { formatRelativeTime } from '../lib/session/relative-time.js';
24
- /** True when the interactive picker should open instead of the printed table:
25
- * a real TTY, no `--json`, and `--no-interactive` not set. */
26
- export function shouldOpenInteractiveComputerSessions(opts, isTTY) {
27
- return opts.interactive !== false && !opts.json && isTTY;
28
- }
29
- /**
30
- * Shared entry point for `agents computer sessions` and `agents sessions
31
- * --computer`. Mirrors `runBrowserSessionsCommand`'s interactive-routing
32
- * split so both call sites stay in lockstep.
33
- */
34
- export async function runComputerSessionsCommand(opts) {
35
- if (!shouldOpenInteractiveComputerSessions(opts, isInteractiveTerminal())) {
36
- if (opts.rows)
37
- printComputerSessionRows(opts.rows, { limit: opts.limit, json: opts.json });
38
- else
39
- runComputerSessions({ machine: opts.machine, limit: opts.limit, json: opts.json });
40
- return;
41
- }
42
- const rows = opts.rows ?? buildComputerSessionRows({ machine: opts.machine });
43
- if (rows.length === 0) {
44
- console.log(`No computer actions recorded${opts.machine ? ` for machine "${opts.machine}"` : ''}.`);
45
- return;
46
- }
47
- await browseComputerSessions(rows);
48
- }
49
24
  function rowLinkSummary(row) {
50
25
  if (row.linkStatus === 'linked' && row.linkedSession) {
51
26
  const s = row.linkedSession;
@@ -111,31 +86,33 @@ function printRunDetail(row) {
111
86
  console.log('(no driving actions recorded)');
112
87
  console.log('');
113
88
  }
114
- /** Run-level picker with an action-list preview. Loops until the user quits
115
- * (esc) so a session's whole run history can be browsed repeatedly in one
116
- * sitting — mirroring `browseAndOpen`'s loop, minus the artifact drill-down
117
- * a computer run has none of. */
118
- async function browseComputerSessions(rows) {
119
- for (;;) {
120
- let picked;
121
- try {
122
- picked = await itemPicker({
123
- message: 'Computer sessions:',
124
- items: rows,
125
- filter: (query) => (query.trim() ? rows.filter((r) => matchesComputerSessionRow(r, query)) : rows),
126
- labelFor: (row) => formatRowLabel(row),
127
- buildPreview: (row) => buildRowPreview(row),
128
- emptyMessage: 'No computer sessions match.',
129
- enterHint: 'view actions',
130
- });
131
- }
132
- catch (err) {
133
- if (isPromptCancelled(err))
134
- return;
135
- throw err;
136
- }
137
- if (!picked)
138
- return;
139
- printRunDetail(picked.item);
140
- }
89
+ const computerSessionsPicker = createSessionsPickerCommand({
90
+ runFlat: (opts) => {
91
+ if (opts.rows)
92
+ printComputerSessionRows(opts.rows, { limit: opts.limit, json: opts.json });
93
+ else
94
+ runComputerSessions({ machine: opts.machine, limit: opts.limit, json: opts.json });
95
+ },
96
+ buildRows: (opts) => opts.rows ?? buildComputerSessionRows({ machine: opts.machine }),
97
+ emptyMessage: (opts) => `No computer actions recorded${opts.machine ? ` for machine "${opts.machine}"` : ''}.`,
98
+ message: 'Computer sessions:',
99
+ matches: matchesComputerSessionRow,
100
+ labelFor: formatRowLabel,
101
+ buildPreview: buildRowPreview,
102
+ emptyFilterMessage: 'No computer sessions match.',
103
+ enterHint: 'view actions',
104
+ onOpen: printRunDetail,
105
+ });
106
+ /** True when the interactive picker should open instead of the printed table:
107
+ * a real TTY, no `--json`, and `--no-interactive` not set. */
108
+ export function shouldOpenInteractiveComputerSessions(opts, isTTY) {
109
+ return computerSessionsPicker.shouldOpen(opts, isTTY);
110
+ }
111
+ /**
112
+ * Shared entry point for `agents computer sessions` and `agents sessions
113
+ * --computer`. Mirrors `runBrowserSessionsCommand`'s interactive-routing
114
+ * split so both call sites stay in lockstep.
115
+ */
116
+ export async function runComputerSessionsCommand(opts) {
117
+ await computerSessionsPicker.run(opts);
141
118
  }
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { resolveHelperExec, resolveSocketPath } from '../lib/computer-rpc.js';
2
+ import { resolveHelperExec, resolveSocketPath } from '../lib/computer/computer-rpc.js';
3
3
  /**
4
4
  * Pure platform gate. The computer subsystem is macOS-only for LOCAL driving
5
5
  * (Accessibility / launchctl). It is NOT blocked off macOS when a remote daemon
@@ -3,8 +3,8 @@ import * as fs from 'fs';
3
3
  import * as os from 'os';
4
4
  import * as path from 'path';
5
5
  import { registerCommandGroups } from '../lib/help.js';
6
- import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, resolvePeersPath, resolveTcpEndpoint, loadComputerAllowList, loadDefaultPeers, writeComputerPolicy, writeComputerPeers, } from '../lib/computer-rpc.js';
7
- import { setupRemoteHelper, startRemoteTunnel, stopRemoteHelper, hydrateRemoteEnvFromState, readRemoteState, resolveRemoteDevice, REMOTE_TASK_NAME, WIN_HELPER_EXE, } from '../lib/ssh-tunnel.js';
6
+ import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, resolvePeersPath, resolveTcpEndpoint, loadComputerAllowList, loadDefaultPeers, writeComputerPolicy, writeComputerPeers, } from '../lib/computer/computer-rpc.js';
7
+ import { setupRemoteHelper, startRemoteTunnel, stopRemoteHelper, hydrateRemoteEnvFromState, readRemoteState, resolveRemoteDevice, REMOTE_TASK_NAME, WIN_HELPER_EXE, } from '../lib/computer/ssh-tunnel.js';
8
8
  import { sshExec } from '../lib/ssh-exec.js';
9
9
  import { encodePowershell } from '../lib/hosts/remote-cmd.js';
10
10
  import { registerActionCommands, withClient, unwrap, pickTarget, emitComputerAction } from './computer-actions.js';
@@ -25,7 +25,7 @@ import type { Command } from 'commander';
25
25
  import { type FleetInventory } from '../lib/devices/fleet-divergence.js';
26
26
  import { type DoctorFinding } from '../lib/devices/doctor-findings.js';
27
27
  import { type VersionResourceReport } from '../lib/doctor-diff.js';
28
- import { type DuplicateVersionHook } from '../lib/hooks.js';
28
+ import { type DuplicateVersionHook } from '../lib/hooks/install.js';
29
29
  import { type SyncStatusRow, type OrphanRow } from '../lib/drift.js';
30
30
  import { type FetchStatusMarker } from '../lib/auto-pull.js';
31
31
  export declare function wrapLine(prefix: string, text: string, width?: number): string[];
@@ -1,4 +1,4 @@
1
- import { IsolationBoundaryError } from '../lib/shims.js';
1
+ import { IsolationBoundaryError } from '../lib/installations/shims.js';
2
2
  import { explainIsolationBoundary } from '../lib/isolation-boundary-report.js';
3
3
  import { addHostOption } from '../lib/hosts/option.js';
4
4
  import { buildRemoteAgentsInvocation } from '../lib/hosts/remote-cmd.js';
@@ -23,7 +23,7 @@ import { getVersionHomePath, listInstalledVersions, } from '../lib/installations
23
23
  import { resolveAgentTargets, AgentSpecError } from '../lib/agent-spec/index.js';
24
24
  import { loadManifest, isStale } from '../lib/staleness/index.js';
25
25
  import { diffVersionResources, DOCTOR_ALL_KINDS, } from '../lib/doctor-diff.js';
26
- import { checkVersionHookWiring, inspectDuplicateVersionHooks, registerHooksToSettings, repairManagedHookRuntimeArtifacts } from '../lib/hooks.js';
26
+ import { checkVersionHookWiring, inspectDuplicateVersionHooks, registerHooksToSettings, repairManagedHookRuntimeArtifacts } from '../lib/hooks/install.js';
27
27
  import { isVersionIsolated } from '../lib/installations/versions.js';
28
28
  import { computeDrift, checkSyncStatus, countOrphans, computeSourceBehind } from '../lib/drift.js';
29
29
  import { readAuthHealthCache, summarizeHostAuth } from '../lib/auth-health.js';
@@ -1523,7 +1523,7 @@ export function registerDoctorCommand(program) {
1523
1523
  console.error(chalk.red('--adopt and --release are mutually exclusive; pass only one.'));
1524
1524
  process.exit(1);
1525
1525
  }
1526
- const { adoptShadowingLauncher, releaseAdoptedLauncher } = await import('../lib/shims.js');
1526
+ const { adoptShadowingLauncher, releaseAdoptedLauncher } = await import('../lib/installations/shims.js');
1527
1527
  const raw = (opts.adopt || opts.release);
1528
1528
  const agent = resolveAgentName(raw);
1529
1529
  if (!agent) {
@@ -312,7 +312,7 @@ export async function loadSessionMetasForFeedEnrichment(load) {
312
312
  export function registerFeedCommand(program) {
313
313
  const feed = program
314
314
  .command('feed')
315
- .description('Operator inbox + agent status posts (aliases: inbox = needs-you; timeline = --filter updates)')
315
+ .description('Operator inbox + agent status posts (alias: inbox = needs-you). Agent progress = --filter updates')
316
316
  .option('--json', 'Output as JSON (each block stamped with its outcome + ask class)')
317
317
  .option('--filter <view>', 'What to show: needs (default) · updates · all', 'needs')
318
318
  .option('--flat', 'List one block per agent instead of grouping by outcome')
@@ -449,7 +449,7 @@ export function registerFeedCommand(program) {
449
449
  setupWarnings.push(activityInstall.error);
450
450
  if (!hookInstall.error || !activityInstall.error) {
451
451
  const [{ iterHooksCapableVersions, parseHookManifest, registerHooksToSettings }, { getVersionHomePath }] = await Promise.all([
452
- import('../lib/hooks.js'),
452
+ import('../lib/hooks/install.js'),
453
453
  import('../lib/installations/versions.js'),
454
454
  ]);
455
455
  const manifest = parseHookManifest({ warn: false });
@@ -647,8 +647,8 @@ export function registerFeedCommand(program) {
647
647
  registerFeedObserveAliases(program);
648
648
  }
649
649
  /**
650
- * `inbox` / `timeline` → feed. Loaded with the feed module so lazy COMMAND_LOADERS
651
- * for those names also get the real `feed` command registered for re-parse.
650
+ * `inbox` → feed. Loaded with the feed module so the lazy COMMAND_LOADERS entry
651
+ * for `inbox` also gets the real `feed` command registered for re-parse.
652
652
  */
653
653
  function registerFeedObserveAliases(program) {
654
654
  const reparse = async (alias) => {
@@ -671,14 +671,6 @@ function registerFeedObserveAliases(program) {
671
671
  .action(async () => {
672
672
  await reparse('inbox');
673
673
  });
674
- program
675
- .command('timeline')
676
- .description('Agent progress stream (alias of `agents feed --filter updates`). What agents posted recently.')
677
- .allowUnknownOption()
678
- .allowExcessArguments()
679
- .action(async () => {
680
- await reparse('timeline');
681
- });
682
674
  }
683
675
  /**
684
676
  * Mirror a written post to the configured sinks (`feed.broadcast` in
@@ -7,7 +7,7 @@ import { checkbox } from '@inquirer/prompts';
7
7
  import { AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
8
8
  import { supports, capableAgents } from '../lib/capabilities.js';
9
9
  import { cloneRepo } from '../lib/git.js';
10
- import { discoverHooksFromRepo, installHooksCentrally, listCentralHooks, listInstalledHooksWithScope, getHookInfo, parseHookManifest, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks.js';
10
+ import { discoverHooksFromRepo, installHooksCentrally, listCentralHooks, listInstalledHooksWithScope, getHookInfo, parseHookManifest, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks/install.js';
11
11
  import { listInstalledVersions, getGlobalDefault, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, resolveInstalledAgentTargets, } from '../lib/installations/versions.js';
12
12
  import { recordVersionResources } from '../lib/state.js';
13
13
  import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, promptRemovalTargets, resolveAgentTargetsAutoInstalling, resolveListFilterOrExit, } from './utils.js';
@@ -21,7 +21,7 @@
21
21
  * management — same behavior as the first-run `agents setup` import flow.
22
22
  */
23
23
  import { withIsolationBoundary } from '../lib/isolation-boundary-report.js';
24
- import { assertIsolationBoundary, createVersionedAlias } from '../lib/shims.js';
24
+ import { assertIsolationBoundary, createVersionedAlias } from '../lib/installations/shims.js';
25
25
  import { markVersionIsolated } from '../lib/installations/versions.js';
26
26
  import chalk from 'chalk';
27
27
  import ora from 'ora';
@@ -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;