@phnx-labs/agents-cli 1.20.34 → 1.20.36

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 (291) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/LICENSE +185 -21
  3. package/README.md +13 -5
  4. package/dist/commands/audit.d.ts +14 -0
  5. package/dist/commands/audit.js +68 -0
  6. package/dist/commands/browser.js +82 -8
  7. package/dist/commands/check.d.ts +15 -0
  8. package/dist/commands/check.js +84 -0
  9. package/dist/commands/cloud.js +143 -3
  10. package/dist/commands/computer-actions.d.ts +15 -0
  11. package/dist/commands/computer-actions.js +120 -22
  12. package/dist/commands/computer.js +81 -0
  13. package/dist/commands/cost.js +17 -12
  14. package/dist/commands/daemon.js +4 -1
  15. package/dist/commands/doctor.d.ts +1 -0
  16. package/dist/commands/doctor.js +54 -97
  17. package/dist/commands/events.d.ts +16 -0
  18. package/dist/commands/events.js +173 -0
  19. package/dist/commands/exec.d.ts +19 -0
  20. package/dist/commands/exec.js +283 -12
  21. package/dist/commands/hosts.js +37 -29
  22. package/dist/commands/inspect.d.ts +2 -0
  23. package/dist/commands/inspect.js +62 -16
  24. package/dist/commands/lock.d.ts +12 -0
  25. package/dist/commands/lock.js +70 -0
  26. package/dist/commands/logs.d.ts +17 -0
  27. package/dist/commands/logs.js +139 -0
  28. package/dist/commands/message.d.ts +15 -0
  29. package/dist/commands/message.js +56 -0
  30. package/dist/commands/models.d.ts +3 -0
  31. package/dist/commands/models.js +27 -8
  32. package/dist/commands/permissions.js +9 -2
  33. package/dist/commands/repo.d.ts +34 -0
  34. package/dist/commands/repo.js +243 -65
  35. package/dist/commands/resource-view.d.ts +20 -0
  36. package/dist/commands/resource-view.js +90 -28
  37. package/dist/commands/routines.js +101 -5
  38. package/dist/commands/secrets-migrate.js +106 -57
  39. package/dist/commands/secrets.d.ts +31 -18
  40. package/dist/commands/secrets.js +196 -91
  41. package/dist/commands/serve.d.ts +10 -0
  42. package/dist/commands/serve.js +37 -0
  43. package/dist/commands/sessions-inject.d.ts +14 -0
  44. package/dist/commands/sessions-inject.js +111 -0
  45. package/dist/commands/sessions-picker.d.ts +2 -0
  46. package/dist/commands/sessions-picker.js +24 -3
  47. package/dist/commands/sessions-resume.d.ts +2 -0
  48. package/dist/commands/sessions-resume.js +221 -0
  49. package/dist/commands/sessions-tail.d.ts +10 -0
  50. package/dist/commands/sessions-tail.js +11 -0
  51. package/dist/commands/sessions.d.ts +143 -1
  52. package/dist/commands/sessions.js +628 -80
  53. package/dist/commands/setup.js +4 -2
  54. package/dist/commands/sync.d.ts +3 -1
  55. package/dist/commands/sync.js +156 -4
  56. package/dist/commands/teams.js +217 -0
  57. package/dist/commands/versions.js +2 -4
  58. package/dist/commands/view.d.ts +3 -0
  59. package/dist/commands/view.js +19 -8
  60. package/dist/commands/wallet.d.ts +6 -0
  61. package/dist/commands/wallet.js +22 -5
  62. package/dist/commands/watchdog.d.ts +18 -0
  63. package/dist/commands/watchdog.js +238 -0
  64. package/dist/index.js +82 -32
  65. package/dist/lib/agents.js +30 -9
  66. package/dist/lib/audit/log.d.ts +92 -0
  67. package/dist/lib/audit/log.js +177 -0
  68. package/dist/lib/auto-pull.js +2 -1
  69. package/dist/lib/browser/cdp.js +5 -1
  70. package/dist/lib/browser/chrome.d.ts +10 -0
  71. package/dist/lib/browser/chrome.js +18 -7
  72. package/dist/lib/browser/drivers/ssh.d.ts +8 -0
  73. package/dist/lib/browser/drivers/ssh.js +77 -16
  74. package/dist/lib/browser/har.d.ts +84 -0
  75. package/dist/lib/browser/har.js +77 -0
  76. package/dist/lib/browser/ipc.d.ts +3 -0
  77. package/dist/lib/browser/ipc.js +37 -12
  78. package/dist/lib/browser/profiles.d.ts +1 -1
  79. package/dist/lib/browser/profiles.js +8 -10
  80. package/dist/lib/browser/refs.d.ts +65 -0
  81. package/dist/lib/browser/refs.js +73 -1
  82. package/dist/lib/browser/runtime-state.js +1 -0
  83. package/dist/lib/browser/service.d.ts +38 -2
  84. package/dist/lib/browser/service.js +183 -23
  85. package/dist/lib/browser/types.d.ts +14 -1
  86. package/dist/lib/budget/live-cloud.d.ts +42 -0
  87. package/dist/lib/budget/live-cloud.js +79 -0
  88. package/dist/lib/budget/live-team.d.ts +31 -0
  89. package/dist/lib/budget/live-team.js +115 -0
  90. package/dist/lib/cloud/codex.js +4 -0
  91. package/dist/lib/cloud/rush.d.ts +12 -1
  92. package/dist/lib/cloud/rush.js +13 -3
  93. package/dist/lib/cloud/types.d.ts +9 -0
  94. package/dist/lib/computer/dispatch.d.ts +8 -0
  95. package/dist/lib/computer/dispatch.js +125 -0
  96. package/dist/lib/computer/loop.d.ts +62 -0
  97. package/dist/lib/computer/loop.js +98 -0
  98. package/dist/lib/computer/model.d.ts +44 -0
  99. package/dist/lib/computer/model.js +157 -0
  100. package/dist/lib/concurrency.d.ts +19 -0
  101. package/dist/lib/concurrency.js +33 -0
  102. package/dist/lib/daemon.d.ts +92 -0
  103. package/dist/lib/daemon.js +222 -18
  104. package/dist/lib/devices/connect.d.ts +3 -2
  105. package/dist/lib/devices/connect.js +5 -3
  106. package/dist/lib/devices/registry.d.ts +7 -0
  107. package/dist/lib/devices/registry.js +24 -0
  108. package/dist/lib/devices/tailscale.js +1 -1
  109. package/dist/lib/drift.d.ts +52 -0
  110. package/dist/lib/drift.js +112 -0
  111. package/dist/lib/events.d.ts +10 -3
  112. package/dist/lib/events.js +101 -24
  113. package/dist/lib/exec.d.ts +17 -0
  114. package/dist/lib/exec.js +100 -12
  115. package/dist/lib/git.d.ts +27 -0
  116. package/dist/lib/git.js +56 -1
  117. package/dist/lib/hooks/cache.d.ts +6 -0
  118. package/dist/lib/hooks/cache.js +54 -12
  119. package/dist/lib/hooks.d.ts +27 -0
  120. package/dist/lib/hooks.js +135 -10
  121. package/dist/lib/hosts/dispatch.d.ts +15 -0
  122. package/dist/lib/hosts/dispatch.js +45 -13
  123. package/dist/lib/hosts/logs.d.ts +16 -0
  124. package/dist/lib/hosts/logs.js +74 -0
  125. package/dist/lib/hosts/option.js +1 -1
  126. package/dist/lib/hosts/passthrough.js +3 -1
  127. package/dist/lib/hosts/progress.d.ts +66 -0
  128. package/dist/lib/hosts/progress.js +125 -17
  129. package/dist/lib/hosts/ready.d.ts +51 -7
  130. package/dist/lib/hosts/ready.js +98 -25
  131. package/dist/lib/hosts/reconcile.d.ts +53 -0
  132. package/dist/lib/hosts/reconcile.js +81 -0
  133. package/dist/lib/hosts/registry.d.ts +19 -2
  134. package/dist/lib/hosts/registry.js +58 -2
  135. package/dist/lib/hosts/remote-cmd.d.ts +62 -1
  136. package/dist/lib/hosts/remote-cmd.js +70 -1
  137. package/dist/lib/hosts/remote-os.d.ts +17 -0
  138. package/dist/lib/hosts/remote-os.js +30 -0
  139. package/dist/lib/hosts/session-index.d.ts +34 -0
  140. package/dist/lib/hosts/session-index.js +56 -0
  141. package/dist/lib/hosts/tasks.d.ts +22 -0
  142. package/dist/lib/hosts/tasks.js +29 -0
  143. package/dist/lib/lock.d.ts +93 -0
  144. package/dist/lib/lock.js +207 -0
  145. package/dist/lib/loop.js +16 -1
  146. package/dist/lib/machine-id.d.ts +21 -0
  147. package/dist/lib/machine-id.js +26 -0
  148. package/dist/lib/mailbox-target.d.ts +36 -0
  149. package/dist/lib/mailbox-target.js +45 -0
  150. package/dist/lib/mailbox.d.ts +47 -0
  151. package/dist/lib/mailbox.js +194 -0
  152. package/dist/lib/mcp.d.ts +5 -0
  153. package/dist/lib/mcp.js +24 -8
  154. package/dist/lib/migrate.d.ts +19 -0
  155. package/dist/lib/migrate.js +134 -26
  156. package/dist/lib/overdue.js +3 -0
  157. package/dist/lib/permissions.d.ts +4 -0
  158. package/dist/lib/permissions.js +35 -0
  159. package/dist/lib/picker.d.ts +26 -0
  160. package/dist/lib/picker.js +127 -0
  161. package/dist/lib/platform/exec.d.ts +46 -0
  162. package/dist/lib/platform/exec.js +74 -0
  163. package/dist/lib/platform/process.d.ts +31 -0
  164. package/dist/lib/platform/process.js +34 -1
  165. package/dist/lib/platform/winpath.js +2 -0
  166. package/dist/lib/plugin-marketplace.d.ts +30 -0
  167. package/dist/lib/plugin-marketplace.js +215 -2
  168. package/dist/lib/plugins.d.ts +5 -0
  169. package/dist/lib/plugins.js +61 -10
  170. package/dist/lib/profiles.d.ts +25 -0
  171. package/dist/lib/profiles.js +22 -6
  172. package/dist/lib/pty-client.js +2 -1
  173. package/dist/lib/rotate.d.ts +61 -0
  174. package/dist/lib/rotate.js +52 -0
  175. package/dist/lib/routines.d.ts +40 -2
  176. package/dist/lib/routines.js +66 -8
  177. package/dist/lib/runner.d.ts +11 -2
  178. package/dist/lib/runner.js +49 -7
  179. package/dist/lib/scheduler.js +6 -1
  180. package/dist/lib/secrets/bundles.d.ts +60 -4
  181. package/dist/lib/secrets/bundles.js +131 -12
  182. package/dist/lib/secrets/filestore.d.ts +3 -0
  183. package/dist/lib/secrets/filestore.js +42 -16
  184. package/dist/lib/secrets/index.d.ts +43 -2
  185. package/dist/lib/secrets/index.js +102 -3
  186. package/dist/lib/secrets/mcp.d.ts +93 -0
  187. package/dist/lib/secrets/mcp.js +205 -0
  188. package/dist/lib/secrets/remote.js +12 -5
  189. package/dist/lib/secrets/sync.js +83 -4
  190. package/dist/lib/secrets/windows.js +14 -3
  191. package/dist/lib/serve/data.d.ts +81 -0
  192. package/dist/lib/serve/data.js +91 -0
  193. package/dist/lib/serve/page.d.ts +7 -0
  194. package/dist/lib/serve/page.js +140 -0
  195. package/dist/lib/serve/server.d.ts +46 -0
  196. package/dist/lib/serve/server.js +115 -0
  197. package/dist/lib/session/active.d.ts +61 -0
  198. package/dist/lib/session/active.js +196 -17
  199. package/dist/lib/session/discover.d.ts +37 -0
  200. package/dist/lib/session/discover.js +204 -27
  201. package/dist/lib/session/inject.d.ts +18 -0
  202. package/dist/lib/session/inject.js +21 -0
  203. package/dist/lib/session/parse.d.ts +6 -0
  204. package/dist/lib/session/parse.js +286 -2
  205. package/dist/lib/session/pid-registry.d.ts +22 -0
  206. package/dist/lib/session/pid-registry.js +104 -0
  207. package/dist/lib/session/provenance.d.ts +14 -2
  208. package/dist/lib/session/provenance.js +39 -8
  209. package/dist/lib/session/remote-active.d.ts +26 -0
  210. package/dist/lib/session/remote-active.js +153 -0
  211. package/dist/lib/session/remote-list.d.ts +51 -0
  212. package/dist/lib/session/remote-list.js +213 -0
  213. package/dist/lib/session/remote.d.ts +7 -1
  214. package/dist/lib/session/remote.js +18 -8
  215. package/dist/lib/session/render.js +1 -1
  216. package/dist/lib/session/sync/config.d.ts +1 -7
  217. package/dist/lib/session/sync/config.js +4 -11
  218. package/dist/lib/session/types.d.ts +18 -1
  219. package/dist/lib/session/types.js +1 -1
  220. package/dist/lib/shims.d.ts +44 -6
  221. package/dist/lib/shims.js +143 -57
  222. package/dist/lib/ssh-exec.d.ts +26 -3
  223. package/dist/lib/ssh-exec.js +47 -3
  224. package/dist/lib/ssh-tunnel.d.ts +24 -5
  225. package/dist/lib/ssh-tunnel.js +62 -63
  226. package/dist/lib/staleness/writers/hooks.js +1 -1
  227. package/dist/lib/startup/command-registry.d.ts +8 -0
  228. package/dist/lib/startup/command-registry.js +17 -1
  229. package/dist/lib/state.d.ts +13 -0
  230. package/dist/lib/state.js +103 -9
  231. package/dist/lib/sync-umbrella.d.ts +14 -7
  232. package/dist/lib/sync-umbrella.js +17 -9
  233. package/dist/lib/teams/forEach.d.ts +110 -0
  234. package/dist/lib/teams/forEach.js +186 -0
  235. package/dist/lib/teams/index.d.ts +1 -0
  236. package/dist/lib/teams/index.js +1 -0
  237. package/dist/lib/teams/pr-watch.d.ts +226 -0
  238. package/dist/lib/teams/pr-watch.js +371 -0
  239. package/dist/lib/teams/registry.js +25 -9
  240. package/dist/lib/teams/supervisor.d.ts +14 -1
  241. package/dist/lib/teams/supervisor.js +19 -0
  242. package/dist/lib/teams/worktree.d.ts +9 -0
  243. package/dist/lib/teams/worktree.js +32 -0
  244. package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
  245. package/dist/lib/terminal/backends/ghostty.js +69 -0
  246. package/dist/lib/terminal/backends/index.d.ts +19 -0
  247. package/dist/lib/terminal/backends/index.js +31 -0
  248. package/dist/lib/terminal/backends/iterm.d.ts +6 -0
  249. package/dist/lib/terminal/backends/iterm.js +62 -0
  250. package/dist/lib/terminal/backends/tmux.d.ts +14 -0
  251. package/dist/lib/terminal/backends/tmux.js +23 -0
  252. package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
  253. package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
  254. package/dist/lib/terminal/engine.d.ts +39 -0
  255. package/dist/lib/terminal/engine.js +54 -0
  256. package/dist/lib/terminal/index.d.ts +17 -0
  257. package/dist/lib/terminal/index.js +8 -0
  258. package/dist/lib/terminal/inject.d.ts +204 -0
  259. package/dist/lib/terminal/inject.js +247 -0
  260. package/dist/lib/terminal/policy.d.ts +11 -0
  261. package/dist/lib/terminal/policy.js +11 -0
  262. package/dist/lib/terminal/quote.d.ts +11 -0
  263. package/dist/lib/terminal/quote.js +13 -0
  264. package/dist/lib/terminal/resolve.d.ts +64 -0
  265. package/dist/lib/terminal/resolve.js +90 -0
  266. package/dist/lib/terminal/shell.d.ts +6 -0
  267. package/dist/lib/terminal/shell.js +23 -0
  268. package/dist/lib/terminal/transport.d.ts +15 -0
  269. package/dist/lib/terminal/transport.js +40 -0
  270. package/dist/lib/terminal/types.d.ts +59 -0
  271. package/dist/lib/terminal/types.js +13 -0
  272. package/dist/lib/triggers/webhook.d.ts +85 -0
  273. package/dist/lib/triggers/webhook.js +141 -0
  274. package/dist/lib/types.d.ts +7 -0
  275. package/dist/lib/versions.d.ts +30 -3
  276. package/dist/lib/versions.js +129 -18
  277. package/dist/lib/watchdog/index.d.ts +3 -0
  278. package/dist/lib/watchdog/index.js +5 -0
  279. package/dist/lib/watchdog/read.d.ts +35 -0
  280. package/dist/lib/watchdog/read.js +149 -0
  281. package/dist/lib/watchdog/runner.d.ts +127 -0
  282. package/dist/lib/watchdog/runner.js +322 -0
  283. package/dist/lib/watchdog/watchdog.d.ts +40 -0
  284. package/dist/lib/watchdog/watchdog.js +166 -0
  285. package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
  286. package/dist/lib/watchdog/watchdogTail.js +154 -0
  287. package/dist/lib/whats-new.d.ts +9 -0
  288. package/dist/lib/whats-new.js +35 -0
  289. package/dist/lib/workflows.d.ts +166 -0
  290. package/dist/lib/workflows.js +193 -0
  291. package/package.json +5 -4
@@ -22,27 +22,26 @@ import * as fs from 'fs';
22
22
  import * as path from 'path';
23
23
  import { fileURLToPath } from 'url';
24
24
  import { randomBytes } from 'crypto';
25
- import { Transform } from 'stream';
26
25
  import { sshExec, SSH_OPTS } from './ssh-exec.js';
26
+ import { backgroundSpawnOptions } from './platform/process.js';
27
27
  import { encodePowerShell } from './browser/drivers/ssh.js';
28
28
  import { getDevice } from './devices/registry.js';
29
29
  import { sshTargetFor } from './devices/connect.js';
30
30
  import { hostNameFor } from './devices/ssh-config.js';
31
31
  import { getCacheDir } from './state.js';
32
32
  import { openComputerClient, resolveTcpEndpoint } from './computer-rpc.js';
33
- /** Build the ssh argv (after the `ssh` program name) for an `-L` tunnel. Pure. */
33
+ /** Build the ssh argv (after the `ssh` program name) for an `-L` tunnel. Pure.
34
+ *
35
+ * Composes the shared hardened baseline (`SSH_OPTS`) rather than re-listing it,
36
+ * so the tunnel inherits the same options — crucially the keepalive, which lets
37
+ * a dropped `-N` tunnel exit instead of lingering as a zombie on the laptop. */
34
38
  export function buildTunnelArgs(user, host, localPort, remotePort) {
35
39
  return [
36
40
  '-L',
37
41
  `${localPort}:127.0.0.1:${remotePort}`,
38
42
  `${user}@${host}`,
39
43
  '-N',
40
- '-o',
41
- 'StrictHostKeyChecking=accept-new',
42
- '-o',
43
- 'BatchMode=yes',
44
- '-o',
45
- 'ConnectTimeout=10',
44
+ ...SSH_OPTS,
46
45
  ];
47
46
  }
48
47
  /**
@@ -58,7 +57,7 @@ export function startSSHTunnel(user, host, localPort, remotePort, opts = {}) {
58
57
  const args = buildTunnelArgs(user, host, localPort, remotePort);
59
58
  const tunnel = spawn('ssh', args, {
60
59
  stdio: opts.detached ? 'ignore' : ['ignore', 'ignore', 'pipe'],
61
- detached: Boolean(opts.detached),
60
+ ...(opts.detached ? backgroundSpawnOptions() : { detached: false, windowsHide: true }),
62
61
  });
63
62
  let stderr = '';
64
63
  tunnel.stderr?.on('data', (data) => {
@@ -150,10 +149,15 @@ export async function resolveRemoteDevice(name) {
150
149
  return { device, target, user, host };
151
150
  }
152
151
  /**
153
- * PowerShell that streams base64 from stdin, decodes it incrementally to
154
- * %LOCALAPPDATA%\agents\computer-helper-win.exe, and stops any running instance
155
- * first so the file isn't locked. The CryptoStream/FromBase64Transform decode
156
- * is streaming the ~156MB exe never lands in memory whole on the remote.
152
+ * Single-quote a string for embedding inside a PowerShell literal.
153
+ */
154
+ function psSingleQuote(s) {
155
+ return "'" + s.replace(/'/g, "''") + "'";
156
+ }
157
+ /**
158
+ * PowerShell that resolves the destination under %LOCALAPPDATA%\agents and
159
+ * stops any running instance first so the file is not locked. The caller copies
160
+ * the exe with scp and then verifies the byte count separately.
157
161
  */
158
162
  export function buildPushScript() {
159
163
  return [
@@ -161,15 +165,18 @@ export function buildPushScript() {
161
165
  `New-Item -ItemType Directory -Force -Path $dir | Out-Null`,
162
166
  `$dst = Join-Path $dir '${WIN_HELPER_EXE}'`,
163
167
  `Get-Process -Name 'computer-helper-win' -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue`,
164
- `$si = [Console]::OpenStandardInput()`,
165
- `$t = New-Object Security.Cryptography.FromBase64Transform`,
166
- `$cs = New-Object Security.Cryptography.CryptoStream($si, $t, [Security.Cryptography.CryptoStreamMode]::Read)`,
167
- `$fs = [IO.File]::Create($dst)`,
168
- `$cs.CopyTo($fs)`,
169
- `$fs.Close(); $cs.Close()`,
170
168
  `Write-Output $dst`,
171
169
  ].join('; ');
172
170
  }
171
+ /** PowerShell that verifies scp wrote the expected number of bytes. */
172
+ export function buildVerifyPushScript(remotePath, expectedBytes) {
173
+ return [
174
+ `$dst = ${psSingleQuote(remotePath)}`,
175
+ `$item = Get-Item -LiteralPath $dst -ErrorAction Stop`,
176
+ `if ($item.Length -ne ${expectedBytes}) { throw "helper copy length mismatch: expected ${expectedBytes}, got $($item.Length)" }`,
177
+ `Write-Output "$dst $($item.Length)"`,
178
+ ].join('; ');
179
+ }
173
180
  /**
174
181
  * PowerShell that registers the daemon as a LOGON scheduled task. Interactive
175
182
  * logon type + Highest run level so the daemon runs in the real desktop session
@@ -195,51 +202,32 @@ export function buildUnregisterTaskScript(taskName) {
195
202
  `Get-Process -Name 'computer-helper-win' -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue`,
196
203
  ].join('; ');
197
204
  }
205
+ /** Convert a Windows path returned by PowerShell into the scp/SFTP path form. */
206
+ export function scpRemotePath(remotePath) {
207
+ return remotePath.replace(/\\/g, '/');
208
+ }
198
209
  /**
199
- * `setup --host`: push the exe, then register + start the LOGON task. Both hops
200
- * go through `sshExec` (BatchMode key auth the same hardening the browser
201
- * driver and `agents ssh` use). Throws with the remote stderr on any failure.
202
- */
203
- /**
204
- * Base64-encode a byte stream in 3-byte-aligned chunks so the concatenated
205
- * output is valid (every chunk boundary lands on a base64 quantum).
210
+ * Build the scp argv used for the helper exe transfer. Exported so tests can
211
+ * assert the real binary copy path keeps BatchMode and does not route bytes
212
+ * through a PowerShell decoder.
206
213
  */
207
- class Base64Encode extends Transform {
208
- leftover = Buffer.alloc(0);
209
- _transform(chunk, _enc, cb) {
210
- const buf = this.leftover.length ? Buffer.concat([this.leftover, chunk]) : chunk;
211
- const usable = buf.length - (buf.length % 3);
212
- this.leftover = Buffer.from(buf.subarray(usable));
213
- if (usable > 0)
214
- this.push(buf.subarray(0, usable).toString('base64'));
215
- cb();
216
- }
217
- _flush(cb) {
218
- if (this.leftover.length)
219
- this.push(this.leftover.toString('base64'));
220
- cb();
221
- }
214
+ export function buildScpArgs(target, remotePath, filePath) {
215
+ return [...SSH_OPTS, filePath, `${target}:${scpRemotePath(remotePath)}`];
222
216
  }
223
217
  /**
224
- * Stream a local file to a remote command's stdin over ssh, base64-encoded on
225
- * the fly. Async spawn + piping honors backpressure; the previous
226
- * `spawnSync({ input })` blob deadlocked once the ssh socket buffer filled
227
- * (~4MB) on large files (the 157MB Windows helper reproduced this reliably),
228
- * and worse, reported a false success leaving a 0-byte remote file. Rejects on
229
- * any pipe error so a broken transfer fails loudly instead.
218
+ * Copy a local file to the remote destination with scp. This is a binary
219
+ * transfer; no base64 transform runs on either side.
230
220
  */
231
- function streamFileOverSsh(target, remoteCmd, filePath, timeoutMs = 600_000) {
221
+ function copyFileOverScp(target, remotePath, filePath, timeoutMs = 600_000) {
232
222
  return new Promise((resolve, reject) => {
233
- const child = spawn('ssh', [...SSH_OPTS, target, remoteCmd], {
234
- stdio: ['pipe', 'pipe', 'pipe'],
223
+ const child = spawn('scp', buildScpArgs(target, remotePath, filePath), {
224
+ stdio: ['ignore', 'ignore', 'pipe'],
235
225
  });
236
226
  let stderr = '';
237
- let stdout = '';
238
227
  child.stderr.on('data', (d) => (stderr += d.toString()));
239
- child.stdout.on('data', (d) => (stdout += d.toString()));
240
228
  const timer = setTimeout(() => {
241
229
  child.kill('SIGKILL');
242
- reject(new Error(`ssh push to ${target} timed out after ${timeoutMs}ms`));
230
+ reject(new Error(`scp push to ${target} timed out after ${timeoutMs}ms`));
243
231
  }, timeoutMs);
244
232
  const fail = (e) => {
245
233
  clearTimeout(timer);
@@ -247,30 +235,41 @@ function streamFileOverSsh(target, remoteCmd, filePath, timeoutMs = 600_000) {
247
235
  reject(e);
248
236
  };
249
237
  child.on('error', fail);
250
- child.stdin.on('error', fail); // EPIPE if the remote decoder dies mid-stream
251
238
  child.on('close', (code) => {
252
239
  clearTimeout(timer);
253
- resolve({ code, stderr: stderr || stdout });
240
+ resolve({ code, stderr });
254
241
  });
255
- const src = fs.createReadStream(filePath);
256
- src.on('error', fail);
257
- // disk -> aligned base64 -> ssh stdin; .pipe() applies backpressure
258
- src.pipe(new Base64Encode()).pipe(child.stdin);
259
242
  });
260
243
  }
244
+ /**
245
+ * `setup --host`: push the exe, then register + start the LOGON task. Remote
246
+ * PowerShell hops go through `sshExec` (BatchMode key auth — the same hardening
247
+ * the browser driver and `agents ssh` use), and the large exe rides a binary
248
+ * scp transfer. Throws with the remote stderr on any failure.
249
+ */
261
250
  export async function setupRemoteHelper(name) {
262
251
  const { target } = await resolveRemoteDevice(name);
263
252
  const exe = resolveWinHelperExe();
264
253
  if (!exe) {
265
254
  throw new Error(`Windows helper exe not built. Run: bash scripts/build-win.sh`);
266
255
  }
267
- // Push: stream the exe from disk, base64-encoded on the fly, to the remote
268
- // decoder. Streaming (vs a single spawnSync `input` blob) honors ssh socket
269
- // backpressure the blob path deadlocks once the socket buffer fills (~4MB).
270
- const push = await streamFileOverSsh(target, encodePowerShell(buildPushScript()), exe);
256
+ const prep = sshExec(target, encodePowerShell(buildPushScript()), { timeoutMs: 60_000 });
257
+ if (prep.code !== 0) {
258
+ throw new Error(`preparing helper exe path on '${name}' failed (exit ${prep.code ?? 'null'}): ${prep.stderr.trim() || prep.stdout.trim()}`);
259
+ }
260
+ const remotePath = prep.stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
261
+ if (!remotePath) {
262
+ throw new Error(`preparing helper exe path on '${name}' did not return a destination path`);
263
+ }
264
+ const push = await copyFileOverScp(target, remotePath, exe);
271
265
  if (push.code !== 0) {
272
266
  throw new Error(`pushing helper exe to '${name}' failed (exit ${push.code ?? 'null'}): ${push.stderr.trim()}`);
273
267
  }
268
+ const expectedBytes = fs.statSync(exe).size;
269
+ const verify = sshExec(target, encodePowerShell(buildVerifyPushScript(remotePath, expectedBytes)), { timeoutMs: 60_000 });
270
+ if (verify.code !== 0) {
271
+ throw new Error(`verifying helper exe on '${name}' failed (exit ${verify.code ?? 'null'}): ${verify.stderr.trim() || verify.stdout.trim()}`);
272
+ }
274
273
  // Register + start the LOGON task.
275
274
  const reg = sshExec(target, encodePowerShell(buildRegisterTaskScript(REMOTE_HELPER_PORT, REMOTE_TASK_NAME)), {
276
275
  timeoutMs: 60_000,
@@ -32,7 +32,7 @@ function buildHooksWriter(agent) {
32
32
  }
33
33
  // Native hook registration in settings.json/hooks.json. Grok auto-
34
34
  // discovers from ~/.grok/hooks/ so the file copy is sufficient.
35
- if (agent === 'claude' || agent === 'codex' || agent === 'gemini' || agent === 'antigravity' || agent === 'kimi') {
35
+ if (agent === 'claude' || agent === 'codex' || agent === 'gemini' || agent === 'antigravity' || agent === 'kimi' || agent === 'droid') {
36
36
  registerHooksToSettings(agent, versionHome);
37
37
  }
38
38
  return { synced };
@@ -49,6 +49,7 @@ export declare const loadPrune: ModuleLoader;
49
49
  export declare const loadTrash: ModuleLoader;
50
50
  export declare const loadRestore: ModuleLoader;
51
51
  export declare const loadDoctor: ModuleLoader;
52
+ export declare const loadCheck: ModuleLoader;
52
53
  export declare const loadStatus: ModuleLoader;
53
54
  export declare const loadProfiles: ModuleLoader;
54
55
  export declare const loadSecrets: ModuleLoader;
@@ -57,6 +58,7 @@ export declare const loadHelper: ModuleLoader;
57
58
  export declare const loadMenubar: ModuleLoader;
58
59
  export declare const loadBeta: ModuleLoader;
59
60
  export declare const loadSync: ModuleLoader;
61
+ export declare const loadLock: ModuleLoader;
60
62
  export declare const loadRefreshRules: ModuleLoader;
61
63
  export declare const loadDrive: ModuleLoader;
62
64
  export declare const loadFactory: ModuleLoader;
@@ -66,9 +68,12 @@ export declare const loadBudget: ModuleLoader;
66
68
  export declare const loadAlias: ModuleLoader;
67
69
  export declare const loadPty: ModuleLoader;
68
70
  export declare const loadTmux: ModuleLoader;
71
+ export declare const loadWatchdog: ModuleLoader;
69
72
  export declare const loadBrowser: ModuleLoader;
70
73
  export declare const loadComputer: ModuleLoader;
71
74
  export declare const loadHosts: ModuleLoader;
75
+ export declare const loadLogs: ModuleLoader;
76
+ export declare const loadEvents: ModuleLoader;
72
77
  export declare const loadSsh: ModuleLoader;
73
78
  export declare const loadPull: ModuleLoader;
74
79
  export declare const loadPush: ModuleLoader;
@@ -77,6 +82,9 @@ export declare const loadSetup: ModuleLoader;
77
82
  export declare const loadSessions: ModuleLoader;
78
83
  export declare const loadTeams: ModuleLoader;
79
84
  export declare const loadCloud: ModuleLoader;
85
+ export declare const loadMessage: ModuleLoader;
86
+ export declare const loadServe: ModuleLoader;
87
+ export declare const loadAudit: ModuleLoader;
80
88
  /**
81
89
  * Commands whose modules pull in the SQLite-backed session/cloud stack. They are
82
90
  * registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
@@ -27,6 +27,7 @@ export const loadPrune = async () => (await import('../../commands/prune.js')).r
27
27
  export const loadTrash = async () => (await import('../../commands/trash.js')).registerTrashCommands;
28
28
  export const loadRestore = async () => (await import('../../commands/trash.js')).registerRestoreCommand;
29
29
  export const loadDoctor = async () => (await import('../../commands/doctor.js')).registerDoctorCommand;
30
+ export const loadCheck = async () => (await import('../../commands/check.js')).registerCheckCommand;
30
31
  export const loadStatus = async () => (await import('../../commands/status.js')).registerStatusCommand;
31
32
  export const loadProfiles = async () => (await import('../../commands/profiles.js')).registerProfilesCommands;
32
33
  export const loadSecrets = async () => (await import('../../commands/secrets.js')).registerSecretsCommands;
@@ -35,6 +36,7 @@ export const loadHelper = async () => (await import('../../commands/helper.js'))
35
36
  export const loadMenubar = async () => (await import('../../commands/menubar.js')).registerMenubarCommands;
36
37
  export const loadBeta = async () => (await import('../../commands/beta.js')).registerBetaCommands;
37
38
  export const loadSync = async () => (await import('../../commands/sync.js')).registerSyncCommand;
39
+ export const loadLock = async () => (await import('../../commands/lock.js')).registerLockCommand;
38
40
  export const loadRefreshRules = async () => (await import('../../commands/refresh-rules.js')).registerRefreshRulesCommand;
39
41
  export const loadDrive = async () => (await import('../../commands/drive.js')).registerDriveCommands;
40
42
  export const loadFactory = async () => (await import('../../commands/factory.js')).registerFactoryCommands;
@@ -44,9 +46,12 @@ export const loadBudget = async () => (await import('../../commands/budget.js'))
44
46
  export const loadAlias = async () => (await import('../../commands/alias.js')).registerAliasCommand;
45
47
  export const loadPty = async () => (await import('../../commands/pty.js')).registerPtyCommands;
46
48
  export const loadTmux = async () => (await import('../../commands/tmux.js')).registerTmuxCommands;
49
+ export const loadWatchdog = async () => (await import('../../commands/watchdog.js')).registerWatchdogCommand;
47
50
  export const loadBrowser = async () => (await import('../../commands/browser.js')).registerBrowserCommand;
48
51
  export const loadComputer = async () => (await import('../../commands/computer.js')).registerComputerCommand;
49
52
  export const loadHosts = async () => (await import('../../commands/hosts.js')).registerHostsCommand;
53
+ export const loadLogs = async () => (await import('../../commands/logs.js')).registerLogsCommand;
54
+ export const loadEvents = async () => (await import('../../commands/events.js')).registerEventsCommand;
50
55
  export const loadSsh = async () => (await import('../../commands/ssh.js')).registerSshCommands;
51
56
  export const loadPull = async () => (await import('../../commands/pull.js')).registerPullCommand;
52
57
  export const loadPush = async () => (await import('../../commands/push.js')).registerPushCommand;
@@ -55,6 +60,9 @@ export const loadSetup = async () => (await import('../../commands/setup.js')).r
55
60
  export const loadSessions = async () => (await import('../../commands/sessions.js')).registerSessionsCommands;
56
61
  export const loadTeams = async () => (await import('../../commands/teams.js')).registerTeamsCommands;
57
62
  export const loadCloud = async () => (await import('../../commands/cloud.js')).registerCloudCommands;
63
+ export const loadMessage = async () => (await import('../../commands/message.js')).registerMessageCommand;
64
+ export const loadServe = async () => (await import('../../commands/serve.js')).registerServeCommand;
65
+ export const loadAudit = async () => (await import('../../commands/audit.js')).registerAuditCommands;
58
66
  /**
59
67
  * Commands whose modules pull in the SQLite-backed session/cloud stack. They are
60
68
  * registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
@@ -62,7 +70,7 @@ export const loadCloud = async () => (await import('../../commands/cloud.js')).r
62
70
  * inherit the root's custom help formatter rather than getting the per-command
63
71
  * recursive pass. Keeping that ordering preserves their `--help` output exactly.
64
72
  */
65
- export const LAZY_COMMAND_NAMES = new Set(['sessions', 'teams', 'cloud']);
73
+ export const LAZY_COMMAND_NAMES = new Set(['sessions', 'teams', 'cloud', 'message', 'serve']);
66
74
  /**
67
75
  * User-typed top-level command name -> ordered list of module loaders to run.
68
76
  *
@@ -111,6 +119,7 @@ export const COMMAND_LOADERS = {
111
119
  trash: [loadTrash],
112
120
  restore: [loadRestore],
113
121
  doctor: [loadDoctor],
122
+ check: [loadCheck],
114
123
  status: [loadStatus],
115
124
  profiles: [loadProfiles],
116
125
  secrets: [loadSecrets],
@@ -119,6 +128,7 @@ export const COMMAND_LOADERS = {
119
128
  menubar: [loadMenubar],
120
129
  beta: [loadBeta],
121
130
  sync: [loadSync],
131
+ lock: [loadLock],
122
132
  'refresh-rules': [loadRefreshRules],
123
133
  drive: [loadDrive],
124
134
  factory: [loadFactory],
@@ -128,9 +138,12 @@ export const COMMAND_LOADERS = {
128
138
  alias: [loadAlias],
129
139
  pty: [loadPty],
130
140
  tmux: [loadTmux],
141
+ watchdog: [loadWatchdog],
131
142
  browser: [loadBrowser],
132
143
  computer: [loadComputer],
133
144
  hosts: [loadHosts],
145
+ logs: [loadLogs],
146
+ events: [loadEvents],
134
147
  ssh: [loadSsh],
135
148
  devices: [loadSsh],
136
149
  pull: [loadPull],
@@ -140,4 +153,7 @@ export const COMMAND_LOADERS = {
140
153
  sessions: [loadSessions],
141
154
  teams: [loadTeams],
142
155
  cloud: [loadCloud],
156
+ message: [loadMessage],
157
+ serve: [loadServe],
158
+ audit: [loadAudit],
143
159
  };
@@ -120,6 +120,8 @@ export declare function getRoutinesDir(): string;
120
120
  export declare function getProjectRoutinesDir(cwd?: string): string | null;
121
121
  /** Path to routine execution logs (~/.agents/.history/runs/). */
122
122
  export declare function getRunsDir(): string;
123
+ /** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
124
+ export declare function getMailboxRootDir(): string;
123
125
  /** Path to installed agent CLI binaries (~/.agents/.history/versions/). */
124
126
  export declare function getVersionsDir(): string;
125
127
  /** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
@@ -226,6 +228,17 @@ export declare function getEnabledExtraRepos(): Array<{
226
228
  export declare function ensureAgentsDir(): void;
227
229
  /** Return an empty Meta object used when no agents.yaml exists yet. */
228
230
  export declare function createDefaultMeta(): Meta;
231
+ /**
232
+ * Per-device machine-local version pins — `~/.agents/devices/<machine>/agents.yaml`.
233
+ * Committed and synced, but each machine only ever writes its OWN folder, so
234
+ * pulls never conflict. `<machine>` = machineId() (Tailscale-aligned short name).
235
+ */
236
+ export declare function getDeviceMetaPath(): string;
237
+ /**
238
+ * Machine-local per-version resource tracking — `~/.agents/.history/version-resources.json`.
239
+ * Gitignored (under .history/) and regenerable; never synced.
240
+ */
241
+ export declare function getVersionResourcesPath(): string;
229
242
  /**
230
243
  * Read and cache ~/.agents/agents.yaml, migrating from legacy locations if needed.
231
244
  *
package/dist/lib/state.js CHANGED
@@ -28,6 +28,7 @@ import * as os from 'os';
28
28
  import * as yaml from 'yaml';
29
29
  import { ensureLockTarget, atomicWriteFileSync, withFileLock } from './fs-atomic.js';
30
30
  import { SEEDED_REGISTRIES } from './types.js';
31
+ import { machineId } from './machine-id.js';
31
32
  const HOME = process.env.HOME ?? os.homedir();
32
33
  /**
33
34
  * Compare two filesystem paths for identity, resolving symlinks and (on
@@ -87,6 +88,7 @@ const RUNS_DIR = path.join(HISTORY_DIR, 'runs');
87
88
  const TEAMS_AGENTS_DIR = path.join(HISTORY_DIR, 'teams', 'agents');
88
89
  const BACKUPS_DIR = path.join(HISTORY_DIR, 'backups');
89
90
  const TRASH_DIR = path.join(HISTORY_DIR, 'trash');
91
+ const MAILBOX_DIR = path.join(HISTORY_DIR, 'mailbox');
90
92
  // Cache bucket (regenerable).
91
93
  const SHIMS_DIR = path.join(CACHE_DIR, 'shims');
92
94
  const HOOK_SHIMS_DIR = path.join(SHIMS_DIR, 'hooks');
@@ -309,6 +311,8 @@ export function getProjectRoutinesDir(cwd = process.cwd()) {
309
311
  }
310
312
  /** Path to routine execution logs (~/.agents/.history/runs/). */
311
313
  export function getRunsDir() { return RUNS_DIR; }
314
+ /** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
315
+ export function getMailboxRootDir() { return MAILBOX_DIR; }
312
316
  /** Path to installed agent CLI binaries (~/.agents/.history/versions/). */
313
317
  export function getVersionsDir() { return VERSIONS_DIR; }
314
318
  /** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
@@ -495,13 +499,37 @@ function safeMtimeMs(filePath) {
495
499
  return 0;
496
500
  }
497
501
  }
498
- /** Compute the combined cache stamp for the user + system agents.yaml files. */
502
+ /**
503
+ * Per-device machine-local version pins — `~/.agents/devices/<machine>/agents.yaml`.
504
+ * Committed and synced, but each machine only ever writes its OWN folder, so
505
+ * pulls never conflict. `<machine>` = machineId() (Tailscale-aligned short name).
506
+ */
507
+ export function getDeviceMetaPath() {
508
+ return path.join(USER_AGENTS_DIR, 'devices', machineId(), 'agents.yaml');
509
+ }
510
+ /**
511
+ * Machine-local per-version resource tracking — `~/.agents/.history/version-resources.json`.
512
+ * Gitignored (under .history/) and regenerable; never synced.
513
+ */
514
+ export function getVersionResourcesPath() {
515
+ return path.join(HISTORY_DIR, 'version-resources.json');
516
+ }
517
+ /**
518
+ * Combined cache stamp across all four Meta sources: central + system
519
+ * agents.yaml, this machine's device pins, and the version-resources tracking.
520
+ * A delimited string, NOT a numeric sum — summing down-scaled epoch-ms values
521
+ * loses precision (float64 rounds sub-unit terms away at ~1.75e12), so a change
522
+ * in any one file must contribute at full resolution.
523
+ */
499
524
  function currentMetaStamp() {
500
- return safeMtimeMs(META_FILE) + safeMtimeMs(SYSTEM_META_FILE) * 1e-3;
525
+ return safeMtimeMs(META_FILE)
526
+ + '|' + safeMtimeMs(SYSTEM_META_FILE)
527
+ + '|' + safeMtimeMs(getDeviceMetaPath())
528
+ + '|' + safeMtimeMs(getVersionResourcesPath());
501
529
  }
502
530
  /** Memoize a parsed Meta against the current file mtimes. */
503
531
  function rememberMeta(meta) {
504
- metaCache = { mtime: currentMetaStamp(), meta };
532
+ metaCache = { stamp: currentMetaStamp(), meta };
505
533
  return meta;
506
534
  }
507
535
  function withMetaLock(fn) {
@@ -526,11 +554,78 @@ function withMetaLock(fn) {
526
554
  }
527
555
  });
528
556
  }
557
+ /** Atomic write only when the on-disk content differs — avoids needless mtime
558
+ * bumps (which would thrash the meta cache) on no-op field routing. */
559
+ function writeIfChanged(filePath, content) {
560
+ let current = null;
561
+ try {
562
+ current = fs.readFileSync(filePath, 'utf-8');
563
+ }
564
+ catch { /* absent */ }
565
+ if (current === content)
566
+ return;
567
+ atomicWriteFileSync(filePath, content);
568
+ }
569
+ /**
570
+ * Partition the in-memory Meta across three files by sync-domain:
571
+ * - central `~/.agents/agents.yaml` — portable, everything else
572
+ * - device `~/.agents/devices/<machine>/agents.yaml` — `agents:` pins (per-device)
573
+ * - history `~/.agents/.history/version-resources.json` — `versions:` (machine-local)
574
+ * All callers funnel through writeMeta → here, so nothing else changes. Empty
575
+ * `agents:` / `versions:` are not written (no empty committed files).
576
+ */
529
577
  function writeMetaUnlocked(meta) {
530
- const content = META_HEADER + yaml.stringify(meta);
531
- atomicWriteFileSync(META_FILE, content);
578
+ const { agents, versions, ...central } = meta;
579
+ // Write the machine-local files FIRST, then strip central — so a crash mid-write
580
+ // never removes pins/versions from central before they're persisted elsewhere.
581
+ const devicePath = getDeviceMetaPath();
582
+ if (agents && Object.keys(agents).length > 0) {
583
+ fs.mkdirSync(path.dirname(devicePath), { recursive: true });
584
+ writeIfChanged(devicePath, META_HEADER + yaml.stringify({ agents }));
585
+ }
586
+ else if (fs.existsSync(devicePath)) {
587
+ // Every pin was cleared. Persist the emptied map instead of skipping the
588
+ // write — otherwise the stale device file survives and overlayMachineLocal
589
+ // re-applies the removed pin on the next read, leaving a dangling default
590
+ // (e.g. a launcher pointing at a version that was just uninstalled).
591
+ writeIfChanged(devicePath, META_HEADER + yaml.stringify({ agents: {} }));
592
+ }
593
+ if (versions && Object.keys(versions).length > 0) {
594
+ const vrPath = getVersionResourcesPath();
595
+ fs.mkdirSync(path.dirname(vrPath), { recursive: true });
596
+ writeIfChanged(vrPath, JSON.stringify(versions, null, 2) + '\n');
597
+ }
598
+ writeIfChanged(META_FILE, META_HEADER + yaml.stringify(central));
532
599
  metaCache = null;
533
600
  }
601
+ /**
602
+ * Overlay this machine's local state onto a central-portable Meta:
603
+ * - `agents:` from the device file (device wins; the union both preserves the
604
+ * one-level merge and self-heals a pre-migration central that still has pins)
605
+ * - `versions:` from the history JSON (wholesale replace; falls back to
606
+ * whatever central carried when the history file doesn't exist yet)
607
+ */
608
+ function overlayMachineLocal(meta) {
609
+ const devicePath = getDeviceMetaPath();
610
+ if (fs.existsSync(devicePath)) {
611
+ try {
612
+ const dm = yaml.parse(fs.readFileSync(devicePath, 'utf-8'));
613
+ if (dm?.agents)
614
+ meta.agents = { ...meta.agents, ...dm.agents };
615
+ }
616
+ catch { /* ignore malformed device file */ }
617
+ }
618
+ const vrPath = getVersionResourcesPath();
619
+ if (fs.existsSync(vrPath)) {
620
+ try {
621
+ const vr = JSON.parse(fs.readFileSync(vrPath, 'utf-8'));
622
+ if (vr)
623
+ meta.versions = vr;
624
+ }
625
+ catch { /* ignore malformed history file */ }
626
+ }
627
+ return meta;
628
+ }
534
629
  function applyRegistrySeeds(meta) {
535
630
  const seeded = new Set(meta.seededPresets || []);
536
631
  let changed = false;
@@ -590,10 +685,7 @@ export function readMeta() {
590
685
  // what we last parsed. Reduces N readMeta calls per CLI invocation to ~2 stat
591
686
  // syscalls plus an in-memory object spread.
592
687
  if (metaCache) {
593
- const userMtime = safeMtimeMs(META_FILE);
594
- const systemMtime = safeMtimeMs(SYSTEM_META_FILE);
595
- const stamp = userMtime + systemMtime * 1e-3;
596
- if (stamp === metaCache.mtime) {
688
+ if (currentMetaStamp() === metaCache.stamp) {
597
689
  return metaCache.meta;
598
690
  }
599
691
  }
@@ -666,6 +758,7 @@ export function readMeta() {
666
758
  ...userMeta?.registries,
667
759
  };
668
760
  }
761
+ overlayMachineLocal(meta);
669
762
  if (applyRegistrySeeds(meta)) {
670
763
  writeMeta(meta);
671
764
  return rememberMeta(meta);
@@ -673,6 +766,7 @@ export function readMeta() {
673
766
  return rememberMeta(meta);
674
767
  }
675
768
  const meta = createDefaultMeta();
769
+ overlayMachineLocal(meta);
676
770
  if (applyRegistrySeeds(meta)) {
677
771
  writeMeta(meta);
678
772
  }
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Umbrella `agents sync` orchestration — "make this machine current".
3
3
  *
4
- * Bare `agents sync` fetches remote state (config repos + secrets + sessions)
5
- * then reconciles it into every installed agent's version home. Each stage is
6
- * an existing exported library function; this module only sequences them and
7
- * decides from the flags which stages run (`planUmbrellaStages`). The
8
- * planner is pure so the flag matrix is unit-tested without any I/O.
4
+ * Bare `agents sync` fetches the config repos then reconciles them into every
5
+ * installed agent's version home. Secrets and sessions are opt-in stages
6
+ * (`--secrets` / `--sessions`) see `planUmbrellaStages` for why they're off by
7
+ * default. Each stage is an existing exported library function; this module only
8
+ * sequences them and decides — from the flags which stages run. The planner is
9
+ * pure so the flag matrix is unit-tested without any I/O.
9
10
  *
10
11
  * Stage backends:
11
12
  * repos -> git pull of ~/.agents + enabled ~/.agents-* extras (pullRepo)
@@ -32,11 +33,17 @@ export interface UmbrellaPlan {
32
33
  }
33
34
  /**
34
35
  * Decide which stages run. Pure — no I/O. Semantics:
35
- * bare (no flags) fetch all three, then reconcile
36
+ * bare (no flags) fetch repos, then reconcile
36
37
  * --local reconcile only, no fetch
37
- * --cloud fetch (all, or the selected subset), skip reconcile
38
+ * --cloud fetch repos (or the selected subset), skip reconcile
38
39
  * --repos/--secrets/... fetch only the selected types, then reconcile
39
40
  * `--local` wins over everything; `--cloud` suppresses reconcile.
41
+ *
42
+ * Secrets and sessions are NOT part of the bare default — they are opt-in via
43
+ * `--secrets` / `--sessions`. Pulling every secret bundle onto the machine on a
44
+ * bare `agents sync` is more blast radius than the verb should carry by
45
+ * default, and session transcripts are queryable on demand (`agents sessions
46
+ * --host <machine>`) so they don't need eager mirroring.
40
47
  */
41
48
  export declare function planUmbrellaStages(f: UmbrellaFlags): UmbrellaPlan;
42
49
  export interface UmbrellaResult {
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Umbrella `agents sync` orchestration — "make this machine current".
3
3
  *
4
- * Bare `agents sync` fetches remote state (config repos + secrets + sessions)
5
- * then reconciles it into every installed agent's version home. Each stage is
6
- * an existing exported library function; this module only sequences them and
7
- * decides from the flags which stages run (`planUmbrellaStages`). The
8
- * planner is pure so the flag matrix is unit-tested without any I/O.
4
+ * Bare `agents sync` fetches the config repos then reconciles them into every
5
+ * installed agent's version home. Secrets and sessions are opt-in stages
6
+ * (`--secrets` / `--sessions`) see `planUmbrellaStages` for why they're off by
7
+ * default. Each stage is an existing exported library function; this module only
8
+ * sequences them and decides — from the flags which stages run. The planner is
9
+ * pure so the flag matrix is unit-tested without any I/O.
9
10
  *
10
11
  * Stage backends:
11
12
  * repos -> git pull of ~/.agents + enabled ~/.agents-* extras (pullRepo)
@@ -20,11 +21,17 @@ import { getUserAgentsDir, getEnabledExtraRepos } from './state.js';
20
21
  import { listRemoteBundles, pullBundle } from './secrets/sync.js';
21
22
  /**
22
23
  * Decide which stages run. Pure — no I/O. Semantics:
23
- * bare (no flags) fetch all three, then reconcile
24
+ * bare (no flags) fetch repos, then reconcile
24
25
  * --local reconcile only, no fetch
25
- * --cloud fetch (all, or the selected subset), skip reconcile
26
+ * --cloud fetch repos (or the selected subset), skip reconcile
26
27
  * --repos/--secrets/... fetch only the selected types, then reconcile
27
28
  * `--local` wins over everything; `--cloud` suppresses reconcile.
29
+ *
30
+ * Secrets and sessions are NOT part of the bare default — they are opt-in via
31
+ * `--secrets` / `--sessions`. Pulling every secret bundle onto the machine on a
32
+ * bare `agents sync` is more blast radius than the verb should carry by
33
+ * default, and session transcripts are queryable on demand (`agents sessions
34
+ * --host <machine>`) so they don't need eager mirroring.
28
35
  */
29
36
  export function planUmbrellaStages(f) {
30
37
  if (f.local) {
@@ -39,8 +46,9 @@ export function planUmbrellaStages(f) {
39
46
  reconcile: !f.cloud,
40
47
  };
41
48
  }
42
- // No per-type selector: bare = all + reconcile; --cloud = all, no reconcile.
43
- return { fetchRepos: true, fetchSecrets: true, fetchSessions: true, reconcile: !f.cloud };
49
+ // No per-type selector: bare = repos + reconcile; --cloud = repos, no
50
+ // reconcile. Secrets/sessions stay off unless explicitly selected above.
51
+ return { fetchRepos: true, fetchSecrets: false, fetchSessions: false, reconcile: !f.cloud };
44
52
  }
45
53
  /**
46
54
  * Execute the planned stages in order: repos -> secrets -> sessions -> reconcile.