@phnx-labs/agents-cli 1.20.72 → 1.20.74

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 (234) hide show
  1. package/CHANGELOG.md +395 -0
  2. package/README.md +40 -4
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +12 -0
  5. package/dist/commands/activity.js +68 -0
  6. package/dist/commands/apply.d.ts +13 -0
  7. package/dist/commands/apply.js +28 -3
  8. package/dist/commands/browser.js +1 -1
  9. package/dist/commands/cloud.js +21 -11
  10. package/dist/commands/doctor.js +21 -5
  11. package/dist/commands/events.d.ts +11 -10
  12. package/dist/commands/events.js +30 -21
  13. package/dist/commands/exec.d.ts +34 -0
  14. package/dist/commands/exec.js +337 -39
  15. package/dist/commands/export.d.ts +11 -0
  16. package/dist/commands/export.js +215 -0
  17. package/dist/commands/feed.d.ts +13 -0
  18. package/dist/commands/feed.js +130 -29
  19. package/dist/commands/hq.d.ts +2 -0
  20. package/dist/commands/hq.js +58 -0
  21. package/dist/commands/import.js +51 -3
  22. package/dist/commands/lease.d.ts +19 -0
  23. package/dist/commands/lease.js +138 -11
  24. package/dist/commands/login.d.ts +2 -0
  25. package/dist/commands/login.js +123 -0
  26. package/dist/commands/logs.js +1 -1
  27. package/dist/commands/mine.d.ts +27 -0
  28. package/dist/commands/mine.js +207 -0
  29. package/dist/commands/monitors.js +79 -49
  30. package/dist/commands/plugins.js +1 -0
  31. package/dist/commands/profiles.js +158 -0
  32. package/dist/commands/repo.js +2 -60
  33. package/dist/commands/resources.d.ts +5 -0
  34. package/dist/commands/resources.js +95 -0
  35. package/dist/commands/routines.d.ts +2 -0
  36. package/dist/commands/routines.js +139 -37
  37. package/dist/commands/secrets.js +234 -47
  38. package/dist/commands/send.d.ts +15 -0
  39. package/dist/commands/send.js +62 -0
  40. package/dist/commands/sessions-tail.d.ts +1 -0
  41. package/dist/commands/sessions-tail.js +20 -9
  42. package/dist/commands/sessions.js +18 -3
  43. package/dist/commands/setup-mine.d.ts +18 -0
  44. package/dist/commands/setup-mine.js +106 -0
  45. package/dist/commands/setup-share.js +2 -2
  46. package/dist/commands/setup.js +9 -2
  47. package/dist/commands/share.d.ts +8 -1
  48. package/dist/commands/share.js +122 -37
  49. package/dist/commands/ssh.d.ts +9 -0
  50. package/dist/commands/ssh.js +196 -26
  51. package/dist/commands/sync.js +1 -1
  52. package/dist/commands/teams.js +11 -2
  53. package/dist/commands/versions.js +36 -5
  54. package/dist/commands/view.js +75 -27
  55. package/dist/index.js +80 -10
  56. package/dist/lib/activity.d.ts +124 -0
  57. package/dist/lib/activity.js +542 -0
  58. package/dist/lib/agents.d.ts +35 -0
  59. package/dist/lib/agents.js +79 -6
  60. package/dist/lib/artifact-actions.d.ts +1 -1
  61. package/dist/lib/artifact-actions.js +1 -1
  62. package/dist/lib/ask-classifier.d.ts +2 -1
  63. package/dist/lib/ask-classifier.js +3 -3
  64. package/dist/lib/auth-health.d.ts +32 -4
  65. package/dist/lib/auth-health.js +40 -1
  66. package/dist/lib/auto-pull.js +8 -1
  67. package/dist/lib/brand.d.ts +40 -0
  68. package/dist/lib/brand.js +122 -0
  69. package/dist/lib/browser/drivers/ssh.js +4 -7
  70. package/dist/lib/browser/service.js +6 -8
  71. package/dist/lib/channels/providers/index.d.ts +2 -0
  72. package/dist/lib/channels/providers/index.js +20 -0
  73. package/dist/lib/channels/providers/mailbox.d.ts +2 -0
  74. package/dist/lib/channels/providers/mailbox.js +26 -0
  75. package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
  76. package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
  77. package/dist/lib/channels/providers/rush.d.ts +6 -0
  78. package/dist/lib/channels/providers/rush.js +55 -0
  79. package/dist/lib/channels/registry.d.ts +42 -0
  80. package/dist/lib/channels/registry.js +20 -0
  81. package/dist/lib/channels/resolve.d.ts +11 -0
  82. package/dist/lib/channels/resolve.js +12 -0
  83. package/dist/lib/cli-resources.d.ts +18 -0
  84. package/dist/lib/cli-resources.js +53 -4
  85. package/dist/lib/cloud/codex.js +6 -3
  86. package/dist/lib/cloud/factory.d.ts +1 -0
  87. package/dist/lib/cloud/factory.js +10 -4
  88. package/dist/lib/cloud/rush.d.ts +3 -1
  89. package/dist/lib/cloud/rush.js +6 -1
  90. package/dist/lib/cloud/types.d.ts +2 -0
  91. package/dist/lib/codex-home.d.ts +35 -0
  92. package/dist/lib/codex-home.js +136 -0
  93. package/dist/lib/config-transfer.d.ts +16 -0
  94. package/dist/lib/config-transfer.js +56 -0
  95. package/dist/lib/crabbox/cli.d.ts +44 -0
  96. package/dist/lib/crabbox/cli.js +120 -20
  97. package/dist/lib/crabbox/lease.d.ts +52 -1
  98. package/dist/lib/crabbox/lease.js +117 -16
  99. package/dist/lib/crabbox/progress.d.ts +31 -0
  100. package/dist/lib/crabbox/progress.js +76 -0
  101. package/dist/lib/crabbox/runtimes.d.ts +4 -0
  102. package/dist/lib/crabbox/runtimes.js +33 -8
  103. package/dist/lib/crabbox/setup-copy.d.ts +87 -0
  104. package/dist/lib/crabbox/setup-copy.js +127 -0
  105. package/dist/lib/daemon.d.ts +5 -5
  106. package/dist/lib/daemon.js +9 -9
  107. package/dist/lib/drive-sync.js +6 -3
  108. package/dist/lib/event-stream.d.ts +36 -0
  109. package/dist/lib/event-stream.js +68 -0
  110. package/dist/lib/events.d.ts +1 -1
  111. package/dist/lib/exec.d.ts +11 -0
  112. package/dist/lib/exec.js +23 -2
  113. package/dist/lib/export.d.ts +72 -0
  114. package/dist/lib/export.js +269 -0
  115. package/dist/lib/feed-policy.d.ts +1 -1
  116. package/dist/lib/feed-policy.js +14 -9
  117. package/dist/lib/feed-ranking.d.ts +32 -0
  118. package/dist/lib/feed-ranking.js +224 -0
  119. package/dist/lib/feed.d.ts +35 -2
  120. package/dist/lib/feed.js +66 -3
  121. package/dist/lib/fleet/auth-sync.d.ts +53 -0
  122. package/dist/lib/fleet/auth-sync.js +92 -0
  123. package/dist/lib/fleet/remote-login.d.ts +187 -0
  124. package/dist/lib/fleet/remote-login.js +556 -0
  125. package/dist/lib/format.d.ts +30 -3
  126. package/dist/lib/format.js +34 -5
  127. package/dist/lib/fs-atomic.d.ts +7 -2
  128. package/dist/lib/fs-atomic.js +8 -12
  129. package/dist/lib/git.d.ts +16 -0
  130. package/dist/lib/git.js +79 -2
  131. package/dist/lib/heal.js +11 -3
  132. package/dist/lib/hosts/progress.d.ts +28 -10
  133. package/dist/lib/hosts/progress.js +79 -22
  134. package/dist/lib/hosts/remote-cmd.js +4 -0
  135. package/dist/lib/hq/floor.d.ts +87 -0
  136. package/dist/lib/hq/floor.js +226 -0
  137. package/dist/lib/import.d.ts +23 -0
  138. package/dist/lib/import.js +74 -2
  139. package/dist/lib/isolation-boundary-report.d.ts +10 -0
  140. package/dist/lib/isolation-boundary-report.js +35 -0
  141. package/dist/lib/menubar/install-menubar.js +14 -20
  142. package/dist/lib/notify.d.ts +1 -0
  143. package/dist/lib/notify.js +3 -3
  144. package/dist/lib/open-url.d.ts +2 -0
  145. package/dist/lib/open-url.js +19 -0
  146. package/dist/lib/openclaw-keychain.d.ts +56 -0
  147. package/dist/lib/openclaw-keychain.js +236 -0
  148. package/dist/lib/overdue.js +10 -0
  149. package/dist/lib/permissions.d.ts +44 -1
  150. package/dist/lib/permissions.js +284 -7
  151. package/dist/lib/project-launch.d.ts +6 -12
  152. package/dist/lib/project-launch.js +13 -228
  153. package/dist/lib/project-resources.d.ts +7 -0
  154. package/dist/lib/project-resources.js +306 -0
  155. package/dist/lib/pty-client.d.ts +27 -0
  156. package/dist/lib/pty-client.js +136 -10
  157. package/dist/lib/refresh.js +14 -10
  158. package/dist/lib/resource-profiles.d.ts +26 -0
  159. package/dist/lib/resource-profiles.js +157 -0
  160. package/dist/lib/resources/permissions.js +7 -1
  161. package/dist/lib/resources/types.d.ts +1 -1
  162. package/dist/lib/resources.d.ts +1 -1
  163. package/dist/lib/resources.js +32 -3
  164. package/dist/lib/routines.d.ts +11 -0
  165. package/dist/lib/routines.js +56 -15
  166. package/dist/lib/runner.d.ts +1 -0
  167. package/dist/lib/runner.js +66 -5
  168. package/dist/lib/secrets/bundles.d.ts +16 -3
  169. package/dist/lib/secrets/bundles.js +206 -37
  170. package/dist/lib/secrets/icloud-import.d.ts +2 -2
  171. package/dist/lib/secrets/icloud-import.js +9 -6
  172. package/dist/lib/secrets/mcp.js +1 -1
  173. package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
  174. package/dist/lib/secrets/vault-age-helper.js +34 -0
  175. package/dist/lib/secrets/vault.d.ts +49 -0
  176. package/dist/lib/secrets/vault.js +397 -0
  177. package/dist/lib/self-heal/checks/path.js +3 -1
  178. package/dist/lib/self-heal/checks/shadowing.js +10 -2
  179. package/dist/lib/self-heal/checks/shims.js +19 -11
  180. package/dist/lib/session/cloud.d.ts +2 -2
  181. package/dist/lib/session/cloud.js +2 -2
  182. package/dist/lib/session/db.d.ts +4 -0
  183. package/dist/lib/session/db.js +44 -7
  184. package/dist/lib/session/discover.d.ts +2 -0
  185. package/dist/lib/session/discover.js +114 -3
  186. package/dist/lib/session/stream-render.d.ts +3 -0
  187. package/dist/lib/session/stream-render.js +130 -0
  188. package/dist/lib/session/types.d.ts +6 -0
  189. package/dist/lib/share/analytics.d.ts +13 -0
  190. package/dist/lib/share/analytics.js +45 -0
  191. package/dist/lib/share/config.d.ts +19 -5
  192. package/dist/lib/share/config.js +44 -8
  193. package/dist/lib/share/provision.d.ts +58 -6
  194. package/dist/lib/share/provision.js +97 -22
  195. package/dist/lib/share/publish.d.ts +36 -13
  196. package/dist/lib/share/publish.js +55 -8
  197. package/dist/lib/share/worker-template.js +83 -17
  198. package/dist/lib/shims.d.ts +35 -0
  199. package/dist/lib/shims.js +159 -13
  200. package/dist/lib/ssh-exec.d.ts +14 -0
  201. package/dist/lib/ssh-exec.js +57 -0
  202. package/dist/lib/staleness/detectors/hooks.js +25 -1
  203. package/dist/lib/staleness/detectors/permissions.js +66 -0
  204. package/dist/lib/staleness/detectors/workflows.js +20 -0
  205. package/dist/lib/staleness/writers/hooks.js +58 -4
  206. package/dist/lib/staleness/writers/permissions.js +2 -2
  207. package/dist/lib/staleness/writers/skills.js +1 -1
  208. package/dist/lib/staleness/writers/sources.d.ts +10 -1
  209. package/dist/lib/staleness/writers/sources.js +68 -1
  210. package/dist/lib/star-nudge.d.ts +45 -0
  211. package/dist/lib/star-nudge.js +91 -0
  212. package/dist/lib/startup/command-registry.d.ts +7 -1
  213. package/dist/lib/startup/command-registry.js +19 -3
  214. package/dist/lib/state.d.ts +2 -0
  215. package/dist/lib/state.js +17 -4
  216. package/dist/lib/subagents-registry.d.ts +2 -0
  217. package/dist/lib/subagents-registry.js +3 -0
  218. package/dist/lib/teams/parsers.js +214 -6
  219. package/dist/lib/teams/supervisor.d.ts +7 -0
  220. package/dist/lib/teams/supervisor.js +2 -1
  221. package/dist/lib/template.d.ts +1 -1
  222. package/dist/lib/template.js +1 -1
  223. package/dist/lib/triggers/webhook.js +36 -3
  224. package/dist/lib/types.d.ts +84 -0
  225. package/dist/lib/uninstall.js +1 -45
  226. package/dist/lib/version.d.ts +40 -0
  227. package/dist/lib/version.js +94 -16
  228. package/dist/lib/versions.d.ts +24 -0
  229. package/dist/lib/versions.js +269 -78
  230. package/dist/lib/workflows.d.ts +2 -0
  231. package/dist/lib/workflows.js +88 -0
  232. package/package.json +2 -1
  233. package/dist/commands/daemon.d.ts +0 -10
  234. package/dist/commands/daemon.js +0 -121
@@ -0,0 +1,226 @@
1
+ import { mailboxIdForActiveSession } from '../mailbox-target.js';
2
+ function slug(s) {
3
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'unknown';
4
+ }
5
+ function roomIdForTeam(team) {
6
+ return `team:${slug(team)}`;
7
+ }
8
+ function roomIdForMachine(machine) {
9
+ return `machine:${slug(machine)}`;
10
+ }
11
+ function agentDisplayName(session) {
12
+ return session.name || session.label || session.agentId || session.sessionId?.slice(0, 8) || session.kind;
13
+ }
14
+ function sessionMachine(session) {
15
+ return session.machine || session.provenance?.host || session.host || 'local';
16
+ }
17
+ function teamForSession(session, teammatesById) {
18
+ if (session.teamName)
19
+ return session.teamName;
20
+ if (session.agentId && teammatesById.has(session.agentId))
21
+ return teammatesById.get(session.agentId).team;
22
+ return undefined;
23
+ }
24
+ function teammateName(session, teammatesById) {
25
+ if (session.agentId && teammatesById.has(session.agentId)) {
26
+ return teammatesById.get(session.agentId).name || session.agentId.slice(0, 8);
27
+ }
28
+ return session.agentId;
29
+ }
30
+ function moodForSession(session, hasOpenBlock) {
31
+ if (session.status === 'input_required' || session.activity === 'waiting_input' || hasOpenBlock)
32
+ return 'waiting';
33
+ if (session.rateLimited)
34
+ return 'blocked';
35
+ if (session.pr)
36
+ return 'celebrating';
37
+ if (session.status === 'running' || session.activity === 'working')
38
+ return 'working';
39
+ return 'idle';
40
+ }
41
+ function action(id, label, command, target, extra = {}) {
42
+ return { id, label, command, target, ...extra };
43
+ }
44
+ function agentActions(agentId, session, mailboxId, team, teammate) {
45
+ const actions = [];
46
+ if (mailboxId) {
47
+ actions.push(action(`agent:${agentId}:message`, 'Message', ['message', mailboxId, '{message}', '--surface', 'hq'], { kind: 'agent', id: agentId }, { input: [{ name: 'message', label: 'Message', placeholder: 'What should this agent do next?', required: true }] }));
48
+ actions.push(action(`agent:${agentId}:stop`, 'Stop', ['feed', '--kill', mailboxId], { kind: 'agent', id: agentId }, { destructive: true }));
49
+ }
50
+ if (team && teammate) {
51
+ actions.push(action(`agent:${agentId}:team-stop`, 'Stop teammate', ['teams', 'stop', team, teammate], { kind: 'agent', id: agentId }, { destructive: true }));
52
+ }
53
+ if (session.sessionId) {
54
+ actions.push(action(`agent:${agentId}:transcript`, 'Transcript', ['sessions', session.sessionId, '--markdown'], { kind: 'agent', id: agentId }));
55
+ }
56
+ return actions;
57
+ }
58
+ function spawnActionForRoom(roomId, team) {
59
+ if (team) {
60
+ return action(`room:${roomId}:spawn-teammate`, 'Spawn teammate', ['teams', 'add', team, '{agent}', '{task}', '--name', '{name}', '--mode', 'edit'], { kind: 'room', id: roomId }, {
61
+ input: [
62
+ { name: 'agent', label: 'Agent', placeholder: 'claude', required: true },
63
+ { name: 'name', label: 'Name', placeholder: 'frontend', required: true },
64
+ { name: 'task', label: 'Task', placeholder: 'Implement the next scoped task', required: true },
65
+ ],
66
+ });
67
+ }
68
+ return action(`room:${roomId}:spawn-run`, 'Run agent', ['run', '{agent}', '{task}', '--name', '{name}'], { kind: 'room', id: roomId }, {
69
+ input: [
70
+ { name: 'agent', label: 'Agent', placeholder: 'claude', required: true },
71
+ { name: 'name', label: 'Name', placeholder: 'investigate', required: true },
72
+ { name: 'task', label: 'Task', placeholder: 'Investigate this room', required: true },
73
+ ],
74
+ });
75
+ }
76
+ export function buildHqFloor(input) {
77
+ const generatedAt = (input.generatedAt ?? new Date()).toISOString();
78
+ const teammatesById = new Map();
79
+ for (const [team, teammates] of input.teammatesByTeam) {
80
+ for (const teammate of teammates)
81
+ teammatesById.set(teammate.agent_id, { ...teammate, team });
82
+ }
83
+ const openBlocksByMailbox = new Map();
84
+ for (const block of input.blocks) {
85
+ const blocks = openBlocksByMailbox.get(block.mailboxId) || [];
86
+ blocks.push(block);
87
+ openBlocksByMailbox.set(block.mailboxId, blocks);
88
+ }
89
+ const rooms = new Map();
90
+ const ensureRoom = (room) => {
91
+ const existing = rooms.get(room.id);
92
+ if (existing)
93
+ return existing;
94
+ const created = {
95
+ ...room,
96
+ counts: { agents: 0, needsInput: 0, running: 0, failed: 0 },
97
+ actions: [spawnActionForRoom(room.id, room.team)],
98
+ };
99
+ rooms.set(room.id, created);
100
+ return created;
101
+ };
102
+ for (const team of input.teams) {
103
+ ensureRoom({ id: roomIdForTeam(team.task_name), kind: 'team', name: team.task_name, team: team.task_name });
104
+ }
105
+ const agents = [];
106
+ for (const session of input.sessions) {
107
+ const team = teamForSession(session, teammatesById);
108
+ const machine = sessionMachine(session);
109
+ const room = team
110
+ ? ensureRoom({ id: roomIdForTeam(team), kind: 'team', name: team, team })
111
+ : ensureRoom({ id: roomIdForMachine(machine), kind: 'machine', name: machine, machine });
112
+ const mailboxId = mailboxIdForActiveSession(session);
113
+ const blocks = mailboxId ? openBlocksByMailbox.get(mailboxId) || [] : [];
114
+ const id = session.agentId || session.sessionId || `${session.kind}:${agents.length + 1}`;
115
+ const mood = moodForSession(session, blocks.length > 0);
116
+ const teammate = teammateName(session, teammatesById);
117
+ room.counts.agents++;
118
+ if (mood === 'waiting')
119
+ room.counts.needsInput++;
120
+ if (session.status === 'running')
121
+ room.counts.running++;
122
+ if (mood === 'blocked')
123
+ room.counts.failed++;
124
+ agents.push({
125
+ id,
126
+ label: agentDisplayName(session),
127
+ agent: session.kind,
128
+ roomId: room.id,
129
+ mood,
130
+ status: session.status,
131
+ activity: session.activity,
132
+ preview: session.preview || session.question?.text,
133
+ machine,
134
+ team,
135
+ teammate,
136
+ sessionId: session.sessionId,
137
+ mailboxId,
138
+ pid: session.pid,
139
+ prUrl: session.pr?.url,
140
+ ticketId: session.ticket?.id,
141
+ actions: agentActions(id, session, mailboxId, team, teammate),
142
+ });
143
+ }
144
+ const ambientEvents = [];
145
+ for (const agent of agents) {
146
+ if (agent.mood === 'waiting') {
147
+ ambientEvents.push({
148
+ id: `needs-input:${agent.id}`,
149
+ kind: 'needs_input',
150
+ roomId: agent.roomId,
151
+ agentId: agent.id,
152
+ label: `${agent.label} needs input`,
153
+ intensity: 'high',
154
+ });
155
+ }
156
+ else if (agent.mood === 'blocked') {
157
+ ambientEvents.push({
158
+ id: `blocked:${agent.id}`,
159
+ kind: 'error',
160
+ roomId: agent.roomId,
161
+ agentId: agent.id,
162
+ label: `${agent.label} is blocked`,
163
+ intensity: 'high',
164
+ });
165
+ }
166
+ else if (agent.mood === 'celebrating' && agent.prUrl) {
167
+ ambientEvents.push({
168
+ id: `pr:${agent.id}`,
169
+ kind: 'pr',
170
+ roomId: agent.roomId,
171
+ agentId: agent.id,
172
+ label: `${agent.label} opened a PR`,
173
+ intensity: 'medium',
174
+ });
175
+ }
176
+ }
177
+ for (const room of rooms.values()) {
178
+ if (room.kind === 'team' && room.counts.running > 0) {
179
+ ambientEvents.push({
180
+ id: `team-active:${room.id}`,
181
+ kind: 'team_active',
182
+ roomId: room.id,
183
+ label: `${room.name} has ${room.counts.running} active teammate${room.counts.running === 1 ? '' : 's'}`,
184
+ intensity: 'low',
185
+ });
186
+ }
187
+ if (room.counts.agents > 0 && room.counts.running === 0 && room.counts.needsInput === 0) {
188
+ ambientEvents.push({
189
+ id: `idle:${room.id}`,
190
+ kind: 'idle_scene',
191
+ roomId: room.id,
192
+ label: `${room.name} is calm`,
193
+ intensity: 'low',
194
+ });
195
+ }
196
+ }
197
+ const floorActions = [
198
+ action('floor:create-team', 'Create team room', ['teams', 'create', '{team}', '--description', '{description}'], { kind: 'floor', id: 'floor' }, {
199
+ input: [
200
+ { name: 'team', label: 'Team', placeholder: 'release-squad', required: true },
201
+ { name: 'description', label: 'Description', placeholder: 'What this room owns' },
202
+ ],
203
+ }),
204
+ ];
205
+ const roomList = [...rooms.values()].sort((a, b) => {
206
+ if (a.kind !== b.kind)
207
+ return a.kind === 'team' ? -1 : 1;
208
+ return a.name.localeCompare(b.name);
209
+ });
210
+ return {
211
+ version: 1,
212
+ generatedAt,
213
+ counters: {
214
+ rooms: roomList.length,
215
+ agents: agents.length,
216
+ teams: input.teams.length,
217
+ needsInput: agents.filter((agent) => agent.mood === 'waiting').length,
218
+ failed: agents.filter((agent) => agent.mood === 'blocked').length,
219
+ prs: agents.filter((agent) => agent.prUrl).length,
220
+ },
221
+ rooms: roomList,
222
+ agents,
223
+ ambientEvents,
224
+ actions: floorActions,
225
+ };
226
+ }
@@ -111,3 +111,26 @@ export declare function importInstallScriptBinary(spec: AgentBinarySpec, version
111
111
  * /opt/homebrew/bin/{cli} -> ../lib/node_modules/{pkg}/dist/index.js
112
112
  */
113
113
  export declare function resolvePackageDirFromBinary(binaryPath: string): string | null;
114
+ /**
115
+ * Seed an ISOLATED version's home from the user's real `~/.<agent>` — the mirror of
116
+ * {@link importAgentConfig}, which adopts.
117
+ *
118
+ * The difference is the whole point: this COPIES and leaves the original in place,
119
+ * never symlinks it, never sets a default, never creates a shim. So an isolated copy
120
+ * can start from the setup the user already has instead of from nothing, which was
121
+ * the only way to get a working sandbox before.
122
+ *
123
+ * Credentials are skipped by default and reported, not silently included. An isolated
124
+ * copy is a separate principal — `agents add --isolated` already tells the user to
125
+ * sign in on first run — and copying tokens into it should be a choice, not a side
126
+ * effect of wanting your settings. `--with-auth` opts in.
127
+ */
128
+ export declare function seedIsolatedConfigFromLocal(agentId: AgentId, version: string, opts?: {
129
+ withAuth?: boolean;
130
+ }): {
131
+ seeded: boolean;
132
+ from: string;
133
+ to: string;
134
+ skippedAuth: string[];
135
+ error?: string;
136
+ };
@@ -20,9 +20,9 @@ import * as fs from 'fs';
20
20
  import * as os from 'os';
21
21
  import * as path from 'path';
22
22
  import { AGENTS } from './agents.js';
23
- import { getVersionsDir } from './state.js';
23
+ import { getUserAgentsDir, getVersionsDir } from './state.js';
24
24
  import { setGlobalDefault } from './versions.js';
25
- import { createShim, createVersionedAlias, ensureShimCurrent, switchHomeFileSymlinks } from './shims.js';
25
+ import { createShim, createVersionedAlias, ensureShimCurrent, switchHomeFileSymlinks, assertIsolationBoundary } from './shims.js';
26
26
  const IMPORT_VERSION_RE = /^(?:latest|[A-Za-z0-9._+-]{1,64})$/;
27
27
  export function isValidImportVersion(version) {
28
28
  return IMPORT_VERSION_RE.test(version);
@@ -39,6 +39,12 @@ export async function importAgentConfig(agentId, version) {
39
39
  if (!isValidImportVersion(version)) {
40
40
  return { success: false, error: `Invalid version: ${JSON.stringify(version)}` };
41
41
  }
42
+ // Adoption, done inline rather than via switchConfigSymlink — so it needs the gate
43
+ // directly. commands/import.ts checks at its entry point too (it must: it registers
44
+ // a normal version first, which would un-protect the agent before this runs), but
45
+ // an exported function that moves the user's real config must not depend on every
46
+ // future caller remembering.
47
+ assertIsolationBoundary(agentId, 'adopt your existing install');
42
48
  const agent = AGENTS[agentId];
43
49
  const configDir = agent.configDir;
44
50
  const versionsDir = getVersionsDir();
@@ -237,3 +243,69 @@ export function resolvePackageDirFromBinary(binaryPath) {
237
243
  return null;
238
244
  }
239
245
  }
246
+ /**
247
+ * Seed an ISOLATED version's home from the user's real `~/.<agent>` — the mirror of
248
+ * {@link importAgentConfig}, which adopts.
249
+ *
250
+ * The difference is the whole point: this COPIES and leaves the original in place,
251
+ * never symlinks it, never sets a default, never creates a shim. So an isolated copy
252
+ * can start from the setup the user already has instead of from nothing, which was
253
+ * the only way to get a working sandbox before.
254
+ *
255
+ * Credentials are skipped by default and reported, not silently included. An isolated
256
+ * copy is a separate principal — `agents add --isolated` already tells the user to
257
+ * sign in on first run — and copying tokens into it should be a choice, not a side
258
+ * effect of wanting your settings. `--with-auth` opts in.
259
+ */
260
+ export function seedIsolatedConfigFromLocal(agentId, version, opts = {}) {
261
+ const agent = AGENTS[agentId];
262
+ const configDir = agent.configDir;
263
+ const versionHome = path.join(getVersionsDir(), agentId, version, 'home');
264
+ // Mirror importAgentConfig's derivation so nested config dirs (e.g. Antigravity's
265
+ // ~/.gemini/antigravity-cli) land where the shim expects them.
266
+ const dest = path.join(versionHome, path.relative(os.homedir(), configDir));
267
+ const result = { seeded: false, from: configDir, to: dest, skippedAuth: [] };
268
+ if (!fs.existsSync(configDir))
269
+ return result;
270
+ // Known credential paths, relative to the config dir. `authFiles` covers the agents
271
+ // that declare them for cross-version carry; the rest are verified filenames for
272
+ // agents that do not declare any (codex `auth.json`, claude `.credentials.json`).
273
+ const authRel = new Set([
274
+ ...(agent.authFiles ?? []),
275
+ 'auth.json',
276
+ '.credentials.json',
277
+ 'credentials.json',
278
+ ]);
279
+ try {
280
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
281
+ const agentsDir = getUserAgentsDir();
282
+ const inside = agentsDir + path.sep;
283
+ fs.cpSync(configDir, dest, {
284
+ recursive: true,
285
+ filter: (src) => {
286
+ const rel = path.relative(configDir, src);
287
+ if (!opts.withAuth && rel && (authRel.has(rel) || rel.startsWith('credentials' + path.sep))) {
288
+ result.skippedAuth.push(rel);
289
+ return false;
290
+ }
291
+ // Same rule as the export path: a link into ~/.agents would dangle for a copy
292
+ // that is supposed to stand on its own.
293
+ try {
294
+ const st = fs.lstatSync(src);
295
+ if (st.isSymbolicLink()) {
296
+ const tgt = path.resolve(path.dirname(src), fs.readlinkSync(src));
297
+ if (tgt === agentsDir || tgt.startsWith(inside))
298
+ return false;
299
+ }
300
+ }
301
+ catch { /* let cpSync surface unreadable entries */ }
302
+ return true;
303
+ },
304
+ });
305
+ result.seeded = true;
306
+ }
307
+ catch (err) {
308
+ return { ...result, error: err.message };
309
+ }
310
+ return result;
311
+ }
@@ -0,0 +1,10 @@
1
+ import { IsolationBoundaryError } from './shims.js';
2
+ /**
3
+ * Turn an {@link IsolationBoundaryError} into guidance. The boundary is enforced by a
4
+ * throw so it cannot be forgotten; this is what keeps that from surfacing as a stack
5
+ * trace. The remedy is always the same shape, because the protection is derived from
6
+ * the isolated copies themselves: drop them and the agent is ordinary again.
7
+ */
8
+ export declare function explainIsolationBoundary(err: IsolationBoundaryError): void;
9
+ /** Run `fn`, converting a boundary refusal into guidance + a non-zero exit. */
10
+ export declare function withIsolationBoundary<T>(fn: () => Promise<T> | T): Promise<T | undefined>;
@@ -0,0 +1,35 @@
1
+ import chalk from 'chalk';
2
+ import { IsolationBoundaryError } from './shims.js';
3
+ import { listInstalledVersions } from './versions.js';
4
+ /**
5
+ * Turn an {@link IsolationBoundaryError} into guidance. The boundary is enforced by a
6
+ * throw so it cannot be forgotten; this is what keeps that from surfacing as a stack
7
+ * trace. The remedy is always the same shape, because the protection is derived from
8
+ * the isolated copies themselves: drop them and the agent is ordinary again.
9
+ */
10
+ export function explainIsolationBoundary(err) {
11
+ const versions = listInstalledVersions(err.agent);
12
+ console.error(chalk.red(`\n${err.agent} is installed only as isolated copies.`));
13
+ console.error(chalk.gray(` Refused: ${err.operation} — that is exactly what --isolated promises not to do.`));
14
+ console.error(chalk.gray('\n To keep the sandbox and act inside it:'));
15
+ console.error(chalk.gray(` agents add ${err.agent}@<version> --isolated # another isolated copy`));
16
+ console.error(chalk.gray(` agents use ${err.agent}@<version> # pick which one 'agents run ${err.agent}' uses`));
17
+ console.error(chalk.gray(` agents export ${err.agent} # copy its config out to your real config dir`));
18
+ console.error(chalk.gray('\n To manage this agent normally instead, remove the isolated copies first:'));
19
+ for (const v of versions) {
20
+ console.error(chalk.gray(` agents remove ${err.agent}@${v} --isolated`));
21
+ }
22
+ }
23
+ /** Run `fn`, converting a boundary refusal into guidance + a non-zero exit. */
24
+ export async function withIsolationBoundary(fn) {
25
+ try {
26
+ return await fn();
27
+ }
28
+ catch (err) {
29
+ if (err instanceof IsolationBoundaryError) {
30
+ explainIsolationBoundary(err);
31
+ process.exit(1);
32
+ }
33
+ throw err;
34
+ }
35
+ }
@@ -20,7 +20,7 @@ import * as fs from 'fs';
20
20
  import * as os from 'os';
21
21
  import * as path from 'path';
22
22
  import { getRuntimeStateDir, getHelpersDir } from '../state.js';
23
- import { getCliVersion } from '../version.js';
23
+ import { getCliVersion, resolveAgentsBin, resolveInstalledLayout } from '../version.js';
24
24
  const APP_BUNDLE_NAME = 'MenubarHelper.app';
25
25
  const INSTALL_DIR_NAME = 'agents-cli';
26
26
  const SERVICE_LABEL = 'com.phnx-labs.agents-menubar';
@@ -78,6 +78,10 @@ export function menubarServiceInstalled() {
78
78
  * 1. dist/lib/menubar/MenubarHelper.app — npm install layout (sibling of this file)
79
79
  * 2. <repo>/bin/MenubarHelper.app — raw working tree (tsx/dev)
80
80
  * 3. apps/cli/menubar/dist/MenubarHelper.app — fresh local build
81
+ * 4. <on-disk install>/dist/lib/menubar/MenubarHelper.app — Bun single-file
82
+ * binary: `import.meta.url` is a virtual `/$bunfs/` path, so the sibling
83
+ * candidates above can't see the on-disk bundle; recover it via the
84
+ * `agents` launcher symlink.
81
85
  */
82
86
  function sourceAppPath() {
83
87
  const candidates = [];
@@ -94,25 +98,15 @@ function sourceAppPath() {
94
98
  if (fs.existsSync(c))
95
99
  return c;
96
100
  }
97
- return null;
98
- }
99
- /** Resolve the `agents` launcher binary on PATH-less GUI processes. */
100
- function resolveAgentsBin() {
101
- const home = os.homedir();
102
- const candidates = [
103
- path.join(home, '.local', 'bin', 'agents'),
104
- '/opt/homebrew/bin/agents',
105
- '/usr/local/bin/agents',
106
- path.join(home, '.npm-global', 'bin', 'agents'),
107
- ];
108
- for (const c of candidates) {
109
- try {
110
- fs.accessSync(c, fs.constants.X_OK);
111
- return c;
112
- }
113
- catch {
114
- /* try next */
115
- }
101
+ // Candidate 4 — only reached when the sibling candidates miss (the Bun
102
+ // single-file binary, whose `import.meta.url` is a virtual `/$bunfs/` path).
103
+ // Resolve the launcher symlink lazily so the common Node path pays no extra
104
+ // filesystem probe.
105
+ const layout = resolveInstalledLayout();
106
+ if (layout) {
107
+ const p = path.join(layout.distDir, 'lib', 'menubar', APP_BUNDLE_NAME);
108
+ if (fs.existsSync(p))
109
+ return p;
116
110
  }
117
111
  return null;
118
112
  }
@@ -11,6 +11,7 @@ export interface NotifyResult {
11
11
  skipped?: boolean;
12
12
  error?: string;
13
13
  }
14
+ export declare function formatUrgentBlockMessage(block: OpenBlock): string;
14
15
  /** Build openclaw argv for urgent notify (exported for tests). */
15
16
  export declare function buildOpenClawNotifyArgs(text: string, options?: Pick<NotifyOptions, 'channel' | 'account' | 'target'>): string[];
16
17
  export declare function notifyUrgentBlock(block: OpenBlock, options?: NotifyOptions): Promise<NotifyResult>;
@@ -9,14 +9,14 @@
9
9
  import { execFile } from 'child_process';
10
10
  import { promisify } from 'util';
11
11
  const execFileAsync = promisify(execFile);
12
- function formatBlock(block) {
12
+ export function formatUrgentBlockMessage(block) {
13
13
  const q = block.questions[0];
14
14
  const header = q?.header ? `[${q.header}] ` : '';
15
15
  const text = q?.text ?? 'Agent needs input';
16
16
  const host = block.host ? ` on ${block.host}` : '';
17
17
  const cls = block.blockClass ?? 'approval';
18
18
  const cost = block.costOfDelay ?? 'low';
19
- return `🚨 ${cls.toUpperCase()}${host} ${header}${text} (cost: ${cost}, id: ${block.blockId})`;
19
+ return `URGENT ${cls.toUpperCase()}${host}: ${header}${text} (cost: ${cost}, id: ${block.blockId})`;
20
20
  }
21
21
  /** Build openclaw argv for urgent notify (exported for tests). */
22
22
  export function buildOpenClawNotifyArgs(text, options = {}) {
@@ -50,7 +50,7 @@ export async function notifyUrgentBlock(block, options = {}) {
50
50
  catch {
51
51
  return { ok: false, error: 'openclaw CLI not found on PATH' };
52
52
  }
53
- const text = formatBlock(block);
53
+ const text = formatUrgentBlockMessage(block);
54
54
  try {
55
55
  await execFileAsync('openclaw', buildOpenClawNotifyArgs(text, options));
56
56
  return { ok: true };
@@ -0,0 +1,2 @@
1
+ /** Best-effort: open a URL in the user's default browser. Never throws. */
2
+ export declare function openUrl(url: string): void;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Open a URL in the user's default browser, cross-platform. Best-effort and
3
+ * detached — never throws and never blocks the caller. Shared by `agents lease`
4
+ * (Hetzner console) and `agents fleet login` (the local login dashboard).
5
+ */
6
+ import { spawn } from 'child_process';
7
+ /** Best-effort: open a URL in the user's default browser. Never throws. */
8
+ export function openUrl(url) {
9
+ const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
10
+ const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
11
+ try {
12
+ const p = spawn(cmd, args, { stdio: 'ignore', detached: true });
13
+ p.on('error', () => { });
14
+ p.unref();
15
+ }
16
+ catch {
17
+ /* best-effort */
18
+ }
19
+ }
@@ -0,0 +1,56 @@
1
+ import { type SpawnSyncOptions } from 'child_process';
2
+ export declare const OPENCLAW_KEYCHAIN_ACCOUNT = "openclaw";
3
+ export declare const OPENCLAW_KEYCHAIN_PROVIDER = "agents_keychain";
4
+ export declare const OPENCLAW_KEYCHAIN_ENV_SERVICES: Record<string, string>;
5
+ export interface OpenClawSecretRef {
6
+ source: 'exec';
7
+ provider: string;
8
+ id: string;
9
+ }
10
+ export interface OpenClawKeychainMigrationOptions {
11
+ account?: string;
12
+ provider?: string;
13
+ agentsBin?: string;
14
+ }
15
+ export interface OpenClawKeychainMigrationResult {
16
+ services: Array<{
17
+ envKey: string;
18
+ service: string;
19
+ value: string;
20
+ }>;
21
+ replacedPaths: string[];
22
+ removedEnvPaths: string[];
23
+ unsupportedEnvKeys: string[];
24
+ changed: boolean;
25
+ }
26
+ interface JsonObject {
27
+ [key: string]: unknown;
28
+ }
29
+ export declare function migrateOpenClawConfigToKeychainRefs(config: JsonObject, opts?: OpenClawKeychainMigrationOptions): OpenClawKeychainMigrationResult;
30
+ export declare function buildOpenClawKeychainStoreInvocation(service: string, value: string, account?: string): {
31
+ command: string;
32
+ args: string[];
33
+ options: SpawnSyncOptions & {
34
+ input: string;
35
+ detached: boolean;
36
+ };
37
+ };
38
+ export declare function storeOpenClawKeychainServices(services: Array<{
39
+ service: string;
40
+ value: string;
41
+ }>, account?: string): void;
42
+ export declare function readOpenClawKeychainService(service: string, account?: string): string;
43
+ export interface OpenClawExecResolverRequest {
44
+ protocolVersion?: number;
45
+ provider?: string;
46
+ ids?: unknown;
47
+ }
48
+ export interface OpenClawExecResolverResponse {
49
+ protocolVersion: 1;
50
+ values: Record<string, string>;
51
+ errors?: Record<string, {
52
+ code: string;
53
+ }>;
54
+ }
55
+ export declare function resolveOpenClawKeychainRequest(request: OpenClawExecResolverRequest, lookup: (service: string) => string): OpenClawExecResolverResponse;
56
+ export {};