@phnx-labs/agents-cli 1.22.47 → 1.22.49

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 (249) hide show
  1. package/CHANGELOG.md +401 -17
  2. package/README.md +66 -32
  3. package/dist/bootstrap.js +4 -2
  4. package/dist/cli/command-registry.d.ts +1 -0
  5. package/dist/cli/command-registry.js +2 -0
  6. package/dist/commands/accounts.d.ts +58 -0
  7. package/dist/commands/accounts.js +206 -32
  8. package/dist/commands/artifacts.js +7 -6
  9. package/dist/commands/attach.js +1 -1
  10. package/dist/commands/browser.js +501 -103
  11. package/dist/commands/config.d.ts +0 -1
  12. package/dist/commands/config.js +0 -20
  13. package/dist/commands/daemon.d.ts +21 -0
  14. package/dist/commands/daemon.js +147 -12
  15. package/dist/commands/exec.js +52 -15
  16. package/dist/commands/focus.d.ts +14 -0
  17. package/dist/commands/focus.js +48 -5
  18. package/dist/commands/go.d.ts +3 -0
  19. package/dist/commands/go.js +16 -3
  20. package/dist/commands/inspect.js +2 -2
  21. package/dist/commands/lease.d.ts +1 -1
  22. package/dist/commands/lease.js +4 -3
  23. package/dist/commands/mailboxes.js +4 -3
  24. package/dist/commands/menubar.js +11 -4
  25. package/dist/commands/projects.d.ts +16 -1
  26. package/dist/commands/projects.js +58 -64
  27. package/dist/commands/resume.js +1 -1
  28. package/dist/commands/route.js +4 -4
  29. package/dist/commands/routines.js +3 -2
  30. package/dist/commands/run-broadcast.d.ts +1 -1
  31. package/dist/commands/run-broadcast.js +1 -1
  32. package/dist/commands/sessions-picker.d.ts +22 -0
  33. package/dist/commands/sessions-picker.js +48 -9
  34. package/dist/commands/sessions-trace.js +1 -1
  35. package/dist/commands/sessions.js +1 -0
  36. package/dist/commands/setup-secrets.js +23 -0
  37. package/dist/commands/share.d.ts +20 -5
  38. package/dist/commands/share.js +152 -52
  39. package/dist/commands/ssh.js +54 -7
  40. package/dist/commands/traces.d.ts +2 -0
  41. package/dist/commands/traces.js +184 -0
  42. package/dist/commands/view.d.ts +1 -1
  43. package/dist/commands/view.js +17 -34
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.js +5 -0
  46. package/dist/lib/accounting/usage.d.ts +14 -3
  47. package/dist/lib/accounting/usage.js +64 -18
  48. package/dist/lib/agent-spec/agents.js +11 -9
  49. package/dist/lib/browser/drivers/ssh.d.ts +16 -2
  50. package/dist/lib/browser/drivers/ssh.js +36 -9
  51. package/dist/lib/browser/ipc.js +8 -3
  52. package/dist/lib/browser/profiles.d.ts +35 -101
  53. package/dist/lib/browser/profiles.js +130 -301
  54. package/dist/lib/browser/registry.d.ts +39 -0
  55. package/dist/lib/browser/registry.js +149 -0
  56. package/dist/lib/browser/remote-control.d.ts +8 -6
  57. package/dist/lib/browser/remote-control.js +8 -6
  58. package/dist/lib/browser/resolve-target.d.ts +68 -0
  59. package/dist/lib/browser/resolve-target.js +250 -0
  60. package/dist/lib/browser/runtime-state.d.ts +40 -16
  61. package/dist/lib/browser/runtime-state.js +92 -25
  62. package/dist/lib/browser/service.d.ts +28 -9
  63. package/dist/lib/browser/service.js +139 -62
  64. package/dist/lib/browser/task-index.d.ts +70 -0
  65. package/dist/lib/browser/task-index.js +180 -0
  66. package/dist/lib/byok-usage.js +2 -2
  67. package/dist/lib/claude-statusline.d.ts +29 -0
  68. package/dist/lib/claude-statusline.js +139 -0
  69. package/dist/lib/cloud/host.d.ts +1 -1
  70. package/dist/lib/cloud/host.js +1 -1
  71. package/dist/lib/cloud/rush.js +1 -1
  72. package/dist/lib/computer/computer-rpc.js +2 -2
  73. package/dist/lib/computer/download.d.ts +15 -15
  74. package/dist/lib/computer/download.js +35 -98
  75. package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
  76. package/dist/lib/computer/ssh-tunnel.js +12 -22
  77. package/dist/lib/config-keys.d.ts +2 -8
  78. package/dist/lib/config-keys.js +2 -13
  79. package/dist/lib/crabbox/cli.d.ts +1 -1
  80. package/dist/lib/crabbox/cli.js +2 -2
  81. package/dist/lib/crabbox/lease.d.ts +9 -0
  82. package/dist/lib/crabbox/lease.js +70 -15
  83. package/dist/lib/crabbox/runtimes.js +1 -1
  84. package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
  85. package/dist/lib/daemon/account-state-daemon-service.js +26 -0
  86. package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
  87. package/dist/lib/daemon/browser-ipc-service.js +46 -0
  88. package/dist/lib/daemon/daemon.d.ts +3 -0
  89. package/dist/lib/daemon/daemon.js +212 -444
  90. package/dist/lib/daemon/device-probe-service.d.ts +26 -0
  91. package/dist/lib/daemon/device-probe-service.js +47 -0
  92. package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
  93. package/dist/lib/daemon/keychain-reap-service.js +32 -0
  94. package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
  95. package/dist/lib/daemon/monitor-engine-service.js +29 -0
  96. package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
  97. package/dist/lib/daemon/secrets-broker-service.js +66 -0
  98. package/dist/lib/daemon/self-heal-service.d.ts +27 -0
  99. package/dist/lib/daemon/self-heal-service.js +48 -0
  100. package/dist/lib/daemon/service.d.ts +102 -0
  101. package/dist/lib/daemon/service.js +98 -0
  102. package/dist/lib/daemon/session-index-service.d.ts +22 -0
  103. package/dist/lib/daemon/session-index-service.js +40 -0
  104. package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
  105. package/dist/lib/daemon/state-dir-check-service.js +63 -0
  106. package/dist/lib/daemon/supervisor.d.ts +87 -0
  107. package/dist/lib/daemon/supervisor.js +298 -0
  108. package/dist/lib/daemon/watchdog-service.d.ts +19 -0
  109. package/dist/lib/daemon/watchdog-service.js +40 -0
  110. package/dist/lib/daemon-health.d.ts +15 -0
  111. package/dist/lib/daemon-health.js +29 -5
  112. package/dist/lib/daemon-services.d.ts +8 -1
  113. package/dist/lib/daemon-services.js +55 -0
  114. package/dist/lib/daemon-ticks.d.ts +13 -2
  115. package/dist/lib/daemon-ticks.js +20 -18
  116. package/dist/lib/device-config.d.ts +0 -2
  117. package/dist/lib/device-config.js +1 -23
  118. package/dist/lib/devices/registry.d.ts +1 -1
  119. package/dist/lib/devices/registry.js +1 -1
  120. package/dist/lib/devices/stats-cache.d.ts +20 -0
  121. package/dist/lib/devices/stats-cache.js +49 -5
  122. package/dist/lib/devices/worker-pick.d.ts +48 -0
  123. package/dist/lib/devices/worker-pick.js +113 -0
  124. package/dist/lib/exec.d.ts +67 -13
  125. package/dist/lib/exec.js +85 -29
  126. package/dist/lib/harness/adapters/cursor.js +8 -11
  127. package/dist/lib/helper-download.d.ts +108 -0
  128. package/dist/lib/helper-download.js +194 -0
  129. package/dist/lib/helper-versions.d.ts +52 -0
  130. package/dist/lib/helper-versions.js +58 -0
  131. package/dist/lib/hooks/cache.js +11 -0
  132. package/dist/lib/hooks/install.js +2 -2
  133. package/dist/lib/hosts/credentials.d.ts +1 -1
  134. package/dist/lib/hosts/credentials.js +1 -1
  135. package/dist/lib/hosts/dispatch.d.ts +7 -1
  136. package/dist/lib/hosts/dispatch.js +24 -5
  137. package/dist/lib/hosts/passthrough.d.ts +8 -15
  138. package/dist/lib/hosts/passthrough.js +19 -16
  139. package/dist/lib/hosts/providers/devices.d.ts +1 -1
  140. package/dist/lib/hosts/providers/devices.js +1 -1
  141. package/dist/lib/hosts/reconnect.d.ts +179 -21
  142. package/dist/lib/hosts/reconnect.js +266 -45
  143. package/dist/lib/installations/migrate.d.ts +4 -5
  144. package/dist/lib/installations/migrate.js +6 -7
  145. package/dist/lib/installations/shims.d.ts +1 -1
  146. package/dist/lib/installations/shims.js +23 -13
  147. package/dist/lib/installations/versions.js +25 -2
  148. package/dist/lib/linear-autoclose.d.ts +1 -1
  149. package/dist/lib/linear-autoclose.js +1 -1
  150. package/dist/lib/loop.d.ts +2 -2
  151. package/dist/lib/loop.js +1 -1
  152. package/dist/lib/menubar/download-menubar.d.ts +47 -0
  153. package/dist/lib/menubar/download-menubar.js +60 -0
  154. package/dist/lib/menubar/install-menubar.d.ts +31 -7
  155. package/dist/lib/menubar/install-menubar.js +79 -21
  156. package/dist/lib/menubar/notify-desktop.d.ts +3 -3
  157. package/dist/lib/menubar/notify-desktop.js +6 -6
  158. package/dist/lib/model-tiers.d.ts +1 -1
  159. package/dist/lib/project-probe.d.ts +1 -1
  160. package/dist/lib/project-probe.js +1 -1
  161. package/dist/lib/project-pull.d.ts +1 -1
  162. package/dist/lib/project-pull.js +2 -2
  163. package/dist/lib/projects.d.ts +1 -1
  164. package/dist/lib/projects.js +1 -1
  165. package/dist/lib/sandbox.js +6 -7
  166. package/dist/lib/secrets/download-keychain.d.ts +47 -0
  167. package/dist/lib/secrets/download-keychain.js +70 -0
  168. package/dist/lib/secrets/install-helper.d.ts +20 -1
  169. package/dist/lib/secrets/install-helper.js +44 -3
  170. package/dist/lib/session/active.js +17 -1
  171. package/dist/lib/session/db.d.ts +11 -1
  172. package/dist/lib/session/db.js +138 -6
  173. package/dist/lib/session/discover.d.ts +21 -1
  174. package/dist/lib/session/discover.js +33 -5
  175. package/dist/lib/session/highlights.d.ts +49 -1
  176. package/dist/lib/session/highlights.js +95 -0
  177. package/dist/lib/session/host-link.d.ts +16 -2
  178. package/dist/lib/session/host-link.js +7 -1
  179. package/dist/lib/session/parse.js +33 -1
  180. package/dist/lib/session/remote/remote-list.d.ts +9 -0
  181. package/dist/lib/session/remote/remote-list.js +19 -2
  182. package/dist/lib/session/remote/watch.js +11 -2
  183. package/dist/lib/session/session-cache.d.ts +53 -0
  184. package/dist/lib/session/session-cache.js +88 -0
  185. package/dist/lib/session/state.js +1 -1
  186. package/dist/lib/session/tool-calls.d.ts +2 -0
  187. package/dist/lib/session/tool-calls.js +1 -1
  188. package/dist/lib/session/trajectory-html.d.ts +5 -1
  189. package/dist/lib/session/trajectory-html.js +42 -22
  190. package/dist/lib/session/trajectory-text.js +9 -2
  191. package/dist/lib/session/trajectory.js +53 -4
  192. package/dist/lib/session/types.d.ts +15 -0
  193. package/dist/lib/sha256-asset.d.ts +26 -0
  194. package/dist/lib/sha256-asset.js +41 -0
  195. package/dist/lib/share/backend.d.ts +95 -0
  196. package/dist/lib/share/backend.js +139 -0
  197. package/dist/lib/share/delete.d.ts +6 -2
  198. package/dist/lib/share/delete.js +8 -9
  199. package/dist/lib/share/html.d.ts +22 -0
  200. package/dist/lib/share/html.js +88 -0
  201. package/dist/lib/share/provision.d.ts +18 -4
  202. package/dist/lib/share/provision.js +38 -4
  203. package/dist/lib/share/publish.d.ts +39 -15
  204. package/dist/lib/share/publish.js +55 -26
  205. package/dist/lib/share/worker-template.js +171 -11
  206. package/dist/lib/sqlite.js +16 -3
  207. package/dist/lib/ssh-exec.d.ts +65 -0
  208. package/dist/lib/ssh-exec.js +120 -4
  209. package/dist/lib/startup/command-registry.js +1 -1
  210. package/dist/lib/startup/dev-build.js +3 -3
  211. package/dist/lib/state.d.ts +1 -1
  212. package/dist/lib/state.js +7 -3
  213. package/dist/lib/subagents-registry.js +1 -1
  214. package/dist/lib/terminal/index.d.ts +1 -1
  215. package/dist/lib/terminal/types.d.ts +1 -1
  216. package/dist/lib/terminal/types.js +1 -1
  217. package/dist/lib/tmux/orphan-reap.js +1 -1
  218. package/dist/lib/tmux/session.js +31 -0
  219. package/dist/lib/traces/backend.d.ts +29 -0
  220. package/dist/lib/traces/backend.js +46 -0
  221. package/dist/lib/traces/classify.d.ts +52 -0
  222. package/dist/lib/traces/classify.js +72 -0
  223. package/dist/lib/traces/config.d.ts +10 -0
  224. package/dist/lib/traces/config.js +2 -0
  225. package/dist/lib/traces/provision.d.ts +19 -0
  226. package/dist/lib/traces/provision.js +30 -0
  227. package/dist/lib/traces/sync.d.ts +150 -0
  228. package/dist/lib/traces/sync.js +447 -0
  229. package/dist/lib/traces/worker-template.d.ts +4 -0
  230. package/dist/lib/traces/worker-template.js +159 -0
  231. package/dist/lib/types.d.ts +18 -19
  232. package/dist/lib/types.js +14 -0
  233. package/dist/lib/usage-refresh.d.ts +1 -1
  234. package/dist/lib/usage-refresh.js +11 -8
  235. package/dist/lib/workflows.d.ts +1 -1
  236. package/package.json +4 -6
  237. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  238. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
  239. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  240. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  241. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
  242. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  243. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -26
  244. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  245. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  246. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
  247. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  248. package/dist/lib/usage-fleet.d.ts +0 -32
  249. package/dist/lib/usage-fleet.js +0 -125
@@ -0,0 +1,70 @@
1
+ export interface TaskBinding {
2
+ device: string;
3
+ profile?: string;
4
+ url?: string;
5
+ sessionId?: string;
6
+ launchId?: string;
7
+ createdAt: number;
8
+ }
9
+ export type TaskIndex = Record<string, TaskBinding>;
10
+ export declare const REJECT_DEVICE_MESSAGE: string;
11
+ export type TaskRoute = {
12
+ kind: 'proceed';
13
+ task?: string;
14
+ device: string;
15
+ } | {
16
+ kind: 'unknown';
17
+ task: string;
18
+ message: string;
19
+ } | {
20
+ kind: 'ambiguous';
21
+ message: string;
22
+ } | {
23
+ kind: 'reject-device';
24
+ message: string;
25
+ };
26
+ export declare function taskIndexPath(): string;
27
+ export declare function readTaskIndex(): TaskIndex;
28
+ export declare function getTaskBinding(name: string): TaskBinding | undefined;
29
+ export declare function listTaskBindings(): Array<{
30
+ name: string;
31
+ } & TaskBinding>;
32
+ export declare function bindTask(name: string, binding: TaskBinding): void;
33
+ export declare function updateTaskBinding(name: string, patch: Partial<TaskBinding>): void;
34
+ export declare function unbindTask(name: string): void;
35
+ /** Drop every binding recorded under this profile (after `stop --profile` / gc). */
36
+ export declare function unbindTasksForProfile(profile: string): void;
37
+ export declare function tasksForCaller(sessionId?: string, launchId?: string): Array<{
38
+ name: string;
39
+ } & TaskBinding>;
40
+ export declare function formatOpenTaskList(entries: Array<{
41
+ name: string;
42
+ } & TaskBinding>, now?: number): string;
43
+ export declare function unknownTaskMessage(name: string, entries?: ({
44
+ name: string;
45
+ } & TaskBinding)[]): string;
46
+ export declare function ambiguousTasksMessage(entries: Array<{
47
+ name: string;
48
+ } & TaskBinding>): string;
49
+ /**
50
+ * Decide where a page verb should run.
51
+ *
52
+ * `--device` on a later verb is always rejected (bind it at start).
53
+ * A named task that is not in the local index fails loud with the open-task
54
+ * list. Two or more tasks for this caller, with no `--task`, fail the same way.
55
+ * Zero matches with no `--task` proceeds locally — the daemon still resolves
56
+ * from caller identity for the first implicit create.
57
+ */
58
+ export declare function resolveTaskRoute(opts: {
59
+ task?: string;
60
+ device?: string;
61
+ sessionId?: string;
62
+ launchId?: string;
63
+ self?: string;
64
+ }): TaskRoute;
65
+ /**
66
+ * The daemon sandboxes screenshot writes to the browser runtime dir, so `-o`
67
+ * outside that dir is ignored there. The CLI process honors `-o` by copying
68
+ * the captured file to the requested path. Same path → no copy.
69
+ */
70
+ export declare function honorScreenshotOutput(requested: string | undefined, daemonPath: string): string;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Local task → device index.
3
+ *
4
+ * `tasks.json` is per-profile and lives in that profile's cache dir on the
5
+ * machine running the browser daemon, so a later verb issued from a different
6
+ * box cannot see where task `post` lives. The machine that STARTS a task
7
+ * records the binding here — a small JSON file in THIS box's browser cache.
8
+ * No fleet state, no sync: the agent that opens a task is the agent that
9
+ * drives it.
10
+ *
11
+ * Kind is not stored. A missing name fails loud and lists the open tasks;
12
+ * it never guesses, never opens a new browser, never falls back to a default
13
+ * profile.
14
+ */
15
+ import * as fs from 'node:fs';
16
+ import * as path from 'node:path';
17
+ import { getBrowserRuntimeDir } from '../state.js';
18
+ import { machineId } from '../machine-id.js';
19
+ export const REJECT_DEVICE_MESSAGE = '--device is only valid on `agents browser start`.\n' +
20
+ 'The task is bound to a device at start; later verbs resolve it from --task.\n' +
21
+ 'Next: agents browser start --task <name> --device <device>';
22
+ export function taskIndexPath() {
23
+ return path.join(getBrowserRuntimeDir(), 'task-index.json');
24
+ }
25
+ export function readTaskIndex() {
26
+ const file = taskIndexPath();
27
+ if (!fs.existsSync(file))
28
+ return {};
29
+ let parsed;
30
+ try {
31
+ parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
32
+ }
33
+ catch (error) {
34
+ throw new Error(`Cannot read browser task index at ${file}: ${error instanceof Error ? error.message : String(error)}`);
35
+ }
36
+ if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
37
+ throw new Error(`Cannot read browser task index at ${file}: document root must be a map.`);
38
+ }
39
+ return parsed;
40
+ }
41
+ function writeTaskIndex(index) {
42
+ const file = taskIndexPath();
43
+ fs.mkdirSync(path.dirname(file), { recursive: true });
44
+ const tmp = `${file}.${process.pid}.tmp`;
45
+ fs.writeFileSync(tmp, `${JSON.stringify(index, null, 2)}\n`);
46
+ fs.renameSync(tmp, file);
47
+ }
48
+ export function getTaskBinding(name) {
49
+ return readTaskIndex()[name];
50
+ }
51
+ export function listTaskBindings() {
52
+ return Object.entries(readTaskIndex())
53
+ .map(([name, binding]) => ({ name, ...binding }))
54
+ .sort((a, b) => a.createdAt - b.createdAt);
55
+ }
56
+ export function bindTask(name, binding) {
57
+ if (!name) {
58
+ throw new Error('Cannot bind a browser task with an empty name.');
59
+ }
60
+ if (!binding.device) {
61
+ throw new Error(`Cannot bind browser task "${name}" without a device.`);
62
+ }
63
+ const index = readTaskIndex();
64
+ index[name] = { ...binding };
65
+ writeTaskIndex(index);
66
+ }
67
+ export function updateTaskBinding(name, patch) {
68
+ const index = readTaskIndex();
69
+ const existing = index[name];
70
+ if (!existing)
71
+ return;
72
+ index[name] = { ...existing, ...patch };
73
+ writeTaskIndex(index);
74
+ }
75
+ export function unbindTask(name) {
76
+ const index = readTaskIndex();
77
+ if (!(name in index))
78
+ return;
79
+ delete index[name];
80
+ writeTaskIndex(index);
81
+ }
82
+ /** Drop every binding recorded under this profile (after `stop --profile` / gc). */
83
+ export function unbindTasksForProfile(profile) {
84
+ const index = readTaskIndex();
85
+ let changed = false;
86
+ for (const [name, binding] of Object.entries(index)) {
87
+ if (binding.profile === profile) {
88
+ delete index[name];
89
+ changed = true;
90
+ }
91
+ }
92
+ if (changed)
93
+ writeTaskIndex(index);
94
+ }
95
+ export function tasksForCaller(sessionId, launchId) {
96
+ return listTaskBindings().filter((entry) => {
97
+ if (sessionId && entry.sessionId === sessionId)
98
+ return true;
99
+ if (launchId && entry.launchId === launchId)
100
+ return true;
101
+ return false;
102
+ });
103
+ }
104
+ function formatAge(createdAt, now = Date.now()) {
105
+ const ageMs = Math.max(0, now - createdAt);
106
+ if (ageMs < 60_000)
107
+ return `${Math.round(ageMs / 1000)}s`;
108
+ return `${Math.round(ageMs / 60_000)}m`;
109
+ }
110
+ export function formatOpenTaskList(entries, now = Date.now()) {
111
+ if (entries.length === 0)
112
+ return ' (no open tasks)';
113
+ return entries
114
+ .map((entry) => {
115
+ const url = entry.url && entry.url.length > 0 ? entry.url : '-';
116
+ return ` ${entry.name} url=${url} device=${entry.device} age=${formatAge(entry.createdAt, now)}`;
117
+ })
118
+ .join('\n');
119
+ }
120
+ export function unknownTaskMessage(name, entries = listTaskBindings()) {
121
+ return [
122
+ `Unknown browser task "${name}".`,
123
+ 'Open tasks:',
124
+ formatOpenTaskList(entries),
125
+ 'Next: agents browser start --task <name> [--device <device>]',
126
+ ].join('\n');
127
+ }
128
+ export function ambiguousTasksMessage(entries) {
129
+ return [
130
+ 'Multiple browser tasks for this session — pass --task <name>:',
131
+ formatOpenTaskList(entries),
132
+ 'Next: agents browser status',
133
+ ].join('\n');
134
+ }
135
+ /**
136
+ * Decide where a page verb should run.
137
+ *
138
+ * `--device` on a later verb is always rejected (bind it at start).
139
+ * A named task that is not in the local index fails loud with the open-task
140
+ * list. Two or more tasks for this caller, with no `--task`, fail the same way.
141
+ * Zero matches with no `--task` proceeds locally — the daemon still resolves
142
+ * from caller identity for the first implicit create.
143
+ */
144
+ export function resolveTaskRoute(opts) {
145
+ if (opts.device) {
146
+ return { kind: 'reject-device', message: REJECT_DEVICE_MESSAGE };
147
+ }
148
+ const self = opts.self ?? machineId();
149
+ if (opts.task) {
150
+ const binding = getTaskBinding(opts.task);
151
+ if (!binding) {
152
+ return { kind: 'unknown', task: opts.task, message: unknownTaskMessage(opts.task) };
153
+ }
154
+ return { kind: 'proceed', task: opts.task, device: binding.device };
155
+ }
156
+ const matches = tasksForCaller(opts.sessionId, opts.launchId);
157
+ if (matches.length > 1) {
158
+ return { kind: 'ambiguous', message: ambiguousTasksMessage(matches) };
159
+ }
160
+ if (matches.length === 1) {
161
+ const only = matches[0];
162
+ return { kind: 'proceed', task: only.name, device: only.device };
163
+ }
164
+ return { kind: 'proceed', device: self };
165
+ }
166
+ /**
167
+ * The daemon sandboxes screenshot writes to the browser runtime dir, so `-o`
168
+ * outside that dir is ignored there. The CLI process honors `-o` by copying
169
+ * the captured file to the requested path. Same path → no copy.
170
+ */
171
+ export function honorScreenshotOutput(requested, daemonPath) {
172
+ if (!requested)
173
+ return daemonPath;
174
+ const dest = path.resolve(requested);
175
+ if (path.resolve(daemonPath) === dest)
176
+ return dest;
177
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
178
+ fs.copyFileSync(daemonPath, dest);
179
+ return dest;
180
+ }
@@ -150,12 +150,12 @@ export function renderByokBar(result) {
150
150
  return '';
151
151
  const { budget } = result;
152
152
  if (budget.limitUsd === null) {
153
- const bar = renderBar(0, 10, 0);
153
+ const bar = renderBar(0, 10);
154
154
  const credit = budget.remainingUsd !== null ? `$${budget.remainingUsd.toFixed(2)} credit, ` : '';
155
155
  return `$: ${bar} ${credit}$${budget.usedUsd.toFixed(2)} used (no spending limit)`;
156
156
  }
157
157
  const pct = budget.usedPercent ?? 0;
158
- const bar = renderBar(pct, 10, pct > 0 ? 1 : 0);
158
+ const bar = renderBar(pct, 10);
159
159
  const color = getUsageColor(pct);
160
160
  const remaining = budget.remainingUsd !== null ? `$${budget.remainingUsd.toFixed(2)}` : '?';
161
161
  const limit = `$${budget.limitUsd.toFixed(2)}`;
@@ -0,0 +1,29 @@
1
+ export declare const CLAUDE_STATUSLINE_COMMAND = "agents __claude-statusline";
2
+ interface ClaudeStatusLinePayload {
3
+ cwd?: string;
4
+ workspace?: {
5
+ current_dir?: string;
6
+ };
7
+ model?: {
8
+ display_name?: string;
9
+ id?: string;
10
+ };
11
+ rate_limits?: {
12
+ five_hour?: {
13
+ used_percentage?: number;
14
+ resets_at?: number;
15
+ };
16
+ seven_day?: {
17
+ used_percentage?: number;
18
+ resets_at?: number;
19
+ };
20
+ };
21
+ }
22
+ export declare function ingestClaudeStatusLineUsage(payload: ClaudeStatusLinePayload, versionHome: string): boolean;
23
+ export declare function renderClaudeStatusLine(payload: ClaudeStatusLinePayload, host?: string, delegated?: string): string;
24
+ export declare function runClaudeStatusLine(): Promise<number>;
25
+ export declare function installClaudeStatusLine(versionHome: string): {
26
+ changed: boolean;
27
+ error?: string;
28
+ };
29
+ export {};
@@ -0,0 +1,139 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import { spawnSync } from 'child_process';
5
+ import { readClaudeHomeConfig } from './agent-spec/agents.js';
6
+ import { atomicWriteFileSync } from './fs-atomic.js';
7
+ import { mergeClaudeUsageCacheWindows } from './accounting/usage.js';
8
+ export const CLAUDE_STATUSLINE_COMMAND = 'agents __claude-statusline';
9
+ const DELEGATE_FILE = path.join('.agents', 'claude-statusline-delegate');
10
+ function isRecord(value) {
11
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
12
+ }
13
+ function versionHomeFromEnv(env) {
14
+ const configDir = env.CLAUDE_CONFIG_DIR?.trim();
15
+ return configDir ? path.dirname(configDir) : null;
16
+ }
17
+ function windowFromNative(key, value) {
18
+ if (!value || !Number.isFinite(value.used_percentage))
19
+ return null;
20
+ const resetSeconds = value.resets_at;
21
+ return {
22
+ key,
23
+ label: key === 'session' ? 'Current session' : 'Current week',
24
+ shortLabel: key === 'session' ? 'S' : 'W',
25
+ usedPercent: Math.max(0, Math.min(100, value.used_percentage)),
26
+ resetsAt: Number.isFinite(resetSeconds) ? new Date(resetSeconds * 1000) : null,
27
+ windowMinutes: key === 'session' ? 300 : 10_080,
28
+ };
29
+ }
30
+ export function ingestClaudeStatusLineUsage(payload, versionHome) {
31
+ const identity = readClaudeHomeConfig(versionHome)?.identity;
32
+ if (!identity?.usageKey || !payload.rate_limits)
33
+ return false;
34
+ const windows = [
35
+ windowFromNative('session', payload.rate_limits.five_hour),
36
+ windowFromNative('week', payload.rate_limits.seven_day),
37
+ ].filter((value) => value !== null);
38
+ if (windows.length === 0)
39
+ return false;
40
+ mergeClaudeUsageCacheWindows(identity.usageKey, {
41
+ source: 'live',
42
+ sourceLabel: 'Claude response rate limits',
43
+ capturedAt: new Date(),
44
+ windows,
45
+ });
46
+ return true;
47
+ }
48
+ function delegatePath(versionHome) {
49
+ return path.join(versionHome, DELEGATE_FILE);
50
+ }
51
+ function renderDelegate(payload, versionHome) {
52
+ let command = '';
53
+ try {
54
+ command = fs.readFileSync(delegatePath(versionHome), 'utf8').trim();
55
+ }
56
+ catch {
57
+ return '';
58
+ }
59
+ if (!command || command === CLAUDE_STATUSLINE_COMMAND)
60
+ return '';
61
+ const result = spawnSync(command, {
62
+ shell: true,
63
+ input: payload,
64
+ encoding: 'utf8',
65
+ env: process.env,
66
+ });
67
+ return result.status === 0 ? result.stdout.trim() : '';
68
+ }
69
+ export function renderClaudeStatusLine(payload, host = os.hostname().split('.')[0] || os.hostname(), delegated = '') {
70
+ const model = payload.model?.display_name?.trim() || payload.model?.id?.trim() || 'model pending';
71
+ const parts = [host, model];
72
+ if (delegated)
73
+ parts.push(delegated);
74
+ const fiveHour = payload.rate_limits?.five_hour?.used_percentage;
75
+ const sevenDay = payload.rate_limits?.seven_day?.used_percentage;
76
+ if (Number.isFinite(fiveHour))
77
+ parts.push(`5h ${Math.round(fiveHour)}%`);
78
+ if (Number.isFinite(sevenDay))
79
+ parts.push(`7d ${Math.round(sevenDay)}%`);
80
+ return parts.join(' · ');
81
+ }
82
+ export async function runClaudeStatusLine() {
83
+ const raw = await new Promise((resolve, reject) => {
84
+ let input = '';
85
+ process.stdin.setEncoding('utf8');
86
+ process.stdin.on('data', (chunk) => { input += chunk; });
87
+ process.stdin.on('end', () => resolve(input));
88
+ process.stdin.on('error', reject);
89
+ });
90
+ let payload;
91
+ try {
92
+ const parsed = JSON.parse(raw);
93
+ payload = isRecord(parsed) ? parsed : {};
94
+ }
95
+ catch {
96
+ payload = {};
97
+ }
98
+ const versionHome = versionHomeFromEnv(process.env);
99
+ if (versionHome)
100
+ ingestClaudeStatusLineUsage(payload, versionHome);
101
+ process.stdout.write(renderClaudeStatusLine(payload, undefined, versionHome ? renderDelegate(raw, versionHome) : ''));
102
+ return 0;
103
+ }
104
+ export function installClaudeStatusLine(versionHome) {
105
+ const settingsPath = path.join(versionHome, '.claude', 'settings.json');
106
+ try {
107
+ let settings = {};
108
+ if (fs.existsSync(settingsPath)) {
109
+ const parsed = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
110
+ if (!isRecord(parsed))
111
+ return { changed: false, error: 'settings.json is not an object' };
112
+ settings = parsed;
113
+ }
114
+ const priorStatusLine = isRecord(settings.statusLine) ? settings.statusLine : {};
115
+ const existing = typeof priorStatusLine.command === 'string'
116
+ ? priorStatusLine.command.trim()
117
+ : '';
118
+ if (existing === CLAUDE_STATUSLINE_COMMAND)
119
+ return { changed: false };
120
+ if (existing) {
121
+ fs.mkdirSync(path.dirname(delegatePath(versionHome)), { recursive: true });
122
+ atomicWriteFileSync(delegatePath(versionHome), `${existing}\n`);
123
+ }
124
+ else {
125
+ fs.rmSync(delegatePath(versionHome), { force: true });
126
+ }
127
+ settings.statusLine = {
128
+ ...priorStatusLine,
129
+ type: 'command',
130
+ command: CLAUDE_STATUSLINE_COMMAND,
131
+ };
132
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
133
+ atomicWriteFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
134
+ return { changed: true };
135
+ }
136
+ catch (error) {
137
+ return { changed: false, error: error instanceof Error ? error.message : String(error) };
138
+ }
139
+ }
@@ -6,7 +6,7 @@
6
6
  * launch as `agents run --device`, and the resulting task shows up in BOTH
7
7
  * `agents cloud ps` and `agents devices ps` — one store (the host-task sidecars
8
8
  * under ~/.agents/.cache/hosts/), two views. No new transport, no relay: SSH
9
- * is the only wire, exactly like the rest of the hosts design (docs/hosts.md).
9
+ * is the only wire, exactly like the rest of the host design (docs/fleet.md).
10
10
  *
11
11
  * Status semantics follow reconcile.ts's prime rule: completion is only ever
12
12
  * CONFIRMED from the remote `.exit` file — an unreachable host leaves a task
@@ -6,7 +6,7 @@
6
6
  * launch as `agents run --device`, and the resulting task shows up in BOTH
7
7
  * `agents cloud ps` and `agents devices ps` — one store (the host-task sidecars
8
8
  * under ~/.agents/.cache/hosts/), two views. No new transport, no relay: SSH
9
- * is the only wire, exactly like the rest of the hosts design (docs/hosts.md).
9
+ * is the only wire, exactly like the rest of the host design (docs/fleet.md).
10
10
  *
11
11
  * Status semantics follow reconcile.ts's prime rule: completion is only ever
12
12
  * CONFIRMED from the remote `.exit` file — an unreachable host leaves a task
@@ -290,7 +290,7 @@ export class RushCloudProvider {
290
290
  `placement does not securely inject them yet. Create and sync one with:`,
291
291
  ` agents accounts add <name> --provider anthropic --auth api-key # or: --auth setup-token`,
292
292
  ` agents accounts sync <name> <device>`,
293
- `then run locally or on that device with --account <name>. See docs/credential-management.md (SING-1b).`,
293
+ `then run locally or on that device with --account <name>. See docs/secrets.md (SING-1b).`,
294
294
  ].join('\n'));
295
295
  }
296
296
  }
@@ -174,8 +174,8 @@ export function writeComputerPeers(allowedExecPaths) {
174
174
  export function resolveHelperExec() {
175
175
  const here = path.dirname(fileURLToPath(import.meta.url));
176
176
  const candidates = [
177
- // Local build (running from the agents-cli checkout). apps/cli/dist/lib/computer -> repo root (5 up) -> native/computer-mac.
178
- path.resolve(here, '..', '..', '..', '..', '..', 'native', 'computer-mac', 'dist', 'ComputerHelper.app', 'Contents', 'MacOS', 'ComputerHelper'),
177
+ // Local build (running from the agents-cli checkout). cli/dist/lib/computer -> repo root (4 up) -> native/computer-mac.
178
+ path.resolve(here, '..', '..', '..', '..', 'native', 'computer-mac', 'dist', 'ComputerHelper.app', 'Contents', 'MacOS', 'ComputerHelper'),
179
179
  // Bundled with the npm package (dist/lib/computer -> dist/).
180
180
  path.resolve(here, '..', '..', 'computer-helper', 'ComputerHelper.app', 'Contents', 'MacOS', 'ComputerHelper'),
181
181
  ];
@@ -3,25 +3,28 @@
3
3
  * ("ComputerHelper.app").
4
4
  *
5
5
  * The helper is a signed + notarized universal `.app` bundle published as a
6
- * GitHub release asset per tagged CLI version the same distribution model as
7
- * the Windows helper (see `lib/computer/ssh-tunnel.ts`). A fresh `npm i -g` machine has
8
- * no local build, so `agents computer setup` / `agents setup computer` fetch the
9
- * asset for the running CLI version, verify its sha256 against the published
6
+ * GitHub release asset on its own `computer-mac/v<x.y.z>` tag (see
7
+ * `helper-versions.ts`). The Windows helper (`lib/computer/ssh-tunnel.ts`) does
8
+ * NOT yet follow this model it still keys its exe to the CLI's own tag.
9
+ *
10
+ * A fresh `npm i -g` machine has no local build, so `agents computer setup` / `agents setup computer` fetch the
11
+ * asset for the pinned helper version, verify its sha256 against the published
10
12
  * `.sha256`, then verify the code signature (Developer ID Team + notarization)
11
13
  * before it is ever copied to /Applications.
12
14
  *
13
- * A `.app` is a directory, so the asset is a zip (`ditto -c -k --keepParent`);
14
- * we extract it with `ditto -x -k` after the checksum passes.
15
+ * The download + verify machinery is shared with the menu-bar helper in
16
+ * `../helper-download.ts`; this file is the ComputerHelper spec + its wrappers,
17
+ * keeping the historical export surface (`verifyMacHelper`, `downloadMacHelperApp`,
18
+ * `ensureMacHelperApp`, …) stable for existing callers. ComputerHelper does NOT
19
+ * pin a designated requirement (no `expectedBundleId`), matching its verification
20
+ * strength before the shared primitive existed.
15
21
  */
16
- /** GitHub repo whose `v<version>` releases carry the helper asset. */
17
- export declare const HELPER_RELEASE_REPO = "phnx-labs/agents-cli";
22
+ import { EXPECTED_TEAM_ID, HELPER_RELEASE_REPO, parseTeamId } from '../helper-download.js';
23
+ export { EXPECTED_TEAM_ID, HELPER_RELEASE_REPO, parseTeamId };
18
24
  /** The zipped `.app` release asset name. */
19
25
  export declare const MAC_HELPER_ASSET = "ComputerHelper.app.zip";
20
26
  /** The bundle directory name once extracted. */
21
27
  export declare const MAC_HELPER_APP_NAME = "ComputerHelper.app";
22
- /** Apple Developer ID Team the helper must be signed by (defense in depth on top
23
- * of `spctl` notarization assessment). "Developer ID Application: Muqit Nawaz". */
24
- export declare const EXPECTED_TEAM_ID = "2HTP252L87";
25
28
  /** Cache dir for the downloaded helper, one subdir per release tag. */
26
29
  export declare function macHelperCacheDir(version: string): string;
27
30
  /** Release-asset URLs for the helper zip + its checksum at one `v<version>` tag. */
@@ -29,9 +32,6 @@ export declare function macHelperAssetUrls(version: string): {
29
32
  zip: string;
30
33
  sha256: string;
31
34
  };
32
- /** Extract `TeamIdentifier=XXXX` from `codesign -dv --verbose=4` output (which is
33
- * emitted on stderr). Returns null when absent (ad-hoc / unsigned). */
34
- export declare function parseTeamId(codesignInfo: string): string | null;
35
35
  /**
36
36
  * Verify a helper `.app` bundle is intact, signed by the expected Developer ID
37
37
  * Team, and notarized (Gatekeeper-accepted). Throws with an actionable message
@@ -48,7 +48,7 @@ export declare function downloadMacHelperApp(version: string): Promise<string>;
48
48
  /**
49
49
  * Resolve the helper `.app` to install from: a local build / bundled copy first
50
50
  * (repo checkout), else the checksum + signature-verified release-asset download
51
- * for the running CLI version. Throws with the tag it checked when neither
51
+ * for the pinned helper version. Throws with the tag it checked when neither
52
52
  * exists. macOS only.
53
53
  */
54
54
  export declare function ensureMacHelperApp(version?: string): Promise<string>;