@phnx-labs/agents-cli 1.22.45 → 1.22.47

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 (177) hide show
  1. package/CHANGELOG.md +303 -0
  2. package/LICENSE +102 -182
  3. package/README.md +54 -17
  4. package/dist/cli/command-registry.d.ts +2 -1
  5. package/dist/cli/command-registry.js +6 -2
  6. package/dist/commands/accounts.js +56 -12
  7. package/dist/commands/auth.d.ts +2 -0
  8. package/dist/commands/auth.js +229 -0
  9. package/dist/commands/browser-sessions-picker.js +9 -7
  10. package/dist/commands/browser.js +266 -15
  11. package/dist/commands/config.js +26 -4
  12. package/dist/commands/cost.d.ts +1 -1
  13. package/dist/commands/detach.d.ts +13 -0
  14. package/dist/commands/detach.js +11 -8
  15. package/dist/commands/exec.js +55 -38
  16. package/dist/commands/feed-watch.d.ts +2 -0
  17. package/dist/commands/feed-watch.js +28 -0
  18. package/dist/commands/feed.js +27 -0
  19. package/dist/commands/feedback.js +3 -19
  20. package/dist/commands/insights.d.ts +1 -1
  21. package/dist/commands/insights.js +2 -2
  22. package/dist/commands/lease.js +3 -3
  23. package/dist/commands/routines.js +1 -1
  24. package/dist/commands/sessions-picker-factory.d.ts +2 -1
  25. package/dist/commands/sessions-picker-factory.js +1 -1
  26. package/dist/commands/sessions-stop.d.ts +24 -0
  27. package/dist/commands/sessions-stop.js +66 -0
  28. package/dist/commands/sessions-trace.d.ts +82 -0
  29. package/dist/commands/sessions-trace.js +387 -0
  30. package/dist/commands/sessions.js +4 -0
  31. package/dist/commands/ssh.d.ts +15 -0
  32. package/dist/commands/ssh.js +194 -16
  33. package/dist/commands/teams.js +34 -2
  34. package/dist/commands/view.d.ts +2 -0
  35. package/dist/commands/view.js +19 -32
  36. package/dist/commands/watchdog.js +9 -14
  37. package/dist/lib/account-capabilities.d.ts +4 -0
  38. package/dist/lib/account-capabilities.js +13 -9
  39. package/dist/lib/account-registry.d.ts +2 -0
  40. package/dist/lib/account-registry.js +30 -2
  41. package/dist/lib/accounting/rotate.d.ts +9 -0
  42. package/dist/lib/accounting/rotate.js +12 -2
  43. package/dist/lib/accounting/usage.d.ts +92 -6
  44. package/dist/lib/accounting/usage.js +329 -75
  45. package/dist/lib/agent-modes.js +29 -1
  46. package/dist/lib/agent-spec/agents.d.ts +4 -3
  47. package/dist/lib/agent-spec/agents.js +20 -7
  48. package/dist/lib/analytics/mix-commands.d.ts +1 -1
  49. package/dist/lib/analytics/mix-commands.js +2 -2
  50. package/dist/lib/auth-health.d.ts +13 -3
  51. package/dist/lib/auth-health.js +50 -8
  52. package/dist/lib/browser/devices.d.ts +19 -0
  53. package/dist/lib/browser/devices.js +23 -0
  54. package/dist/lib/browser/ipc.d.ts +1 -1
  55. package/dist/lib/browser/ipc.js +33 -7
  56. package/dist/lib/browser/profiles.d.ts +106 -1
  57. package/dist/lib/browser/profiles.js +300 -17
  58. package/dist/lib/browser/remote-control.d.ts +26 -2
  59. package/dist/lib/browser/remote-control.js +33 -3
  60. package/dist/lib/browser/runtime-state.d.ts +9 -0
  61. package/dist/lib/browser/runtime-state.js +24 -8
  62. package/dist/lib/browser/service.d.ts +37 -0
  63. package/dist/lib/browser/service.js +78 -20
  64. package/dist/lib/browser/sessions-list.d.ts +1 -3
  65. package/dist/lib/browser/sessions-list.js +3 -16
  66. package/dist/lib/browser/types.d.ts +12 -1
  67. package/dist/lib/cli-resources.js +17 -15
  68. package/dist/lib/codex-policy.d.ts +11 -1
  69. package/dist/lib/codex-policy.js +20 -5
  70. package/dist/lib/config-keys.d.ts +4 -3
  71. package/dist/lib/config-keys.js +17 -7
  72. package/dist/lib/daemon/runner.js +15 -2
  73. package/dist/lib/daemon-ticks.js +2 -2
  74. package/dist/lib/device-config.d.ts +25 -8
  75. package/dist/lib/device-config.js +90 -17
  76. package/dist/lib/devices/config-migration.d.ts +7 -0
  77. package/dist/lib/devices/config-migration.js +67 -3
  78. package/dist/lib/devices/harness-inventory.js +20 -3
  79. package/dist/lib/devices/health.d.ts +27 -2
  80. package/dist/lib/devices/health.js +39 -9
  81. package/dist/lib/devices/interactive-host.d.ts +15 -0
  82. package/dist/lib/devices/interactive-host.js +53 -0
  83. package/dist/lib/devices/registry.d.ts +61 -4
  84. package/dist/lib/devices/registry.js +94 -38
  85. package/dist/lib/devices/stats-cache.d.ts +6 -0
  86. package/dist/lib/devices/stats-cache.js +10 -2
  87. package/dist/lib/exec.d.ts +25 -3
  88. package/dist/lib/exec.js +75 -22
  89. package/dist/lib/feed/answer.d.ts +29 -0
  90. package/dist/lib/feed/answer.js +145 -0
  91. package/dist/lib/feed/attention.d.ts +83 -0
  92. package/dist/lib/feed/attention.js +0 -0
  93. package/dist/lib/feed/feed.d.ts +109 -0
  94. package/dist/lib/feed/feed.js +154 -3
  95. package/dist/lib/feed/pr-status.d.ts +14 -0
  96. package/dist/lib/feed/pr-status.js +47 -0
  97. package/dist/lib/feed/watch.d.ts +59 -0
  98. package/dist/lib/feed/watch.js +180 -0
  99. package/dist/lib/fleet/capture.js +6 -0
  100. package/dist/lib/fleet/remote-login.js +8 -3
  101. package/dist/lib/fleet/types.d.ts +17 -0
  102. package/dist/lib/harness/adapter.d.ts +11 -0
  103. package/dist/lib/harness/adapters/claude.js +38 -24
  104. package/dist/lib/harness/adapters/codex.js +2 -4
  105. package/dist/lib/hooks/cache.js +8 -0
  106. package/dist/lib/hooks/match.d.ts +3 -0
  107. package/dist/lib/hooks/match.js +25 -0
  108. package/dist/lib/hosts/passthrough.js +16 -0
  109. package/dist/lib/hosts/registry.js +7 -0
  110. package/dist/lib/identity/client.d.ts +53 -0
  111. package/dist/lib/identity/client.js +106 -0
  112. package/dist/lib/identity/index.d.ts +115 -0
  113. package/dist/lib/identity/index.js +82 -0
  114. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  115. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  116. package/dist/lib/menubar/install-menubar.d.ts +13 -0
  117. package/dist/lib/menubar/install-menubar.js +26 -2
  118. package/dist/lib/open-url.d.ts +60 -2
  119. package/dist/lib/open-url.js +167 -12
  120. package/dist/lib/probe.d.ts +8 -0
  121. package/dist/lib/probe.js +105 -0
  122. package/dist/lib/refresh-coordinator.d.ts +1 -1
  123. package/dist/lib/refresh-coordinator.js +1 -1
  124. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  125. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
  126. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  127. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  128. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
  129. package/dist/lib/session/active.d.ts +67 -0
  130. package/dist/lib/session/active.js +69 -1
  131. package/dist/lib/session/db.js +4 -2
  132. package/dist/lib/session/discover.d.ts +2 -0
  133. package/dist/lib/session/discover.js +12 -6
  134. package/dist/lib/session/prompt.d.ts +35 -0
  135. package/dist/lib/session/prompt.js +56 -0
  136. package/dist/lib/session/remote/watch.d.ts +1 -1
  137. package/dist/lib/session/remote/watch.js +6 -1
  138. package/dist/lib/session/render.js +7 -3
  139. package/dist/lib/session/session-cache.js +5 -1
  140. package/dist/lib/session/trajectory-compare.d.ts +47 -0
  141. package/dist/lib/session/trajectory-compare.js +116 -0
  142. package/dist/lib/session/trajectory-html.d.ts +15 -0
  143. package/dist/lib/session/trajectory-html.js +750 -0
  144. package/dist/lib/session/trajectory-lineage.d.ts +92 -0
  145. package/dist/lib/session/trajectory-lineage.js +195 -0
  146. package/dist/lib/session/trajectory-text.d.ts +37 -0
  147. package/dist/lib/session/trajectory-text.js +277 -0
  148. package/dist/lib/session/trajectory.d.ts +102 -0
  149. package/dist/lib/session/trajectory.js +335 -0
  150. package/dist/lib/startup/command-registry.d.ts +5 -2
  151. package/dist/lib/startup/command-registry.js +8 -5
  152. package/dist/lib/state.d.ts +1 -1
  153. package/dist/lib/state.js +4 -2
  154. package/dist/lib/teams/agents.js +9 -0
  155. package/dist/lib/terminal/inject.d.ts +19 -0
  156. package/dist/lib/terminal/inject.js +19 -9
  157. package/dist/lib/tmux/session.d.ts +46 -13
  158. package/dist/lib/tmux/session.js +173 -27
  159. package/dist/lib/types.d.ts +16 -0
  160. package/dist/lib/usage-backoff.d.ts +2 -1
  161. package/dist/lib/usage-backoff.js +60 -19
  162. package/dist/lib/usage-refresh.d.ts +17 -3
  163. package/dist/lib/usage-refresh.js +57 -11
  164. package/dist/lib/view-types.d.ts +2 -2
  165. package/dist/lib/watchdog/log.d.ts +1 -1
  166. package/dist/lib/watchdog/log.js +1 -1
  167. package/dist/lib/watchdog/runner.d.ts +36 -45
  168. package/dist/lib/watchdog/runner.js +150 -119
  169. package/dist/lib/watchdog/service.d.ts +0 -1
  170. package/dist/lib/watchdog/service.js +0 -1
  171. package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
  172. package/dist/lib/watchdog/watchdog-agent.js +59 -0
  173. package/dist/lib/watchdog/watchdog.d.ts +11 -2
  174. package/dist/lib/watchdog/watchdog.js +37 -104
  175. package/package.json +2 -2
  176. package/dist/commands/usage.d.ts +0 -27
  177. package/dist/commands/usage.js +0 -96
@@ -1,53 +1,9 @@
1
- // Watchdog: pure logic for detecting stalled agent terminals and rendering
2
- // prompts to a headless decider instance that decides whether to nudge them.
3
- // Ported from Swarmify (extension/src/core/watchdog.ts) behavior verbatim.
4
- // Terminal/session delivery lives elsewhere; this module reads no files and
5
- // touches no host APIs so it can be unit-tested in isolation.
6
- const FORCE_REVIEW_STALL_MS = 15 * 60 * 1000;
7
- const BLOCKED_HINTS = [
8
- 'blocked',
9
- 'stuck',
10
- "can't",
11
- 'cannot',
12
- 'unable',
13
- 'failed',
14
- 'error',
15
- 'exception',
16
- 'traceback',
17
- 'timed out',
18
- 'timeout',
19
- 'rate limit',
20
- 'permission denied',
21
- ];
22
- const WAITING_HINTS = [
23
- 'waiting on user',
24
- 'awaiting user',
25
- 'askuserquestion',
26
- ];
27
- const COMPLETION_HINTS = [
28
- 'done',
29
- 'completed',
30
- 'all set',
31
- 'finished',
32
- ];
33
- const TOOL_CALL_HINTS = [
34
- '"type":"tool_use"',
35
- '"type":"tool_call"',
36
- '"type":"function_call"',
37
- ];
38
- const ASSISTANT_LINE_HINTS = [
39
- '"type":"assistant"',
40
- '"role":"assistant"',
41
- '"payload":{"type":"message"',
42
- ];
43
- const PROMISE_HINTS = [
44
- "i'll",
45
- 'i will',
46
- 'let me',
47
- 'going to',
48
- "next i'll",
49
- 'next i will',
50
- ];
1
+ // Watchdog: pure logic for detecting stalled agent terminals and rendering the
2
+ // prompt that the watchdog AGENT reads to decide idle-vs-unfinished and craft a
3
+ // nudge. There is deliberately no heuristic decider here (no regex over the tail
4
+ // guessing "done" vs "stuck") that judgment is the agent's job. This module
5
+ // only classifies idleness by timestamp and renders/parses the agent's I/O, so
6
+ // it reads no files and touches no host APIs and can be unit-tested in isolation.
51
7
  export function classifyTerminal(input) {
52
8
  if (input.optedOut)
53
9
  return { kind: 'opted_out' };
@@ -64,14 +20,16 @@ export function classifyTerminal(input) {
64
20
  }
65
21
  return { kind: 'stalled', stalledForMs: age };
66
22
  }
67
- export const WATCHDOG_SYSTEM_PROMPT = `You are the watchdog for AI coding agents running in terminals. Your one job is to get
68
- IDLE agents moving to completion: each agent has a goal and is expected to DRIVE TO
69
- COMPLETION end-to-end, but the terminals below have gone idle. Read each one's goal and
70
- WHY it stopped, then decide NUDGE (send a message that unsticks it and drives it to
71
- finish) or SKIP (it genuinely needs the human).
23
+ export const WATCHDOG_SYSTEM_PROMPT = `You are the watchdog for AI coding agents running in terminals. You are given the idle
24
+ sessions on this machine each with its originating TASK, how long it has been idle, and
25
+ the tail of its transcript. Your one job is to tell, for each one, whether it is
26
+ IDLE-BUT-UNFINISHED (it was given a task, went quiet, and has NOT finished or handed it
27
+ off) or IDLE-AND-DONE (it finished, or it genuinely needs the human). Idle-but-unfinished
28
+ is the dangerous state — the work is most likely to be silently abandoned — so those get a
29
+ NUDGE that drives them to finish. Everything else is a SKIP.
72
30
 
73
- Read the transcript before judging — an agent that already reached a decision needs "do
74
- it," not "decide."
31
+ Read each transcript before judging — an agent that already reached a decision needs "do
32
+ it," not "decide." Judge from the task + tail, not from keywords.
75
33
 
76
34
  NUDGE when the agent went idle and could keep going on its own:
77
35
  - It asked permission for an obvious or already-authorized next step
@@ -92,18 +50,19 @@ The nudge text MUST carry context, not shove:
92
50
  - Tell it to use best judgment and finish end-to-end WITHOUT asking again.
93
51
  - Imperative, 1-2 sentences, no emojis, under 240 characters.
94
52
 
95
- SKIP when the agent genuinely needs the human (these belong in the user's feed, not a
96
- nudge):
97
- - Credentials, auth, login, 2FA, or biometric.
98
- - An irreversible or outward-facing action that needs sign-off (force-push, delete
99
- prod data, publish/release, spend money, send an external message) UNLESS the House
100
- Rules below authorize it.
101
- - A real product or intent decision with genuine ambiguity (not a trivial default).
102
- - The task is actually complete.
103
- - You cannot tell what the agent is doing.
53
+ SKIP in two distinct cases mark which with "needsHuman":
54
+ - needsHuman=true — the agent is genuinely blocked on a human (these belong in the user's
55
+ feed): credentials, auth, login, 2FA, or biometric; an irreversible or outward-facing
56
+ action needing sign-off (force-push, delete prod data, publish/release, spend money,
57
+ send an external message) UNLESS the House Rules below authorize it; a real product or
58
+ intent decision with genuine ambiguity (not a trivial default); or you cannot tell what
59
+ the agent is doing.
60
+ - needsHuman=false — the task is actually complete (idle-and-done). Leave it alone; do NOT
61
+ poke a finished session.
104
62
 
105
- Respond with ONLY a JSON array (no prose, no code fence):
106
- [{"terminalId":"<id>","action":"nudge"|"skip","text":"<message or empty>","reason":"<brief>"}]`;
63
+ Respond with ONLY a JSON array (no prose, no code fence). Include "needsHuman" on every
64
+ skip:
65
+ [{"terminalId":"<id>","action":"nudge"|"skip","text":"<message or empty>","reason":"<brief>","needsHuman":true|false}]`;
107
66
  // User-editable playbook appended below the built-in prompt. The user maintains
108
67
  // the source at ~/.agents/playbooks/watchdog.md (read by the delivery layer);
109
68
  // this function is pure so it can be tested without filesystem access.
@@ -115,10 +74,14 @@ export function composePromptWithPlaybook(basePrompt, playbook) {
115
74
  }
116
75
  export function renderWatchdogPrompt(candidates, playbook = '') {
117
76
  const systemPrompt = composePromptWithPlaybook(WATCHDOG_SYSTEM_PROMPT, playbook);
118
- const parts = [systemPrompt, '', 'STALLED TERMINALS:', ''];
77
+ const parts = [systemPrompt, '', 'IDLE SESSIONS:', ''];
119
78
  for (const c of candidates) {
120
79
  const seconds = Math.round(c.stalledForMs / 1000);
121
80
  parts.push(`--- terminal ${c.terminalId} (${c.agentType}, idle ${seconds}s) ---`);
81
+ if (c.task)
82
+ parts.push(`task: ${c.task}`);
83
+ if (c.cwd)
84
+ parts.push(`cwd: ${c.cwd}`);
122
85
  parts.push('last JSONL lines:');
123
86
  for (const line of c.tailLines) {
124
87
  parts.push(line);
@@ -153,41 +116,11 @@ export function parseWatchdogResponse(stdout) {
153
116
  const reason = typeof obj.reason === 'string' ? obj.reason : '';
154
117
  if (!terminalId || !action)
155
118
  continue;
156
- decisions.push({ terminalId, action, text, reason });
119
+ // needsHuman only meaningful on a skip; a nudge is never "needs human".
120
+ const needsHuman = action === 'skip' && obj.needsHuman === true ? true
121
+ : action === 'skip' && obj.needsHuman === false ? false
122
+ : undefined;
123
+ decisions.push({ terminalId, action, text, reason, needsHuman });
157
124
  }
158
125
  return decisions;
159
126
  }
160
- export function isLikelyTrulyBlocked(candidate) {
161
- // With no tail to reason over, only a very long stall counts as blocked.
162
- if (candidate.tailLines.length === 0)
163
- return candidate.stalledForMs >= FORCE_REVIEW_STALL_MS;
164
- const lowerTail = candidate.tailLines.join('\n').toLowerCase();
165
- // Waiting-on-user and completion hints are checked BEFORE the 15m force-review
166
- // short-circuit — a long-idle OPEN QUESTION must defer (not be blindly
167
- // force-nudged) and a finished task is done. The old order tested stall age
168
- // first, so a 15m-60m idle session whose tail said "waiting on user" / "done"
169
- // was force-nudged anyway. Precedence fixed here (watchdog-brain-v2).
170
- if (WAITING_HINTS.some((hint) => lowerTail.includes(hint)))
171
- return false;
172
- if (COMPLETION_HINTS.some((hint) => lowerTail.includes(hint)))
173
- return false;
174
- if (candidate.stalledForMs >= FORCE_REVIEW_STALL_MS)
175
- return true;
176
- if (BLOCKED_HINTS.some((hint) => lowerTail.includes(hint)))
177
- return true;
178
- let sawToolAfter = false;
179
- for (let i = candidate.tailLines.length - 1; i >= 0; i--) {
180
- const line = candidate.tailLines[i].toLowerCase();
181
- if (TOOL_CALL_HINTS.some((hint) => line.includes(hint))) {
182
- sawToolAfter = true;
183
- continue;
184
- }
185
- if (!sawToolAfter) {
186
- const isAssistantLine = ASSISTANT_LINE_HINTS.some((hint) => line.includes(hint));
187
- const hasPromise = PROMISE_HINTS.some((hint) => line.includes(hint));
188
- if (isAssistantLine && hasPromise)
189
- return true;
190
- }
191
- }
192
- return false;
193
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.45",
3
+ "version": "1.22.47",
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",
@@ -79,7 +79,7 @@
79
79
  "multi-agent"
80
80
  ],
81
81
  "author": "Phoenix Labs",
82
- "license": "Apache-2.0",
82
+ "license": "FSL-1.1-Apache-2.0",
83
83
  "engines": {
84
84
  "node": ">=22.5.0",
85
85
  "npm": ">=9"
@@ -1,27 +0,0 @@
1
- /**
2
- * Usage command -- show rate-limit / quota status for each installed agent.
3
- *
4
- * Lists every installed agent with the best available usage snapshot:
5
- * - claude: live OAuth API call (cached for 5 minutes)
6
- * - codex: parsed from latest session log's rate_limits event
7
- * - kimi: live Kimi Code /usages API call (cached for 5 minutes)
8
- * - droid: live Factory billing/limits API call (cached for 5 minutes)
9
- * - grok: parsed from the latest local usage event
10
- * - cursor: live Cursor usage API call (cached for 5 minutes)
11
- * - others: marked as "not exposed by CLI"
12
- */
13
- import type { Command } from 'commander';
14
- import type { AgentId } from '../lib/types.js';
15
- import { getUsageInfoForIdentity } from '../lib/accounting/usage.js';
16
- /** One agent's usage snapshot — the unit the text and --json renderers share. */
17
- export interface AgentUsageRecord {
18
- agent: AgentId;
19
- /** Plain agent name (no ANSI) — safe to emit in --json; the text table colorizes it. */
20
- label: string;
21
- status: 'unsupported' | 'no-version' | 'not-signed-in' | 'ok';
22
- email?: string;
23
- usage?: Awaited<ReturnType<typeof getUsageInfoForIdentity>>;
24
- }
25
- export declare function registerUsageCommand(program: Command): void;
26
- /** Render one usage record as the human table section. */
27
- export declare function formatAgentUsage(rec: AgentUsageRecord): string;
@@ -1,96 +0,0 @@
1
- import { addHostOption } from '../lib/hosts/option.js';
2
- import chalk from 'chalk';
3
- import { ALL_AGENT_IDS, AGENTS, getAccountInfo, agentLabel, resolveAgentName, formatAgentError, } from '../lib/agents.js';
4
- import { listInstalledVersions, getGlobalDefault, getVersionHomePath } from '../lib/installations/versions.js';
5
- import { agentReportsUsage, formatUsageSection, getUsageInfoForIdentity } from '../lib/accounting/usage.js';
6
- export function registerUsageCommand(program) {
7
- addHostOption(program.command('usage [agent]'))
8
- .description('Show rate-limit / quota usage per agent')
9
- .option('--json', 'Emit machine-readable JSON (per-agent usage snapshot) instead of the table')
10
- .option('-r, --refresh', 'Collect fresh usage through the shared device coordinator before rendering')
11
- .addHelpText('after', `
12
- Examples:
13
- agents usage Show usage for all installed agents
14
- agents usage claude Show usage for Claude only
15
- agents usage codex Show usage for Codex only
16
- agents usage claude --refresh Explicitly refresh Claude once, device-wide
17
- agents usage --json Machine-readable snapshot for scripts
18
- `)
19
- .action(async (agentFilter, options) => {
20
- let filter;
21
- if (agentFilter) {
22
- const resolved = resolveAgentName(agentFilter);
23
- if (!resolved) {
24
- console.error(chalk.red(formatAgentError(agentFilter)));
25
- process.exit(1);
26
- }
27
- filter = resolved;
28
- }
29
- const targets = filter
30
- ? [filter]
31
- : ALL_AGENT_IDS.filter((id) => listInstalledVersions(id).length > 0);
32
- if (targets.length === 0) {
33
- if (options.json) {
34
- console.log('[]');
35
- return;
36
- }
37
- console.log(chalk.gray('No agents installed. Run `agents add <agent>` first.'));
38
- return;
39
- }
40
- const records = await Promise.all(targets.map((agentId) => collectAgentUsage(agentId, options.refresh === true)));
41
- if (options.json) {
42
- console.log(JSON.stringify(records, null, 2));
43
- return;
44
- }
45
- console.log(records.map(formatAgentUsage).filter(Boolean).join('\n\n'));
46
- });
47
- }
48
- /** Gather one agent's usage snapshot as structured data (shared by both renderers). */
49
- async function collectAgentUsage(agentId, forceRefresh = false) {
50
- // Plain name — color is applied only at text-render time (formatAgentUsage), so
51
- // `--json` never emits ANSI escapes in `label` (e.g. under FORCE_COLOR=1).
52
- const label = AGENTS[agentId].name;
53
- if (!agentReportsUsage(agentId)) {
54
- return { agent: agentId, label, status: 'unsupported' };
55
- }
56
- const versions = listInstalledVersions(agentId);
57
- const version = getGlobalDefault(agentId) || versions[0];
58
- if (!version) {
59
- return { agent: agentId, label, status: 'no-version' };
60
- }
61
- const home = getVersionHomePath(agentId, version);
62
- const info = await getAccountInfo(agentId, home);
63
- if (!info.usageKey && !info.accountKey) {
64
- return { agent: agentId, label, status: 'not-signed-in' };
65
- }
66
- const usage = await getUsageInfoForIdentity({ agentId, home, info, cliVersion: null }, forceRefresh ? { forceRefresh: true } : undefined);
67
- return { agent: agentId, label, status: 'ok', email: info.email ?? undefined, usage };
68
- }
69
- /** Render one usage record as the human table section. */
70
- export function formatAgentUsage(rec) {
71
- const cfg = AGENTS[rec.agent];
72
- // Colorize the heading here (not in the record) so the plain `label` stays
73
- // clean for --json while the text table keeps its per-agent color.
74
- const heading = agentLabel(rec.agent);
75
- switch (rec.status) {
76
- case 'unsupported':
77
- return [heading, ` ${chalk.dim(`${cfg.name} CLI does not publish usage data.`)}`].join('\n');
78
- case 'no-version':
79
- return [heading, ` ${chalk.dim('No version installed.')}`].join('\n');
80
- case 'not-signed-in':
81
- return [heading, ` ${chalk.dim('Not signed in.')}`].join('\n');
82
- case 'ok': {
83
- const lines = [heading];
84
- if (rec.email)
85
- lines.push(` ${chalk.dim(rec.email)}`);
86
- const section = formatUsageSection(rec.usage);
87
- if (section.length === 0) {
88
- lines.push(` ${chalk.dim('No usage data available right now.')}`);
89
- }
90
- else {
91
- lines.push(...section);
92
- }
93
- return lines.join('\n');
94
- }
95
- }
96
- }