@phnx-labs/agents-cli 1.22.43 → 1.22.45

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 (164) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +14 -12
  3. package/dist/bootstrap.js +3 -6
  4. package/dist/cli/command-registry.d.ts +0 -7
  5. package/dist/cli/command-registry.js +1 -21
  6. package/dist/commands/accounts.d.ts +1 -9
  7. package/dist/commands/accounts.js +12 -90
  8. package/dist/commands/apply.d.ts +7 -10
  9. package/dist/commands/apply.js +15 -35
  10. package/dist/commands/artifacts.js +4 -5
  11. package/dist/commands/audit.d.ts +6 -6
  12. package/dist/commands/audit.js +12 -12
  13. package/dist/commands/beta.d.ts +7 -1
  14. package/dist/commands/beta.js +16 -9
  15. package/dist/commands/commands.js +1 -1
  16. package/dist/commands/doctor.js +1 -4
  17. package/dist/commands/events.d.ts +2 -2
  18. package/dist/commands/events.js +6 -5
  19. package/dist/commands/exec.d.ts +1 -1
  20. package/dist/commands/exec.js +8 -4
  21. package/dist/commands/factory.d.ts +1 -1
  22. package/dist/commands/fleet-capture.d.ts +1 -1
  23. package/dist/commands/fleet-capture.js +4 -5
  24. package/dist/commands/harness.js +1 -49
  25. package/dist/commands/hooks.js +1 -1
  26. package/dist/commands/insights.d.ts +1 -1
  27. package/dist/commands/insights.js +85 -156
  28. package/dist/commands/menubar.js +58 -1
  29. package/dist/commands/output.js +2 -2
  30. package/dist/commands/prune.js +5 -4
  31. package/dist/commands/routines.test-fixture.d.ts +86 -0
  32. package/dist/commands/routines.test-fixture.js +345 -0
  33. package/dist/commands/sessions.test-fixture.d.ts +24 -0
  34. package/dist/commands/sessions.test-fixture.js +224 -0
  35. package/dist/commands/setup.js +5 -1
  36. package/dist/commands/share.d.ts +9 -10
  37. package/dist/commands/share.js +68 -50
  38. package/dist/commands/skills.js +1 -1
  39. package/dist/commands/ssh.js +15 -62
  40. package/dist/commands/subagents.js +1 -1
  41. package/dist/commands/view.js +5 -2
  42. package/dist/lib/analytics/mix-commands.d.ts +6 -28
  43. package/dist/lib/analytics/mix-commands.js +14 -40
  44. package/dist/lib/audit/log.d.ts +2 -2
  45. package/dist/lib/audit/log.js +2 -2
  46. package/dist/lib/beta.js +1 -1
  47. package/dist/lib/browser/domain-skills.d.ts +26 -6
  48. package/dist/lib/browser/domain-skills.js +81 -43
  49. package/dist/lib/daemon/daemon.js +11 -0
  50. package/dist/lib/daemon/daemon.test-fixture.d.ts +24 -0
  51. package/dist/lib/daemon/daemon.test-fixture.js +71 -0
  52. package/dist/lib/daemon-ticks.d.ts +38 -1
  53. package/dist/lib/daemon-ticks.js +52 -5
  54. package/dist/lib/device-config.js +1 -2
  55. package/dist/lib/devices/discovery-policy.d.ts +3 -3
  56. package/dist/lib/devices/discovery-policy.js +3 -3
  57. package/dist/lib/devices/fleet.d.ts +4 -8
  58. package/dist/lib/devices/fleet.js +3 -15
  59. package/dist/lib/devices/health-report.js +3 -3
  60. package/dist/lib/devices/pool.d.ts +0 -6
  61. package/dist/lib/devices/pool.js +0 -6
  62. package/dist/lib/devices/registry.d.ts +0 -15
  63. package/dist/lib/devices/registry.js +0 -9
  64. package/dist/lib/devices/stats-cache.d.ts +12 -0
  65. package/dist/lib/devices/stats-cache.js +24 -5
  66. package/dist/lib/fleet/capture.d.ts +1 -1
  67. package/dist/lib/fleet/manifest.js +1 -1
  68. package/dist/lib/fleet/remote-login.d.ts +2 -2
  69. package/dist/lib/fleet/remote-login.js +3 -4
  70. package/dist/lib/hosts/dispatch.d.ts +2 -0
  71. package/dist/lib/hosts/dispatch.js +8 -1
  72. package/dist/lib/hosts/passthrough.js +0 -1
  73. package/dist/lib/hosts/providers/devices.js +1 -13
  74. package/dist/lib/hosts/ready.d.ts +12 -3
  75. package/dist/lib/hosts/ready.js +27 -12
  76. package/dist/lib/hosts/registry.d.ts +4 -5
  77. package/dist/lib/hosts/registry.js +3 -7
  78. package/dist/lib/installations/versions.js +21 -2
  79. package/dist/lib/menubar/install-menubar.d.ts +109 -0
  80. package/dist/lib/menubar/install-menubar.js +199 -4
  81. package/dist/lib/remote-agents-json.js +1 -7
  82. package/dist/lib/routines-placement.d.ts +1 -1
  83. package/dist/lib/routines-placement.js +1 -1
  84. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  85. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
  86. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  87. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
  88. package/dist/lib/session/active.d.ts +4 -16
  89. package/dist/lib/session/active.js +2 -6
  90. package/dist/lib/session/db.d.ts +13 -98
  91. package/dist/lib/session/db.js +4 -11
  92. package/dist/lib/session/discover.d.ts +5 -81
  93. package/dist/lib/session/discover.js +5 -5
  94. package/dist/lib/session/remote/remote-bundle.d.ts +12 -0
  95. package/dist/lib/session/remote/remote-bundle.js +61 -0
  96. package/dist/lib/session/remote/remote-list.d.ts +148 -0
  97. package/dist/lib/session/remote/remote-list.js +607 -0
  98. package/dist/lib/session/remote/remote.d.ts +132 -0
  99. package/dist/lib/session/remote/remote.js +314 -0
  100. package/dist/lib/session/remote/watch.d.ts +101 -0
  101. package/dist/lib/session/remote/watch.js +241 -0
  102. package/dist/lib/session/remote-bundle.d.ts +1 -12
  103. package/dist/lib/session/remote-bundle.js +3 -61
  104. package/dist/lib/session/remote-list.d.ts +1 -148
  105. package/dist/lib/session/remote-list.js +3 -607
  106. package/dist/lib/session/remote.d.ts +1 -132
  107. package/dist/lib/session/remote.js +3 -314
  108. package/dist/lib/session/watch.d.ts +1 -101
  109. package/dist/lib/session/watch.js +3 -242
  110. package/dist/lib/share/config.d.ts +12 -2
  111. package/dist/lib/share/config.js +47 -11
  112. package/dist/lib/share/delete.js +1 -1
  113. package/dist/lib/share/publish.js +1 -1
  114. package/dist/lib/share/worker-template.js +2 -2
  115. package/dist/lib/smart-launch.d.ts +4 -4
  116. package/dist/lib/smart-launch.js +8 -18
  117. package/dist/lib/staleness/index.d.ts +3 -1
  118. package/dist/lib/staleness/index.js +21 -1
  119. package/dist/lib/staleness/types.d.ts +11 -0
  120. package/dist/lib/staleness/writers/commands.js +13 -4
  121. package/dist/lib/staleness/writers/hooks.js +3 -1
  122. package/dist/lib/staleness/writers/mcp.d.ts +0 -7
  123. package/dist/lib/staleness/writers/mcp.js +16 -1
  124. package/dist/lib/staleness/writers/rules.js +1 -1
  125. package/dist/lib/staleness/writers/skills.js +3 -1
  126. package/dist/lib/staleness/writers/subagents.d.ts +0 -9
  127. package/dist/lib/staleness/writers/subagents.js +18 -1
  128. package/dist/lib/staleness/writers/types.d.ts +10 -0
  129. package/dist/lib/startup/command-registry.d.ts +8 -1
  130. package/dist/lib/startup/command-registry.js +21 -6
  131. package/dist/lib/startup/root-command.d.ts +18 -1
  132. package/dist/lib/startup/root-command.js +18 -1
  133. package/dist/lib/state.js +7 -0
  134. package/dist/lib/teams/placement-probe.js +27 -12
  135. package/dist/lib/teams/scheduler.d.ts +3 -1
  136. package/dist/lib/types.d.ts +1 -1
  137. package/dist/lib/view-types.d.ts +3 -0
  138. package/package.json +2 -3
  139. package/dist/bin/agents +0 -0
  140. package/dist/commands/auth.d.ts +0 -9
  141. package/dist/commands/auth.js +0 -108
  142. package/dist/commands/org.d.ts +0 -6
  143. package/dist/commands/org.js +0 -175
  144. package/dist/commands/serve.d.ts +0 -10
  145. package/dist/commands/serve.js +0 -68
  146. package/dist/commands/trends.d.ts +0 -10
  147. package/dist/commands/trends.js +0 -12
  148. package/dist/lib/entitlement.d.ts +0 -31
  149. package/dist/lib/entitlement.js +0 -137
  150. package/dist/lib/prix-account.d.ts +0 -158
  151. package/dist/lib/prix-account.js +0 -214
  152. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  153. package/dist/lib/serve/control.d.ts +0 -95
  154. package/dist/lib/serve/control.js +0 -260
  155. package/dist/lib/serve/data.d.ts +0 -81
  156. package/dist/lib/serve/data.js +0 -91
  157. package/dist/lib/serve/page.d.ts +0 -7
  158. package/dist/lib/serve/page.js +0 -140
  159. package/dist/lib/serve/server.d.ts +0 -80
  160. package/dist/lib/serve/server.js +0 -145
  161. package/dist/lib/serve/stream.d.ts +0 -43
  162. package/dist/lib/serve/stream.js +0 -116
  163. package/dist/lib/serve/token.d.ts +0 -35
  164. package/dist/lib/serve/token.js +0 -85
@@ -1,5 +1,14 @@
1
+ /**
2
+ * MCP writer — thin dispatcher into `installMcpServers` from `lib/mcp.ts`.
3
+ *
4
+ * The per-agent format handling (Claude CLI, Codex TOML, Cursor JSON, etc.)
5
+ * lives in lib/mcp.ts; we keep it there to avoid the import cycle with
6
+ * `versions.ts`. The writer simply hands the selection to that function.
7
+ */
8
+ import * as fs from 'fs';
1
9
  import { capableAgents } from '../../capabilities.js';
2
10
  import { installMcpServers } from '../../mcp.js';
11
+ import { getMcpConfigPathForHome } from '../../agents.js';
3
12
  import { lazyAgentMap } from './lazy-map.js';
4
13
  function buildMcpWriter(agent) {
5
14
  return {
@@ -9,7 +18,13 @@ function buildMcpWriter(agent) {
9
18
  const r = installMcpServers(agent, version, versionHome, selection, { cwd });
10
19
  // Forward r.errors: dropping them is what let a harness with no config
11
20
  // writer report a clean sync while writing nothing (RUSH-2677).
12
- return { synced: r.applied, ...(r.errors.length ? { errors: r.errors } : {}) };
21
+ // All applied servers land in the one canonical config file — that file
22
+ // is the artifact whose deletion must read as stale (#2398). Recorded
23
+ // only when it verifiably exists after the write, so an agent whose CLI
24
+ // wrote elsewhere can never produce a perpetually-stale manifest.
25
+ const configPath = getMcpConfigPathForHome(agent, versionHome);
26
+ const paths = r.applied.length > 0 && fs.existsSync(configPath) ? [configPath] : [];
27
+ return { synced: r.applied, paths, ...(r.errors.length ? { errors: r.errors } : {}) };
13
28
  },
14
29
  };
15
30
  }
@@ -43,7 +43,7 @@ function buildRulesWriter(agent) {
43
43
  }
44
44
  catch { /* destination did not exist */ }
45
45
  fs.writeFileSync(destFile, composed.content);
46
- return { synced: [targetName] };
46
+ return { synced: [targetName], paths: [destFile] };
47
47
  },
48
48
  };
49
49
  }
@@ -56,6 +56,7 @@ function buildSkillsWriter(agent) {
56
56
  catch { /* does not exist yet */ }
57
57
  fs.mkdirSync(skillsTarget, { recursive: true });
58
58
  const synced = [];
59
+ const paths = [];
59
60
  for (const skill of selection) {
60
61
  const srcDir = resolveSkillSource(skill, { agent });
61
62
  if (!srcDir)
@@ -64,8 +65,9 @@ function buildSkillsWriter(agent) {
64
65
  removePath(destDir);
65
66
  copyDir(srcDir, destDir);
66
67
  synced.push(skill);
68
+ paths.push(destDir);
67
69
  }
68
- return { synced };
70
+ return { synced, paths };
69
71
  },
70
72
  remove({ versionHome, name }) {
71
73
  const agentDir = path.join(versionHome, agentConfigDirName(agent));
@@ -1,12 +1,3 @@
1
- /**
2
- * Subagents writer. Every agent's on-disk layout (target dir, file/dir shape,
3
- * transform) is declared once in the subagent registry; this writer is generic
4
- * and iterates the registry instead of a per-agent `else if` chain.
5
- *
6
- * Source-side discovery is `listInstalledSubagents` from lib/subagents.ts —
7
- * it reads user + system layers only (project layer excluded for the same
8
- * defense as commands/skills/hooks).
9
- */
10
1
  import type { AgentId } from '../../types.js';
11
2
  import type { ResourceWriter } from './types.js';
12
3
  export declare const subagentsWriters: Partial<Record<AgentId, ResourceWriter<string[]>>>;
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Subagents writer. Every agent's on-disk layout (target dir, file/dir shape,
3
+ * transform) is declared once in the subagent registry; this writer is generic
4
+ * and iterates the registry instead of a per-agent `else if` chain.
5
+ *
6
+ * Source-side discovery is `listInstalledSubagents` from lib/subagents.ts —
7
+ * it reads user + system layers only (project layer excluded for the same
8
+ * defense as commands/skills/hooks).
9
+ */
10
+ import * as fs from 'fs';
1
11
  import { capableAgents } from '../../capabilities.js';
2
12
  import { listInstalledSubagents } from '../../subagents.js';
3
13
  import { subagentTarget } from '../../subagents-registry.js';
@@ -14,6 +24,7 @@ function buildSubagentsWriter(agent) {
14
24
  const map = new Map(all.map(s => [s.name, s]));
15
25
  const dir = target.dir(versionHome);
16
26
  const synced = [];
27
+ const paths = [];
17
28
  for (const name of selection) {
18
29
  const sub = map.get(name);
19
30
  if (!sub)
@@ -21,10 +32,16 @@ function buildSubagentsWriter(agent) {
21
32
  try {
22
33
  target.write(dir, sub);
23
34
  synced.push(sub.name);
35
+ // The registry owns the layout — record the artifact paths this
36
+ // write occupies so a later deletion reads as stale (#2398).
37
+ for (const entry of target.occupied(dir, sub.name)) {
38
+ if (fs.existsSync(entry.path))
39
+ paths.push(entry.path);
40
+ }
24
41
  }
25
42
  catch { /* per-item sync failure: skip */ }
26
43
  }
27
- return { synced };
44
+ return { synced, paths };
28
45
  },
29
46
  };
30
47
  }
@@ -28,6 +28,16 @@ export interface WriteArgs<Sel> {
28
28
  export interface WriteResult {
29
29
  /** Names actually written. Empty array = write produced nothing (not an error). */
30
30
  synced: string[];
31
+ /**
32
+ * Absolute on-disk paths of the artifacts this write materialized (a file or
33
+ * a dir root per resource, per the agent's layout). Recorded into the sync
34
+ * manifest as `writtenTargets` so `isStale` can flag a deleted artifact as
35
+ * stale with one existsSync per path (#2398) — no content reads, keeping the
36
+ * fast-guard budget (RUSH-2320). Optional: a writer whose artifact path is
37
+ * not cheaply known (goose recipe YAML, plugins) may omit entries; absence
38
+ * means "not verified", never a false stale.
39
+ */
40
+ paths?: string[];
31
41
  /**
32
42
  * Per-item failures the writer could not complete, as user-facing sentences.
33
43
  *
@@ -24,7 +24,14 @@ export declare const KNOWN_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
24
24
  * use `agents feed --filter updates` (RUSH-2692). `status` moved under
25
25
  * `agents sync status` (RUSH-2864). `tickets` was removed — use `linear`
26
26
  * (linear-cli) (RUSH-2932). `alias` moved under `agents setup alias` (RUSH-2965).
27
- * `inbox` was a pure alias of `agents feed` (RUSH-2984).
27
+ * `inbox` was a pure alias of `agents feed` (RUSH-2984). `unshare` nested under
28
+ * `agents artifacts unshare` (RUSH-2989). `audit` nested under `agents events audit`.
29
+ * `trends` nested under `agents insights mix` / `insights trends`. `serve` (the
30
+ * read-only local web companion + `--control` anchor) was removed with the
31
+ * unshipped iOS Fleet Cockpit it existed for (RUSH-3001). `apply` nested under
32
+ * `agents fleet apply` / `agents devices apply`. `beta` nested under
33
+ * `agents setup beta` (RUSH-2981). `auth` and `org` (the Prix-coupled account
34
+ * layer) were removed pending the Phoenix-backed replacement (RUSH-2581).
28
35
  */
29
36
  export declare const RETIRED_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
30
37
  export declare function isKnownTopLevelCommand(name: string): boolean;
@@ -1,15 +1,15 @@
1
1
  const LOADED_COMMAND_NAMES = [
2
- 'accounts', 'auth', 'org', 'view', 'inspect', 'feedback', 'commands', 'hooks', 'skills', 'rules', 'memory',
2
+ 'accounts', 'view', 'inspect', 'feedback', 'commands', 'hooks', 'skills', 'rules', 'memory',
3
3
  'permissions', 'mcp', 'clis', 'subagents', 'plugins', 'workflows', 'add', 'use', 'list',
4
4
  'remove', 'rm', 'purge', 'update', 'prune', 'import', 'registry', 'search', 'install',
5
5
  'routines', 'monitors', 'projects', 'run', 'open', 'reconnect', 'fork', 'config',
6
- 'models', 'modes', 'trash', 'restore', 'doctor', 'apply',
7
- 'route', 'harness', 'harnesses', 'secrets', 'menubar', 'beta', 'sync',
8
- 'refresh-rules', 'factory', 'usage', 'insights', 'perf', 'trends',
6
+ 'models', 'modes', 'trash', 'restore', 'doctor',
7
+ 'route', 'harness', 'harnesses', 'secrets', 'menubar', 'sync',
8
+ 'refresh-rules', 'factory', 'usage', 'insights', 'perf',
9
9
  'pty', 'tmux', 'watchdog', 'browser', 'computer', 'logs', 'events',
10
10
  'ssh', 'devices', 'fleet', 'repos', 'repo', 'setup', 'uninstall', 'upgrade', 'sessions',
11
11
  'teams', 'cloud', 'message', 'send', 'notify', 'feed',
12
- 'mailboxes', 'mailbox', 'serve', 'artifacts', 'unshare', 'audit', 'webhooks',
12
+ 'mailboxes', 'mailbox', 'artifacts', 'webhooks',
13
13
  'humans', 'daemon',
14
14
  ];
15
15
  const INLINE_COMMAND_NAMES = [
@@ -44,10 +44,20 @@ export const KNOWN_TOP_LEVEL_COMMANDS = new Set([
44
44
  * use `agents feed --filter updates` (RUSH-2692). `status` moved under
45
45
  * `agents sync status` (RUSH-2864). `tickets` was removed — use `linear`
46
46
  * (linear-cli) (RUSH-2932). `alias` moved under `agents setup alias` (RUSH-2965).
47
- * `inbox` was a pure alias of `agents feed` (RUSH-2984).
47
+ * `inbox` was a pure alias of `agents feed` (RUSH-2984). `unshare` nested under
48
+ * `agents artifacts unshare` (RUSH-2989). `audit` nested under `agents events audit`.
49
+ * `trends` nested under `agents insights mix` / `insights trends`. `serve` (the
50
+ * read-only local web companion + `--control` anchor) was removed with the
51
+ * unshipped iOS Fleet Cockpit it existed for (RUSH-3001). `apply` nested under
52
+ * `agents fleet apply` / `agents devices apply`. `beta` nested under
53
+ * `agents setup beta` (RUSH-2981). `auth` and `org` (the Prix-coupled account
54
+ * layer) were removed pending the Phoenix-backed replacement (RUSH-2581).
48
55
  */
49
56
  export const RETIRED_TOP_LEVEL_COMMANDS = new Set([
50
57
  'webhook',
58
+ 'auth',
59
+ 'org',
60
+ 'serve',
51
61
  'login',
52
62
  'logout',
53
63
  'budget',
@@ -67,6 +77,11 @@ export const RETIRED_TOP_LEVEL_COMMANDS = new Set([
67
77
  'tickets',
68
78
  'alias',
69
79
  'inbox',
80
+ 'unshare',
81
+ 'audit',
82
+ 'trends',
83
+ 'apply',
84
+ 'beta',
70
85
  ]);
71
86
  export function isKnownTopLevelCommand(name) {
72
87
  return KNOWN_TOP_LEVEL_COMMANDS.has(name);
@@ -1,3 +1,20 @@
1
1
  import type { Command } from 'commander';
2
- /** Configure the public root surface shared by the live CLI and reference generator. */
2
+ /**
3
+ * Configure the public root surface shared by the live CLI and reference generator.
4
+ *
5
+ * Does NOT call `enablePositionalOptions()` — tried for RUSH-2687 (a subcommand
6
+ * option whose long name collides with an ancestor's is silently dropped at
7
+ * parse time) and reverted. Commander's `copyInheritedSettings()` copies
8
+ * `_enablePositionalOptions` onto every command created via `.command()` from
9
+ * that point on, so setting it here cascades to all ~552 registered commands,
10
+ * not just the root's own scan. That broke a real, load-bearing pattern used
11
+ * elsewhere in the tree: a parent command (e.g. `sessions`, which owns
12
+ * `--since`/`--json`/`--local`) whose LEAF subcommands declare none of their
13
+ * own options and read them back via `command.optsWithGlobals()` (e.g.
14
+ * `sessions backfill tools --since 7d --json --local`) — positional options
15
+ * makes the parent stop scanning at the first subcommand-shaped token, so it
16
+ * never sees the flags typed after it, and the leaf sees `unknown option`
17
+ * instead of a silent drop. RUSH-2687 was fixed per-surface in share.ts
18
+ * instead (renaming the colliding options), not with this global flag.
19
+ */
3
20
  export declare function configureRootCommand(program: Command, name: string, version: string): Command;
@@ -1,4 +1,21 @@
1
- /** Configure the public root surface shared by the live CLI and reference generator. */
1
+ /**
2
+ * Configure the public root surface shared by the live CLI and reference generator.
3
+ *
4
+ * Does NOT call `enablePositionalOptions()` — tried for RUSH-2687 (a subcommand
5
+ * option whose long name collides with an ancestor's is silently dropped at
6
+ * parse time) and reverted. Commander's `copyInheritedSettings()` copies
7
+ * `_enablePositionalOptions` onto every command created via `.command()` from
8
+ * that point on, so setting it here cascades to all ~552 registered commands,
9
+ * not just the root's own scan. That broke a real, load-bearing pattern used
10
+ * elsewhere in the tree: a parent command (e.g. `sessions`, which owns
11
+ * `--since`/`--json`/`--local`) whose LEAF subcommands declare none of their
12
+ * own options and read them back via `command.optsWithGlobals()` (e.g.
13
+ * `sessions backfill tools --since 7d --json --local`) — positional options
14
+ * makes the parent stop scanning at the first subcommand-shaped token, so it
15
+ * never sees the flags typed after it, and the leaf sees `unknown option`
16
+ * instead of a silent drop. RUSH-2687 was fixed per-surface in share.ts
17
+ * instead (renaming the colliding options), not with this global flag.
18
+ */
2
19
  export function configureRootCommand(program, name, version) {
3
20
  return program
4
21
  .name(name)
package/dist/lib/state.js CHANGED
@@ -955,6 +955,13 @@ function serializeCentral(central) {
955
955
  // config data-loss bug). A known device key lingering in the synced file is
956
956
  // still removed — it belongs in the per-machine file, not here.
957
957
  if (!(k in central) && KNOWN_META_KEYS.has(k)) {
958
+ // RUSH-2837: a partial writeMeta (reconstructed Meta missing `share`)
959
+ // deleted the share endpoint from agents.yaml and synced that deletion
960
+ // fleet-wide. `share` is restored only by setup/join — never drop it
961
+ // just because this write omitted the key. Explicit `share: null` still
962
+ // goes through doc.set above.
963
+ if (k === 'share')
964
+ continue;
958
965
  doc.delete(k);
959
966
  changed = true;
960
967
  }
@@ -7,9 +7,9 @@
7
7
  *
8
8
  * - reachability + headroom + load ← {@link probeFleetStats} (one parallel
9
9
  * SSH fan-out over the pool; the local box is measured directly).
10
- * - requested agent installed + signed in (when known) ← a one-shot readiness
11
- * probe per remote device ({@link buildReadyProbeCommand} → `agents view`),
12
- * the local box via {@link checkCliAvailable}/{@link checkCliSignedIn}.
10
+ * - requested agent installed + account eligibility (when known) ← a one-shot
11
+ * readiness probe per remote device ({@link buildReadyProbeCommand} →
12
+ * `agents view`), with the same candidate-readiness gate evaluated locally.
13
13
  *
14
14
  * The result is cached briefly per (pool, agent-or-any) so a `teams start` wave that
15
15
  * places N teammates probes the pool ONCE, not N times — the roster-count part
@@ -25,10 +25,11 @@ import { execFile } from 'child_process';
25
25
  import { probeFleetStats, headroom } from '../devices/health.js';
26
26
  import { loadDevicesSync } from '../devices/registry.js';
27
27
  import { buildSshInvocation, writeAskpassShim } from '../devices/connect.js';
28
- import { buildReadyProbeCommand, parseReadyProbe, viewAgentSignedIn, viewHasAgent, } from '../hosts/ready.js';
28
+ import { buildReadyProbeCommand, parseReadyProbe, viewAgentAccountEligibility, viewHasAgent, } from '../hosts/ready.js';
29
+ import { collectRunCandidates, isSignInRecoverable, readinessFromCandidate, } from '../accounting/rotate.js';
29
30
  import { localMachineId } from '../origin-machine.js';
30
31
  import { normalizeHost } from '../machine-id.js';
31
- import { checkCliAvailable, checkCliSignedIn } from './agents.js';
32
+ import { checkCliAvailable } from './agents.js';
32
33
  /** Per-remote readiness probe budget — matches the health probe's short window
33
34
  * (`agents view` on a warm box is sub-second; a wedged one degrades to unknown). */
34
35
  export const READY_PROBE_TIMEOUT_MS = 8_000;
@@ -61,19 +62,22 @@ function probeRemoteReadiness(device, agent) {
61
62
  ({ args, env } = buildSshInvocation(device, [cmd], shim, {}, { agentOnly: true }));
62
63
  }
63
64
  catch {
64
- return Promise.resolve({ installed: undefined, signedIn: undefined });
65
+ return Promise.resolve({ installed: undefined, signedIn: undefined, pickerEligible: undefined });
65
66
  }
66
67
  return new Promise((resolve) => {
67
68
  execFile('ssh', args, { encoding: 'utf-8', env: { ...process.env, ...env }, timeout: READY_PROBE_TIMEOUT_MS }, (err, stdout) => {
68
69
  if (err || !stdout)
69
- return resolve({ installed: undefined, signedIn: undefined });
70
+ return resolve({ installed: undefined, signedIn: undefined, pickerEligible: undefined });
70
71
  const probe = parseReadyProbe(stdout);
71
72
  if (!probe.reachable)
72
- return resolve({ installed: undefined, signedIn: undefined });
73
+ return resolve({ installed: undefined, signedIn: undefined, pickerEligible: undefined });
73
74
  if (!probe.version)
74
- return resolve({ installed: false, signedIn: false });
75
+ return resolve({ installed: false, signedIn: false, pickerEligible: false });
75
76
  const installed = viewHasAgent(probe.view, agent);
76
- resolve({ installed, signedIn: installed ? viewAgentSignedIn(probe.view, agent) : false });
77
+ const eligibility = installed
78
+ ? viewAgentAccountEligibility(probe.view, agent)
79
+ : { signedIn: false, pickerEligible: false };
80
+ resolve({ installed, ...eligibility });
77
81
  });
78
82
  });
79
83
  }
@@ -108,8 +112,18 @@ export async function probePoolSignals(pool, agent, opts = {}) {
108
112
  const isSelf = normalizeHost(d.name) === self;
109
113
  if (isSelf) {
110
114
  const inst = checkCliAvailable(agent)[0];
111
- const signedIn = inst ? await checkCliSignedIn(agent) : undefined;
112
- return [d.name, { installed: inst, signedIn }];
115
+ if (!inst)
116
+ return [d.name, { installed: false, signedIn: false, pickerEligible: false }];
117
+ const candidates = await collectRunCandidates(agent).catch(() => null);
118
+ if (!candidates) {
119
+ return [d.name, { installed: true, signedIn: undefined, pickerEligible: undefined }];
120
+ }
121
+ const readiness = candidates.map(readinessFromCandidate);
122
+ return [d.name, {
123
+ installed: true,
124
+ signedIn: readiness.some((candidate) => candidate.ready),
125
+ pickerEligible: readiness.some((candidate) => candidate.ready || isSignInRecoverable(candidate)),
126
+ }];
113
127
  }
114
128
  return [d.name, await probeRemoteReadiness(d, agent)];
115
129
  }))
@@ -128,6 +142,7 @@ export async function probePoolSignals(pool, agent, opts = {}) {
128
142
  memPercent: s?.memPercent,
129
143
  installed: inst?.installed,
130
144
  signedIn: inst?.signedIn,
145
+ pickerEligible: inst?.pickerEligible,
131
146
  });
132
147
  }
133
148
  cache.set(key, { at: now, signals });
@@ -18,8 +18,10 @@ export interface DevicePlacementSignal {
18
18
  memPercent?: number;
19
19
  /** Requested agent installed on the device. `false` → excluded (can't run). */
20
20
  installed?: boolean;
21
- /** Requested agent signed in (best-effort). Ranks a signed-in device first. */
21
+ /** Requested agent has at least one launch-ready account. Ranks a ready device first. */
22
22
  signedIn?: boolean;
23
+ /** Requested agent has at least one row the interactive account picker can launch. */
24
+ pickerEligible?: boolean;
23
25
  }
24
26
  /** Team fields the placement cascade reads (a subset of TeamMeta). */
25
27
  export interface PlacementTeam {
@@ -80,7 +80,7 @@ export interface BudgetConfig {
80
80
  */
81
81
  require_confirm_over?: number;
82
82
  }
83
- /** Preview features that users can opt into via `agents beta`. */
83
+ /** Preview features that users can opt into via `agents setup beta`. */
84
84
  export type BetaFeatureName = 'factory';
85
85
  /** Subset of chalk color names used for agent-specific terminal output. */
86
86
  export type ChalkColor = 'magenta' | 'green' | 'blue' | 'cyan' | 'yellowBright' | 'redBright' | 'whiteBright' | 'blueBright' | 'greenBright' | 'magentaBright' | 'cyanBright';
@@ -1,6 +1,7 @@
1
1
  import type { ConfiguredModelSource } from './models.js';
2
2
  import type { ProfileSummary } from './profiles.js';
3
3
  import type { AgentId } from './types.js';
4
+ import type { AuthVerdict } from './auth-health.js';
4
5
  export type SyncState = 'synced' | 'new' | 'modified' | 'deleted';
5
6
  export interface ViewJsonVersion {
6
7
  version: string;
@@ -8,6 +9,8 @@ export interface ViewJsonVersion {
8
9
  isolated: boolean;
9
10
  isIsolatedDefault: boolean;
10
11
  signedIn: boolean;
12
+ /** Live cached authentication verdict for this installed version. */
13
+ authVerdict: AuthVerdict | null;
11
14
  email: string | null;
12
15
  accountId?: string | null;
13
16
  organizationType?: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.43",
3
+ "version": "1.22.45",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,7 +25,6 @@
25
25
  "dist/**/*.d.ts",
26
26
  "dist/**/*.json",
27
27
  "dist/**/*.sh",
28
- "dist/bin/**",
29
28
  "dist/lib/secrets/Agents CLI.app/**",
30
29
  "dist/lib/menubar/MenubarHelper.app/**",
31
30
  "scripts/postinstall.js",
@@ -49,7 +48,7 @@
49
48
  "build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([ -d 'bin/Agents CLI.app' ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true) && ([ -d 'bin/MenubarHelper.app' ] && mkdir -p 'dist/lib/menubar' && cp -R 'bin/MenubarHelper.app' 'dist/lib/menubar/MenubarHelper.app' || true) && ([ -f 'bin/agents-macos' ] && mkdir -p dist/bin && cp -f 'bin/agents-macos' 'dist/bin/agents' && chmod 755 'dist/bin/agents' || true)",
50
49
  "build:bin": "scripts/build-bin.sh",
51
50
  "prepare": "npm run build",
52
- "prepack": "cp ../../README.md README.md && scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh && scripts/verify-cli-binary.sh",
51
+ "prepack": "cp ../../README.md README.md && scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh",
53
52
  "postinstall": "node scripts/postinstall.js",
54
53
  "dev": "tsx src/index.ts",
55
54
  "changelog": "bun scripts/gen-changelog.ts",
package/dist/bin/agents DELETED
Binary file
@@ -1,9 +0,0 @@
1
- import type { Command } from 'commander';
2
- /**
3
- * `agents auth login` — RFC 8628 device-code flow against the already-shipped
4
- * `/api/v1/auth/device/*` routes. If the live API rejects the request shape
5
- * (a route this spec missed, or the endpoint moved), this fails loud with the
6
- * `rush login` fallback rather than silently stubbing a fake session.
7
- */
8
- export declare function runAuthLogin(): Promise<void>;
9
- export declare function registerAuthCommand(program: Command): void;
@@ -1,108 +0,0 @@
1
- import chalk from 'chalk';
2
- import { setHelpSections } from '../lib/help.js';
3
- import { runOrDie } from '../lib/format.js';
4
- import { clearPrixSession, fetchWhoAmI, pollDeviceToken, PrixApiError, readPrixSession, resolvePrixToken, startDeviceAuthorization, writePrixSession, } from '../lib/prix-account.js';
5
- function sleep(ms) {
6
- return new Promise(resolve => setTimeout(resolve, ms));
7
- }
8
- /**
9
- * `agents auth login` — RFC 8628 device-code flow against the already-shipped
10
- * `/api/v1/auth/device/*` routes. If the live API rejects the request shape
11
- * (a route this spec missed, or the endpoint moved), this fails loud with the
12
- * `rush login` fallback rather than silently stubbing a fake session.
13
- */
14
- export async function runAuthLogin() {
15
- let authorization;
16
- try {
17
- authorization = await startDeviceAuthorization();
18
- }
19
- catch (err) {
20
- const message = err instanceof Error ? err.message : String(err);
21
- const existing = resolvePrixToken();
22
- if (existing?.source === 'rush') {
23
- console.log(chalk.yellow(`Device login is unavailable right now (${message}), but you're already signed in via 'rush login'.`));
24
- console.log("Run 'agents auth whoami' to confirm.");
25
- return;
26
- }
27
- throw new Error(`Device login is unavailable right now (${message}). Run 'rush login' instead, then 'agents auth whoami' to confirm.`);
28
- }
29
- console.log(`To sign in, open:\n\n ${chalk.cyan(authorization.verification_uri_complete)}\n`);
30
- console.log(chalk.gray(`(code: ${authorization.user_code})`));
31
- const deadline = Date.now() + authorization.expires_in * 1000;
32
- let interval = Math.max(authorization.interval, 1) * 1000;
33
- while (Date.now() < deadline) {
34
- await sleep(interval);
35
- const poll = await pollDeviceToken(authorization.device_code);
36
- if (poll.status === 'pending')
37
- continue;
38
- if (poll.status === 'slow_down') {
39
- interval += 1000;
40
- continue;
41
- }
42
- if (poll.status === 'expired')
43
- throw new Error('Login code expired. Run `agents auth login` again.');
44
- if (poll.status === 'denied')
45
- throw new Error('Login was denied.');
46
- writePrixSession({
47
- access_token: poll.access_token,
48
- refresh_token: poll.refresh_token,
49
- expires_at: poll.expires_in ? Date.now() + poll.expires_in * 1000 : undefined,
50
- email: poll.user.email,
51
- userId: poll.user.id,
52
- });
53
- console.log(chalk.green(`Signed in as ${poll.user.email}.`));
54
- return;
55
- }
56
- throw new Error('Login code expired. Run `agents auth login` again.');
57
- }
58
- async function runAuthWhoami(json) {
59
- const resolved = resolvePrixToken();
60
- if (!resolved)
61
- throw new Error("Not signed in. Run 'agents auth login' first.");
62
- let who;
63
- try {
64
- who = await fetchWhoAmI(resolved.token);
65
- }
66
- catch (err) {
67
- if (err instanceof PrixApiError && err.status === 401) {
68
- throw new Error(`Signed-in token is no longer valid (${resolved.source === 'agents' ? "run 'agents auth login'" : "run 'rush login'"} again).`);
69
- }
70
- throw err;
71
- }
72
- if (json) {
73
- console.log(JSON.stringify({ ...who, source: resolved.source }, null, 2));
74
- return;
75
- }
76
- console.log(`${chalk.bold(who.email)} ${chalk.gray(who.userId)}`);
77
- console.log(chalk.gray(`signed in via ${resolved.source === 'agents' ? "'agents auth login'" : "'rush login' (shared session)"}`));
78
- }
79
- function runAuthLogout() {
80
- const cleared = clearPrixSession();
81
- const rushRemains = resolvePrixToken()?.source === 'rush';
82
- if (!cleared && !readPrixSession()) {
83
- console.log(chalk.gray('Not signed in via `agents auth login` — nothing to clear.'));
84
- }
85
- else {
86
- console.log(chalk.green('Signed out.'));
87
- }
88
- if (rushRemains) {
89
- console.log(chalk.gray("You're still signed in via `rush login` — `agents auth whoami` will use that session. Run `rush logout` to clear it too."));
90
- }
91
- }
92
- export function registerAuthCommand(program) {
93
- const auth = program.command('auth').description('Sign in to your Rush account (shared by `agents org` and paid tiers)');
94
- auth.command('login').description('Sign in via the device-code flow').action(() => runOrDie(() => runAuthLogin()));
95
- auth.command('whoami').description('Show the signed-in account').option('--json', 'Machine-readable output')
96
- .action((o, command) => {
97
- const json = !!(o.json || command.optsWithGlobals().json);
98
- return runOrDie(() => runAuthWhoami(json), { json });
99
- });
100
- auth.command('logout').description('Clear the local `agents auth login` session').action(() => runOrDie(() => runAuthLogout()));
101
- setHelpSections(auth, {
102
- examples: `agents auth login
103
- agents auth whoami
104
- agents auth whoami --json
105
- agents auth logout`,
106
- notes: "`agents auth login` writes its own session, separate from \`rush login\`'s \`~/.rush/user.yaml\` — \`agents auth logout\` never signs you out of \`rush\`. If you're already signed in via \`rush login\`, \`agents auth whoami\`/\`agents org\` use that session automatically; no separate login is required.",
107
- });
108
- }
@@ -1,6 +0,0 @@
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;