@phnx-labs/agents-cli 1.22.34 → 1.22.37

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 (493) hide show
  1. package/CHANGELOG.md +672 -2
  2. package/README.md +82 -35
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.d.ts +15 -0
  5. package/dist/bootstrap.js +1211 -0
  6. package/dist/commands/accounts.d.ts +4 -11
  7. package/dist/commands/accounts.js +159 -27
  8. package/dist/commands/attach.js +17 -4
  9. package/dist/commands/audit.d.ts +8 -8
  10. package/dist/commands/audit.js +57 -47
  11. package/dist/commands/browser.js +15 -11
  12. package/dist/commands/cloud.js +1 -1
  13. package/dist/commands/commands.js +0 -11
  14. package/dist/commands/computer-actions.js +33 -0
  15. package/dist/commands/computer-sessions-picker.d.ts +21 -0
  16. package/dist/commands/computer-sessions-picker.js +141 -0
  17. package/dist/commands/computer.d.ts +16 -0
  18. package/dist/commands/computer.js +37 -0
  19. package/dist/commands/config.d.ts +17 -0
  20. package/dist/commands/config.js +401 -0
  21. package/dist/commands/cp.d.ts +84 -0
  22. package/dist/commands/cp.js +231 -0
  23. package/dist/commands/daemon.js +243 -14
  24. package/dist/commands/detach-core.d.ts +1 -1
  25. package/dist/commands/detach-core.js +12 -2
  26. package/dist/commands/detach.js +1 -1
  27. package/dist/commands/doctor.js +64 -5
  28. package/dist/commands/events.d.ts +24 -12
  29. package/dist/commands/events.js +144 -28
  30. package/dist/commands/exec.js +85 -41
  31. package/dist/commands/feed.d.ts +1 -1
  32. package/dist/commands/feed.js +16 -8
  33. package/dist/commands/focus.d.ts +33 -1
  34. package/dist/commands/focus.js +177 -17
  35. package/dist/commands/funnel.d.ts +1 -1
  36. package/dist/commands/go.js +9 -8
  37. package/dist/commands/harness-wizard.d.ts +1 -0
  38. package/dist/commands/harness-wizard.js +18 -20
  39. package/dist/commands/harness.d.ts +2 -0
  40. package/dist/commands/harness.js +59 -26
  41. package/dist/commands/hooks.js +0 -11
  42. package/dist/commands/inspect.d.ts +94 -1
  43. package/dist/commands/inspect.js +866 -40
  44. package/dist/commands/lease.d.ts +4 -4
  45. package/dist/commands/lease.js +6 -6
  46. package/dist/commands/login.js +0 -13
  47. package/dist/commands/logs.d.ts +2 -2
  48. package/dist/commands/logs.js +2 -2
  49. package/dist/commands/mcp.js +7 -2
  50. package/dist/commands/memory.js +7 -2
  51. package/dist/commands/message.d.ts +1 -1
  52. package/dist/commands/message.js +37 -6
  53. package/dist/commands/models.js +4 -0
  54. package/dist/commands/monitors.d.ts +0 -1
  55. package/dist/commands/monitors.js +199 -16
  56. package/dist/commands/open.d.ts +18 -0
  57. package/dist/commands/open.js +57 -0
  58. package/dist/commands/packages.js +2 -107
  59. package/dist/commands/plugins.js +6 -3
  60. package/dist/commands/profiles.d.ts +1 -0
  61. package/dist/commands/profiles.js +16 -160
  62. package/dist/commands/projects.d.ts +28 -0
  63. package/dist/commands/projects.js +125 -5
  64. package/dist/commands/reconnect.js +9 -3
  65. package/dist/commands/repo.js +42 -1
  66. package/dist/commands/resource-view.d.ts +17 -0
  67. package/dist/commands/resource-view.js +36 -14
  68. package/dist/commands/resume.js +9 -2
  69. package/dist/commands/routines.d.ts +0 -2
  70. package/dist/commands/routines.js +473 -289
  71. package/dist/commands/secrets.js +52 -9
  72. package/dist/commands/sessions-export.d.ts +47 -0
  73. package/dist/commands/sessions-export.js +116 -6
  74. package/dist/commands/sessions-import.d.ts +17 -0
  75. package/dist/commands/sessions-import.js +114 -6
  76. package/dist/commands/sessions-migrate.d.ts +27 -0
  77. package/dist/commands/sessions-migrate.js +50 -9
  78. package/dist/commands/sessions-picker.js +23 -4
  79. package/dist/commands/sessions-resume.d.ts +5 -3
  80. package/dist/commands/sessions-resume.js +30 -8
  81. package/dist/commands/sessions-watch.d.ts +2 -0
  82. package/dist/commands/sessions-watch.js +53 -0
  83. package/dist/commands/sessions.d.ts +66 -3
  84. package/dist/commands/sessions.js +298 -47
  85. package/dist/commands/set.d.ts +1 -1
  86. package/dist/commands/set.js +2 -2
  87. package/dist/commands/setup-browser.js +2 -2
  88. package/dist/commands/setup-mine.js +0 -1
  89. package/dist/commands/setup-preferences.js +1 -1
  90. package/dist/commands/setup-share.js +34 -4
  91. package/dist/commands/setup.d.ts +5 -0
  92. package/dist/commands/setup.js +26 -0
  93. package/dist/commands/share.d.ts +85 -0
  94. package/dist/commands/share.js +379 -8
  95. package/dist/commands/skills.js +0 -11
  96. package/dist/commands/snapshot.js +1 -1
  97. package/dist/commands/ssh.d.ts +18 -0
  98. package/dist/commands/ssh.js +549 -210
  99. package/dist/commands/sync.d.ts +5 -0
  100. package/dist/commands/sync.js +212 -32
  101. package/dist/commands/teams.d.ts +13 -0
  102. package/dist/commands/teams.js +187 -30
  103. package/dist/commands/tickets.d.ts +2 -0
  104. package/dist/commands/tickets.js +43 -0
  105. package/dist/commands/update.js +2 -4
  106. package/dist/commands/usage.js +5 -3
  107. package/dist/commands/versions.js +11 -5
  108. package/dist/commands/view.js +15 -7
  109. package/dist/commands/webhook.d.ts +2 -2
  110. package/dist/commands/webhook.js +7 -7
  111. package/dist/index.d.ts +18 -2
  112. package/dist/index.js +53 -1264
  113. package/dist/lib/account-catalog.d.ts +18 -0
  114. package/dist/lib/account-catalog.js +38 -0
  115. package/dist/lib/account-provider-registry.d.ts +18 -0
  116. package/dist/lib/account-provider-registry.js +59 -0
  117. package/dist/lib/account-registry.d.ts +40 -0
  118. package/dist/lib/account-registry.js +239 -0
  119. package/dist/lib/account-schema.d.ts +44 -0
  120. package/dist/lib/account-schema.js +91 -0
  121. package/dist/lib/account-state-service.d.ts +21 -0
  122. package/dist/lib/account-state-service.js +60 -0
  123. package/dist/lib/activity.d.ts +1 -1
  124. package/dist/lib/activity.js +8 -1
  125. package/dist/lib/add-dir.d.ts +80 -0
  126. package/dist/lib/add-dir.js +241 -0
  127. package/dist/lib/agent-cli-commands.d.ts +11 -0
  128. package/dist/lib/agent-cli-commands.js +30 -0
  129. package/dist/lib/agents.js +44 -36
  130. package/dist/lib/answer-router.js +1 -1
  131. package/dist/lib/audit/log.d.ts +10 -27
  132. package/dist/lib/audit/log.js +20 -33
  133. package/dist/lib/auth-health.d.ts +9 -2
  134. package/dist/lib/auth-health.js +42 -9
  135. package/dist/lib/auto-pull-worker.js +7 -3
  136. package/dist/lib/auto-pull.d.ts +44 -3
  137. package/dist/lib/auto-pull.js +88 -5
  138. package/dist/lib/binary-shadow.d.ts +28 -0
  139. package/dist/lib/binary-shadow.js +121 -0
  140. package/dist/lib/brand.js +9 -3
  141. package/dist/lib/browser/ipc.d.ts +30 -0
  142. package/dist/lib/browser/ipc.js +81 -4
  143. package/dist/lib/browser/profiles.d.ts +3 -2
  144. package/dist/lib/browser/profiles.js +65 -22
  145. package/dist/lib/browser/service.d.ts +4 -0
  146. package/dist/lib/browser/service.js +30 -3
  147. package/dist/lib/browser/sessions-list.d.ts +22 -2
  148. package/dist/lib/browser/sessions-list.js +54 -6
  149. package/dist/lib/browser/stream.d.ts +2 -0
  150. package/dist/lib/browser/stream.js +1 -0
  151. package/dist/lib/browser/types.d.ts +19 -0
  152. package/dist/lib/byok-usage.d.ts +9 -2
  153. package/dist/lib/byok-usage.js +133 -31
  154. package/dist/lib/cli-entry.d.ts +11 -0
  155. package/dist/lib/cli-entry.js +46 -0
  156. package/dist/lib/cloud/host.d.ts +1 -1
  157. package/dist/lib/cloud/host.js +2 -2
  158. package/dist/lib/cloud/registry.js +1 -1
  159. package/dist/lib/cloud/types.d.ts +1 -1
  160. package/dist/lib/codex-policy.d.ts +9 -1
  161. package/dist/lib/codex-policy.js +17 -2
  162. package/dist/lib/commands.d.ts +2 -0
  163. package/dist/lib/commands.js +2 -0
  164. package/dist/lib/computer/sessions-list.d.ts +150 -0
  165. package/dist/lib/computer/sessions-list.js +363 -0
  166. package/dist/lib/config-keys.d.ts +86 -0
  167. package/dist/lib/config-keys.js +193 -0
  168. package/dist/lib/config-machine-keys.d.ts +12 -0
  169. package/dist/lib/config-machine-keys.js +17 -0
  170. package/dist/lib/config-transfer.js +3 -4
  171. package/dist/lib/crabbox/cli.d.ts +2 -2
  172. package/dist/lib/crabbox/cli.js +3 -3
  173. package/dist/lib/daemon-health.d.ts +22 -0
  174. package/dist/lib/daemon-health.js +33 -3
  175. package/dist/lib/daemon-services.d.ts +50 -0
  176. package/dist/lib/daemon-services.js +147 -0
  177. package/dist/lib/daemon-ticks.d.ts +35 -66
  178. package/dist/lib/daemon-ticks.js +77 -159
  179. package/dist/lib/daemon.d.ts +72 -4
  180. package/dist/lib/daemon.js +792 -202
  181. package/dist/lib/deeplink/register.d.ts +49 -0
  182. package/dist/lib/deeplink/register.js +253 -0
  183. package/dist/lib/deeplink/url.d.ts +34 -0
  184. package/dist/lib/deeplink/url.js +78 -0
  185. package/dist/lib/device-config.d.ts +124 -29
  186. package/dist/lib/device-config.js +346 -141
  187. package/dist/lib/devices/config-migration.d.ts +42 -0
  188. package/dist/lib/devices/config-migration.js +203 -0
  189. package/dist/lib/devices/connect.js +14 -7
  190. package/dist/lib/devices/doctor-findings.d.ts +4 -1
  191. package/dist/lib/devices/doctor-findings.js +20 -1
  192. package/dist/lib/devices/harness-inventory.d.ts +11 -2
  193. package/dist/lib/devices/harness-inventory.js +0 -0
  194. package/dist/lib/devices/health.d.ts +1 -1
  195. package/dist/lib/devices/health.js +1 -1
  196. package/dist/lib/devices/pending.d.ts +22 -1
  197. package/dist/lib/devices/pending.js +67 -2
  198. package/dist/lib/devices/registry.d.ts +0 -25
  199. package/dist/lib/devices/registry.js +2 -83
  200. package/dist/lib/devices/resolve-profile.d.ts +20 -0
  201. package/dist/lib/devices/resolve-profile.js +44 -0
  202. package/dist/lib/devices/rollout-verify.d.ts +101 -0
  203. package/dist/lib/devices/rollout-verify.js +193 -0
  204. package/dist/lib/devices/ssh-config.js +10 -6
  205. package/dist/lib/event-families.d.ts +28 -0
  206. package/dist/lib/event-families.js +159 -0
  207. package/dist/lib/event-stream.d.ts +9 -1
  208. package/dist/lib/event-stream.js +26 -9
  209. package/dist/lib/events.d.ts +9 -1
  210. package/dist/lib/events.js +21 -6
  211. package/dist/lib/exec.d.ts +30 -0
  212. package/dist/lib/exec.js +220 -122
  213. package/dist/lib/feed-broadcast.d.ts +25 -0
  214. package/dist/lib/feed-broadcast.js +33 -0
  215. package/dist/lib/feed.d.ts +1 -1
  216. package/dist/lib/feed.js +12 -2
  217. package/dist/lib/fleet/capture.js +15 -0
  218. package/dist/lib/fleet/manifest.js +9 -0
  219. package/dist/lib/fleet/types.d.ts +9 -0
  220. package/dist/lib/fleet-status.js +10 -8
  221. package/dist/lib/git.d.ts +20 -6
  222. package/dist/lib/git.js +121 -17
  223. package/dist/lib/hosts/dispatch.d.ts +8 -3
  224. package/dist/lib/hosts/dispatch.js +29 -3
  225. package/dist/lib/hosts/logs.d.ts +2 -2
  226. package/dist/lib/hosts/logs.js +2 -2
  227. package/dist/lib/hosts/option.js +1 -1
  228. package/dist/lib/hosts/passthrough.d.ts +2 -3
  229. package/dist/lib/hosts/passthrough.js +10 -24
  230. package/dist/lib/hosts/progress.js +2 -2
  231. package/dist/lib/hosts/providers/devices.d.ts +3 -3
  232. package/dist/lib/hosts/providers/devices.js +18 -12
  233. package/dist/lib/hosts/ready.d.ts +5 -1
  234. package/dist/lib/hosts/ready.js +9 -3
  235. package/dist/lib/hosts/reconcile.d.ts +2 -2
  236. package/dist/lib/hosts/reconcile.js +2 -2
  237. package/dist/lib/hosts/registry.js +14 -9
  238. package/dist/lib/hosts/remote-cmd.js +1 -1
  239. package/dist/lib/hosts/remote-os.d.ts +9 -6
  240. package/dist/lib/hosts/remote-os.js +13 -6
  241. package/dist/lib/hosts/routing-flag.d.ts +23 -0
  242. package/dist/lib/hosts/routing-flag.js +41 -0
  243. package/dist/lib/hosts/run-target.js +1 -1
  244. package/dist/lib/hosts/session-index.js +1 -1
  245. package/dist/lib/hosts/tasks.d.ts +6 -6
  246. package/dist/lib/hosts/tasks.js +4 -4
  247. package/dist/lib/hosts/types.js +1 -2
  248. package/dist/lib/installations/resolve.js +2 -8
  249. package/dist/lib/isolation-boundary-report.js +0 -1
  250. package/dist/lib/linear-cache.d.ts +7 -0
  251. package/dist/lib/linear-cache.js +31 -4
  252. package/dist/lib/linear-project-counts.d.ts +1 -1
  253. package/dist/lib/linear-project-counts.js +2 -3
  254. package/dist/lib/linear-projects.js +1 -1
  255. package/dist/lib/loop.d.ts +2 -2
  256. package/dist/lib/loop.js +1 -1
  257. package/dist/lib/mailbox-target.d.ts +1 -1
  258. package/dist/lib/mailbox-target.js +1 -1
  259. package/dist/lib/manifest.js +7 -4
  260. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  261. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  262. package/dist/lib/menubar/snapshot.d.ts +3 -2
  263. package/dist/lib/menubar/snapshot.js +6 -2
  264. package/dist/lib/migrate-fold.d.ts +33 -0
  265. package/dist/lib/migrate-fold.js +130 -0
  266. package/dist/lib/migrate.d.ts +34 -34
  267. package/dist/lib/migrate.js +106 -128
  268. package/dist/lib/model-tier-overrides.d.ts +1 -1
  269. package/dist/lib/model-tier-overrides.js +15 -2
  270. package/dist/lib/monitors/config.d.ts +17 -3
  271. package/dist/lib/monitors/config.js +52 -18
  272. package/dist/lib/monitors/engine.d.ts +32 -1
  273. package/dist/lib/monitors/engine.js +94 -11
  274. package/dist/lib/monitors/fingerprint.d.ts +46 -0
  275. package/dist/lib/monitors/fingerprint.js +100 -0
  276. package/dist/lib/monitors/remote.d.ts +47 -0
  277. package/dist/lib/monitors/remote.js +81 -0
  278. package/dist/lib/monitors/state.d.ts +39 -0
  279. package/dist/lib/monitors/state.js +56 -0
  280. package/dist/lib/onepassword.d.ts +17 -0
  281. package/dist/lib/onepassword.js +52 -22
  282. package/dist/lib/open-url.js +1 -1
  283. package/dist/lib/picker.d.ts +14 -2
  284. package/dist/lib/picker.js +35 -13
  285. package/dist/lib/placement.d.ts +1 -1
  286. package/dist/lib/placement.js +1 -1
  287. package/dist/lib/pr-land-detach.d.ts +32 -0
  288. package/dist/lib/pr-land-detach.js +79 -0
  289. package/dist/lib/profiles-presets.js +13 -0
  290. package/dist/lib/profiles.d.ts +2 -0
  291. package/dist/lib/profiles.js +44 -1
  292. package/dist/lib/project-focus.d.ts +1 -1
  293. package/dist/lib/project-focus.js +1 -1
  294. package/dist/lib/project-key.d.ts +17 -0
  295. package/dist/lib/project-key.js +26 -0
  296. package/dist/lib/project-probe.d.ts +4 -1
  297. package/dist/lib/project-probe.js +5 -4
  298. package/dist/lib/project-resources.js +2 -18
  299. package/dist/lib/project-root.d.ts +16 -0
  300. package/dist/lib/project-root.js +20 -2
  301. package/dist/lib/projects.d.ts +20 -1
  302. package/dist/lib/projects.js +78 -1
  303. package/dist/lib/refresh-coordinator.d.ts +24 -0
  304. package/dist/lib/refresh-coordinator.js +52 -0
  305. package/dist/lib/registry.d.ts +2 -2
  306. package/dist/lib/registry.js +1 -1
  307. package/dist/lib/resource-aliases.d.ts +13 -0
  308. package/dist/lib/resource-aliases.js +26 -0
  309. package/dist/lib/resources/types.d.ts +1 -1
  310. package/dist/lib/resources.d.ts +16 -0
  311. package/dist/lib/resources.js +66 -10
  312. package/dist/lib/rotate.d.ts +3 -4
  313. package/dist/lib/rotate.js +4 -5
  314. package/dist/lib/routine-activation.d.ts +22 -2
  315. package/dist/lib/routine-activation.js +63 -16
  316. package/dist/lib/routine-context.d.ts +9 -2
  317. package/dist/lib/routine-context.js +21 -6
  318. package/dist/lib/routines-project.d.ts +58 -48
  319. package/dist/lib/routines-project.js +190 -159
  320. package/dist/lib/routines.d.ts +41 -6
  321. package/dist/lib/routines.js +92 -34
  322. package/dist/lib/run-defaults.d.ts +5 -0
  323. package/dist/lib/run-defaults.js +11 -1
  324. package/dist/lib/runner.d.ts +10 -8
  325. package/dist/lib/runner.js +122 -39
  326. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  327. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  328. package/dist/lib/secrets/agent.d.ts +10 -3
  329. package/dist/lib/secrets/agent.js +43 -8
  330. package/dist/lib/secrets/bundles.d.ts +1 -0
  331. package/dist/lib/secrets/bundles.js +92 -80
  332. package/dist/lib/secrets/index.js +32 -9
  333. package/dist/lib/secrets/install-helper.d.ts +4 -4
  334. package/dist/lib/secrets/install-helper.js +4 -4
  335. package/dist/lib/secrets/push.d.ts +40 -6
  336. package/dist/lib/secrets/push.js +58 -11
  337. package/dist/lib/secrets/read-backoff.d.ts +1 -1
  338. package/dist/lib/secrets/read-backoff.js +1 -1
  339. package/dist/lib/secrets/remote.d.ts +24 -0
  340. package/dist/lib/secrets/remote.js +54 -4
  341. package/dist/lib/self-update.d.ts +108 -0
  342. package/dist/lib/self-update.js +276 -1
  343. package/dist/lib/session/active.d.ts +89 -5
  344. package/dist/lib/session/active.js +165 -10
  345. package/dist/lib/session/actor-sidecar.d.ts +1 -1
  346. package/dist/lib/session/actor-sidecar.js +2 -1
  347. package/dist/lib/session/db.d.ts +176 -2
  348. package/dist/lib/session/db.js +484 -17
  349. package/dist/lib/session/discover.js +27 -4
  350. package/dist/lib/session/hook-sessions.js +2 -2
  351. package/dist/lib/session/host-link.d.ts +2 -2
  352. package/dist/lib/session/host-link.js +2 -2
  353. package/dist/lib/session/pid-registry.d.ts +21 -0
  354. package/dist/lib/session/pid-registry.js +57 -0
  355. package/dist/lib/session/remote-active.js +15 -1
  356. package/dist/lib/session/remote.js +2 -2
  357. package/dist/lib/session/session-cache.d.ts +14 -7
  358. package/dist/lib/session/session-cache.js +51 -9
  359. package/dist/lib/session/state.d.ts +2 -2
  360. package/dist/lib/session/state.js +1 -1
  361. package/dist/lib/session/sync/config.d.ts +10 -10
  362. package/dist/lib/session/sync/config.js +10 -10
  363. package/dist/lib/session/sync/r2.d.ts +34 -0
  364. package/dist/lib/session/sync/r2.js +123 -0
  365. package/dist/lib/session/throughput.d.ts +1 -1
  366. package/dist/lib/session/throughput.js +1 -1
  367. package/dist/lib/session/types.d.ts +23 -1
  368. package/dist/lib/session/types.js +15 -0
  369. package/dist/lib/session/viewing-in.d.ts +1 -1
  370. package/dist/lib/session/viewing-in.js +1 -1
  371. package/dist/lib/session/watch.d.ts +101 -0
  372. package/dist/lib/session/watch.js +242 -0
  373. package/dist/lib/share/config.d.ts +4 -0
  374. package/dist/lib/share/config.js +1 -0
  375. package/dist/lib/share/delete.d.ts +93 -0
  376. package/dist/lib/share/delete.js +127 -0
  377. package/dist/lib/share/provision.d.ts +38 -0
  378. package/dist/lib/share/provision.js +50 -0
  379. package/dist/lib/share/publish.d.ts +40 -1
  380. package/dist/lib/share/publish.js +116 -3
  381. package/dist/lib/share/worker-template.js +71 -6
  382. package/dist/lib/shims.d.ts +17 -3
  383. package/dist/lib/shims.js +136 -27
  384. package/dist/lib/signin-badge.js +3 -2
  385. package/dist/lib/skills.js +2 -0
  386. package/dist/lib/smart-launch.d.ts +8 -8
  387. package/dist/lib/smart-launch.js +80 -59
  388. package/dist/lib/ssh-exec.d.ts +1 -1
  389. package/dist/lib/ssh-exec.js +1 -1
  390. package/dist/lib/staleness/prune.d.ts +88 -0
  391. package/dist/lib/staleness/prune.js +69 -0
  392. package/dist/lib/staleness/registry.d.ts +1 -1
  393. package/dist/lib/staleness/writers/commands.js +34 -2
  394. package/dist/lib/staleness/writers/hooks.js +16 -0
  395. package/dist/lib/staleness/writers/kinds.d.ts +6 -0
  396. package/dist/lib/staleness/writers/skills.js +19 -0
  397. package/dist/lib/staleness/writers/subagents.d.ts +2 -3
  398. package/dist/lib/staleness/writers/subagents.js +0 -4
  399. package/dist/lib/staleness/writers/types.d.ts +32 -0
  400. package/dist/lib/startup/command-registry.d.ts +6 -12
  401. package/dist/lib/startup/command-registry.js +14 -31
  402. package/dist/lib/startup/dev-build.d.ts +10 -0
  403. package/dist/lib/startup/dev-build.js +13 -1
  404. package/dist/lib/startup/spellcheck.d.ts +18 -0
  405. package/dist/lib/startup/spellcheck.js +45 -0
  406. package/dist/lib/state.d.ts +36 -9
  407. package/dist/lib/state.js +96 -46
  408. package/dist/lib/subagents-registry.d.ts +0 -7
  409. package/dist/lib/subagents-registry.js +38 -65
  410. package/dist/lib/subagents.d.ts +0 -33
  411. package/dist/lib/subagents.js +0 -75
  412. package/dist/lib/sync-umbrella.js +1 -1
  413. package/dist/lib/teams/agents.d.ts +41 -1
  414. package/dist/lib/teams/agents.js +187 -9
  415. package/dist/lib/teams/api.d.ts +11 -1
  416. package/dist/lib/teams/api.js +11 -2
  417. package/dist/lib/teams/delivery.d.ts +41 -0
  418. package/dist/lib/teams/delivery.js +60 -0
  419. package/dist/lib/teams/registry.d.ts +14 -0
  420. package/dist/lib/teams/registry.js +51 -1
  421. package/dist/lib/teams/remoteWorktree.d.ts +19 -0
  422. package/dist/lib/teams/remoteWorktree.js +29 -0
  423. package/dist/lib/teams/scheduler.d.ts +1 -1
  424. package/dist/lib/teams/scheduler.js +1 -1
  425. package/dist/lib/teams/worktree.d.ts +24 -0
  426. package/dist/lib/teams/worktree.js +42 -0
  427. package/dist/lib/terminal/backends/vscodium-agent.d.ts +10 -1
  428. package/dist/lib/terminal/backends/vscodium-agent.js +16 -5
  429. package/dist/lib/terminal/engine.d.ts +4 -0
  430. package/dist/lib/terminal/engine.js +8 -2
  431. package/dist/lib/terminal/types.d.ts +21 -2
  432. package/dist/lib/testdata/refresh-coordinator-worker.d.ts +1 -0
  433. package/dist/lib/testdata/refresh-coordinator-worker.js +23 -0
  434. package/dist/lib/tickets/list.d.ts +53 -0
  435. package/dist/lib/tickets/list.js +153 -0
  436. package/dist/lib/tmux/orphan-reap.d.ts +320 -0
  437. package/dist/lib/tmux/orphan-reap.js +644 -0
  438. package/dist/lib/tmux/session.d.ts +68 -3
  439. package/dist/lib/tmux/session.js +95 -5
  440. package/dist/lib/triggers/handlers.d.ts +20 -0
  441. package/dist/lib/triggers/handlers.js +6 -1
  442. package/dist/lib/types.d.ts +46 -34
  443. package/dist/lib/usage-fleet.d.ts +32 -0
  444. package/dist/lib/usage-fleet.js +125 -0
  445. package/dist/lib/usage-refresh.d.ts +3 -3
  446. package/dist/lib/usage-refresh.js +19 -16
  447. package/dist/lib/usage.d.ts +51 -47
  448. package/dist/lib/usage.js +145 -153
  449. package/dist/lib/version-duplicates.js +2 -2
  450. package/dist/lib/versions.d.ts +56 -13
  451. package/dist/lib/versions.js +195 -45
  452. package/dist/lib/watchdog/log.d.ts +1 -1
  453. package/dist/lib/watchdog/log.js +3 -3
  454. package/dist/lib/watchdog/rotate.d.ts +6 -6
  455. package/dist/lib/watchdog/rotate.js +6 -6
  456. package/dist/lib/workflows.d.ts +1 -1
  457. package/dist/lib/yaml-io.d.ts +47 -0
  458. package/dist/lib/yaml-io.js +55 -0
  459. package/package.json +2 -1
  460. package/scripts/install-helper.js +2 -2
  461. package/scripts/postinstall.js +23 -20
  462. package/dist/commands/defaults.d.ts +0 -7
  463. package/dist/commands/defaults.js +0 -107
  464. package/dist/commands/export.d.ts +0 -11
  465. package/dist/commands/export.js +0 -215
  466. package/dist/commands/helper.d.ts +0 -12
  467. package/dist/commands/helper.js +0 -87
  468. package/dist/commands/hosts.d.ts +0 -11
  469. package/dist/commands/hosts.js +0 -330
  470. package/dist/commands/lock.d.ts +0 -12
  471. package/dist/commands/lock.js +0 -70
  472. package/dist/commands/pull.d.ts +0 -17
  473. package/dist/commands/pull.js +0 -39
  474. package/dist/commands/push.d.ts +0 -14
  475. package/dist/commands/push.js +0 -30
  476. package/dist/commands/wallet.d.ts +0 -20
  477. package/dist/commands/wallet.js +0 -216
  478. package/dist/commands/worktree.d.ts +0 -19
  479. package/dist/commands/worktree.js +0 -272
  480. package/dist/lib/account-labels.d.ts +0 -24
  481. package/dist/lib/account-labels.js +0 -72
  482. package/dist/lib/auto-dispatch-linear.d.ts +0 -18
  483. package/dist/lib/auto-dispatch-linear.js +0 -107
  484. package/dist/lib/auto-dispatch-provider.d.ts +0 -10
  485. package/dist/lib/auto-dispatch-provider.js +0 -30
  486. package/dist/lib/auto-dispatch.d.ts +0 -93
  487. package/dist/lib/auto-dispatch.js +0 -128
  488. package/dist/lib/export.d.ts +0 -72
  489. package/dist/lib/export.js +0 -269
  490. package/dist/lib/lock.d.ts +0 -93
  491. package/dist/lib/lock.js +0 -207
  492. package/dist/lib/wallet/index.d.ts +0 -78
  493. package/dist/lib/wallet/index.js +0 -253
@@ -25,7 +25,7 @@ import { getKeychainToken, hasKeychainToken, secretsKeychainItem, setKeychainTok
25
25
  import { assertOpAvailable, createPasswordItem, deleteItemByTitle, extractSecrets, itemExistsByTitle, listItems, listVaults, } from '../lib/onepassword.js';
26
26
  import { GLOBAL_HARNESS } from '../lib/secrets/scope.js';
27
27
  import { createSecretLease, selectLeasedEnv } from '../lib/secrets/lease.js';
28
- import { secretsHoldMs, secretsAgentDurable, agentLoad, agentLock, agentPing, agentStatus, ensureAgentRunning, runAgentLoadFromStdin, runSecretsAgent, uninstallSecretsAgentService, } from '../lib/secrets/agent.js';
28
+ import { secretsHoldMs, secretsAgentDurable, agentLoad, agentLock, agentPing, agentStatus, ensureAgentRunning, runAgentLoadFromStdin, runSecretsAgent, uninstallSecretsAgentService, isSecretsBrokerEnabled, } from '../lib/secrets/agent.js';
29
29
  import { saveSession, deleteBundleSessions, deleteAllSessions } from '../lib/secrets/session-store.js';
30
30
  import { getCliVersionFresh } from '../lib/version.js';
31
31
  import { readMeta } from '../lib/state.js';
@@ -432,8 +432,14 @@ export function importBundleFromFile(filePath, passphrase) {
432
432
  * session (run sequentially) so a remote Touch-ID / passphrase prompt can
433
433
  * surface (e.g. `view --reveal`); otherwise hosts are queried in parallel.
434
434
  * Exits non-zero if any host fails.
435
+ *
436
+ * `secret` marks a browse that streams plaintext VALUES back (`view --reveal`)
437
+ * rather than only key names (`list`, masked `view`). A secret browse rides the
438
+ * credential-transport posture — managed host-key pinning + no multiplex — so a
439
+ * revealed value can't be intercepted on an unpinned/multiplexed connection
440
+ * (RUSH-2527). It composes with `tty`: `view --reveal` at a terminal is both.
435
441
  */
436
- async function browseRemote(targets, args, tty) {
442
+ async function browseRemote(targets, args, tty, secret = false) {
437
443
  const multi = targets.length > 1;
438
444
  let failures = 0;
439
445
  const render = (name, res) => {
@@ -455,12 +461,12 @@ async function browseRemote(targets, args, tty) {
455
461
  if (tty) {
456
462
  for (const t of targets) {
457
463
  const target = await resolveHostSshTarget(t);
458
- render(t, remoteSecretsRaw(target, args, { tty: true, osLookupName: t }));
464
+ render(t, remoteSecretsRaw(target, args, { tty: true, osLookupName: t, secret }));
459
465
  }
460
466
  }
461
467
  else {
462
468
  const resolved = await Promise.all(targets.map(async (t) => ({ name: t, target: await resolveHostSshTarget(t) })));
463
- const results = resolved.map(({ name, target }) => remoteSecretsRaw(target, args, { osLookupName: name }));
469
+ const results = resolved.map(({ name, target }) => remoteSecretsRaw(target, args, { osLookupName: name, secret }));
464
470
  targets.forEach((t, i) => render(t, results[i]));
465
471
  }
466
472
  if (failures > 0)
@@ -1241,7 +1247,10 @@ export function registerSecretsCommands(program) {
1241
1247
  // (and the remote's "--reveal in a non-TTY needs --plaintext" gate is
1242
1248
  // satisfied) — only when this side is itself interactive.
1243
1249
  const tty = Boolean(opts.reveal) && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
1244
- await browseRemote(targets, args, tty);
1250
+ // `--reveal` streams the plaintext value back over ssh stdout — a
1251
+ // secret-bearing read that must pin the host key and not multiplex,
1252
+ // whether it runs on the interactive (tty) or `--plaintext` non-tty path.
1253
+ await browseRemote(targets, args, tty, Boolean(opts.reveal));
1245
1254
  return;
1246
1255
  }
1247
1256
  const resolvedName = name ?? (await pickBundleName('view'));
@@ -1875,7 +1884,18 @@ Examples:
1875
1884
  delete bundle.vars[resolvedKey];
1876
1885
  writeBundle(bundle);
1877
1886
  if (willPurge) {
1878
- const item = secretsKeychainItem(resolvedBundleName, raw.slice('keychain:'.length));
1887
+ const shortId = raw.slice('keychain:'.length);
1888
+ // Purge only the removed key's own storage slot. A corrupted ref
1889
+ // (e.g. a stale manifest write-back leaving OPENAI_API_KEY pointing
1890
+ // at keychain:LINEAR_API_KEY) must never destroy another key's item —
1891
+ // the ref is gone from the bundle either way; the stray item is the
1892
+ // recoverable side of that trade.
1893
+ if (shortId !== resolvedKey) {
1894
+ console.log(chalk.yellow(`Removed ${resolvedBundleName}.${resolvedKey}, but its ref pointed at '${shortId}' — ` +
1895
+ `left that keychain item untouched (it belongs to a different key).`));
1896
+ return;
1897
+ }
1898
+ const item = secretsKeychainItem(resolvedBundleName, shortId);
1879
1899
  const removed = bundleItemStore(bundle.backend).delete(item);
1880
1900
  if (removed) {
1881
1901
  const where = bundle.backend === 'file'
@@ -2157,7 +2177,16 @@ Examples:
2157
2177
  const remotePassphrase = remoteBackend === 'file' ? (process.env.AGENTS_SECRETS_PASSPHRASE ?? '') : '';
2158
2178
  // Resolve ONCE for N hosts — reading a bundle can prompt, and doing it
2159
2179
  // per host would prompt per host.
2160
- const resolvedForPush = resolveBundleForPush(resolvedBundleName, 'ssh export');
2180
+ //
2181
+ // Same predicate as `view --reveal` / `exec`: a human at a TTY gets the
2182
+ // Touch ID sheet, an agent launch or a piped/CI run stays broker-only
2183
+ // and fails fast with the `agents secrets unlock` hint. Requiring a
2184
+ // prior unlock for an interactive push was an inconsistency, not a
2185
+ // boundary — this read is strictly less exposed than `view --reveal`,
2186
+ // which already prompts.
2187
+ const resolvedForPush = resolveBundleForPush(resolvedBundleName, 'ssh export', {
2188
+ agentOnly: isHeadlessSecretsContext() || !isInteractiveTerminal(),
2189
+ });
2161
2190
  const keyCount = resolvedForPush.keyCount;
2162
2191
  let failures = 0;
2163
2192
  for (const host of hosts) {
@@ -2561,6 +2590,10 @@ Examples:
2561
2590
  process.exit(1);
2562
2591
  }
2563
2592
  const expiresAt = Date.now() + ttlMs;
2593
+ if (!isSecretsBrokerEnabled()) {
2594
+ console.error(chalk.red('Secrets broker is disabled — re-enable with \'agents daemon services enable secrets-broker\'.'));
2595
+ process.exit(1);
2596
+ }
2564
2597
  if (!(await ensureAgentRunning())) {
2565
2598
  console.error(chalk.red('Could not start the secrets broker.'));
2566
2599
  process.exit(1);
@@ -2582,7 +2615,10 @@ Examples:
2582
2615
  for (const name of targets) {
2583
2616
  try {
2584
2617
  // noAgent: read the real keychain (one Touch ID) rather than the
2585
- // agent we're about to populate.
2618
+ // agent we're about to populate. Snapshot time is captured BEFORE the
2619
+ // read so a concurrent eviction always beats this load (broker
2620
+ // tombstones reject snapshots that predate an eviction).
2621
+ const snapshotAt = Date.now();
2586
2622
  const { bundle, env } = readAndResolveBundleEnv(name, {
2587
2623
  noAgent: true,
2588
2624
  caller: 'unlock secrets',
@@ -2600,7 +2636,7 @@ Examples:
2600
2636
  // held exactly as before. scopeHeldEnv fails closed on an unknown key.
2601
2637
  const { heldEnv, lease } = scopeHeldEnv({ bundle: name, env, keys: opts.keys ?? null, ttlMs, harness, sleepPersist: durable });
2602
2638
  const heldExpiresAt = lease ? lease.expiresAt : expiresAt;
2603
- if (await agentLoad(name, bundle, heldEnv, ttlMs, harness, lease)) {
2639
+ if (await agentLoad(name, bundle, heldEnv, ttlMs, harness, lease, snapshotAt)) {
2604
2640
  loaded++;
2605
2641
  // Persist a durable session snapshot so the unlock survives a daemon
2606
2642
  // restart / upgrade (and sleep too, with --durable). session-store.ts.
@@ -2675,6 +2711,10 @@ Examples:
2675
2711
  console.log(chalk.gray('secrets-agent is macOS-only.'));
2676
2712
  return;
2677
2713
  }
2714
+ if (!isSecretsBrokerEnabled()) {
2715
+ console.log(chalk.yellow('broker: disabled — re-enable with \'agents daemon services enable secrets-broker\''));
2716
+ return;
2717
+ }
2678
2718
  const ping = await agentPing();
2679
2719
  const brokerUp = ping.reachable;
2680
2720
  console.log(chalk.gray('broker: ') +
@@ -2800,6 +2840,9 @@ Examples:
2800
2840
  console.error(chalk.red('The secrets broker is macOS-only.'));
2801
2841
  process.exit(1);
2802
2842
  }
2843
+ if (!isSecretsBrokerEnabled()) {
2844
+ console.error(chalk.yellow('Secrets broker is disabled — the daemon will start but will not host the broker. Re-enable with \'agents daemon services enable secrets-broker\'.'));
2845
+ }
2803
2846
  process.stdout.write(chalk.gray('Starting the daemon…\n'));
2804
2847
  ensureDaemonStarted();
2805
2848
  // The daemon hosts the broker socket-first; wait briefly for it to answer.
@@ -1,5 +1,52 @@
1
1
  import type { Command } from 'commander';
2
2
  import type { SessionMeta } from '../lib/session/types.js';
3
+ import { type BundleHeader, type BundleRecord } from '../lib/session/bundle.js';
3
4
  export declare function registerSessionsExportCommand(sessionsCmd: Command): void;
5
+ interface GlobalSelection {
6
+ since?: string;
7
+ limit?: string;
8
+ all?: boolean;
9
+ agent?: string;
10
+ redact?: boolean;
11
+ encrypt?: boolean;
12
+ toR2?: boolean;
13
+ output?: string;
14
+ stdout?: boolean;
15
+ host?: string[];
16
+ claude?: boolean;
17
+ codex?: boolean;
18
+ kimi?: boolean;
19
+ grok?: boolean;
20
+ opencode?: boolean;
21
+ antigravity?: boolean;
22
+ }
23
+ /**
24
+ * Upload each record to R2 as its own self-describing one-record bundle, keyed by
25
+ * the surviving object layout (`sessions/<machine>/<agent>/<sessionId>.jsonl`, or
26
+ * `.../<sessionId>/<relKey>` for dir-shaped agents). Each object is independently
27
+ * a valid bundle, so `import --from-r2` restores it through the same parse/place
28
+ * path as a local bundle. Fails loud on the first upload error (no silent
29
+ * partial-success).
30
+ */
31
+ /**
32
+ * `--to-r2` preflight as a pure, unit-testable function: the impossible-combo +
33
+ * not-configured gate. Returns the message to fail loud with, or null when the
34
+ * export may proceed. Only meaningful when `g.toR2` is set (the caller gates on
35
+ * that). Kept pure — no process.exit, no keychain read — so both the command and
36
+ * its test drive the exact same decision.
37
+ */
38
+ export declare function r2ExportGateError(g: Pick<GlobalSelection, 'toR2' | 'host'>, isConfigured: boolean): string | null;
39
+ export declare function uploadToR2(header: BundleHeader, records: BundleRecord[]): Promise<void>;
40
+ /** R2 object key for one record — dir-shaped agents key by relKey, file-shaped by session. */
41
+ export declare function r2KeyForRecord(rec: BundleRecord): string;
42
+ /**
43
+ * Resolve the AES key for an R2 backup: the fleet-shared R2_SYNC_ENC_KEY from the
44
+ * r2.backups bundle so any box on the bundle can restore. Unlike a local bundle,
45
+ * an ephemeral key is useless here (it is never stored, so a fresh box could not
46
+ * decrypt), so a missing key means the objects go up unencrypted (R2 server-side
47
+ * encryption only) with a loud warning — never a silent weaker default.
48
+ */
49
+ export declare function resolveR2BackupKey(): Buffer | null;
4
50
  /** ids > query > everything-in-scope. Exported for the id-resolution test. */
5
51
  export declare function selectSessions(metas: SessionMeta[], selectors: string[]): SessionMeta[];
52
+ export {};
@@ -23,11 +23,12 @@ import chalk from 'chalk';
23
23
  import { discoverSessions, resolveSessionById, looksLikeSessionId } from '../lib/session/discover.js';
24
24
  import { findSessionsById } from '../lib/session/db.js';
25
25
  import { filterSessionsByQuery, parseAgentFilter } from './sessions.js';
26
- import { listLocalTranscripts, SYNC_AGENTS } from '../lib/session/sync/agents.js';
26
+ import { listLocalTranscripts, objectKey, SYNC_AGENTS } from '../lib/session/sync/agents.js';
27
27
  import { machineId } from '../lib/machine-id.js';
28
28
  import { getHistoryDir } from '../lib/state.js';
29
- import { loadR2Config } from '../lib/session/sync/config.js';
29
+ import { isSyncConfigured, loadR2Config } from '../lib/session/sync/config.js';
30
30
  import { resolveSyncEncKey, generateSyncEncKey } from '../lib/session/sync/transcript-crypto.js';
31
+ import { R2Client } from '../lib/session/sync/r2.js';
31
32
  import { buildRecord, makeHeader, mergeRecords, serializeBundle, writeBundleFile, specForAgent, } from '../lib/session/bundle.js';
32
33
  import { knownSecretValuesFromEnv } from '../lib/redact.js';
33
34
  import { pullBundlesFromHosts } from '../lib/session/remote-bundle.js';
@@ -40,7 +41,8 @@ export function registerSessionsExportCommand(sessionsCmd) {
40
41
  .description('Bundle sessions (by id, query, or the parent selection flags like --since/-a) into a portable archive.')
41
42
  .option('-o, --output <path>', 'Write the bundle to this file')
42
43
  .option('--stdout', 'Write the bundle to stdout (for piping into `sessions import -`)')
43
- .option('--encrypt', 'Seal each transcript body with AES-256-GCM before writing');
44
+ .option('--encrypt', 'Seal each transcript body with AES-256-GCM before writing')
45
+ .option('--to-r2', 'Back the selected sessions up to Cloudflare R2 (requires the r2.backups bundle) instead of a local file');
44
46
  setHelpSections(cmd, {
45
47
  examples: `# Bundle the last week of sessions to a file
46
48
  agents sessions export --since 7d -o week.bundle
@@ -49,11 +51,19 @@ agents sessions export --since 7d -o week.bundle
49
51
  agents sessions export 4f8a2b1c 9d3e7a55 -o pair.bundle
50
52
 
51
53
  # Encrypt + pipe straight into another machine over SSH
52
- agents sessions export --since 7d --stdout --encrypt | agents ssh boxB 'agents sessions import - --decrypt <key>'`,
54
+ agents sessions export --since 7d --stdout --encrypt | agents ssh boxB 'agents sessions import - --decrypt <key>'
55
+
56
+ # Back the last month up off-box to Cloudflare R2 (encrypted with the shared key)
57
+ agents sessions export --since 30d --to-r2`,
53
58
  notes: `Selection uses the same flags as 'agents sessions' (--since, -n/--limit, --all,
54
59
  -a/--agent, --no-redact). Bundles are self-describing NDJSON: a header line + one
55
60
  line per transcript file. Secrets are redacted by default. Dir-shaped sessions
56
- (Kimi) carry all their files. Restore with 'agents sessions import'.`,
61
+ (Kimi) carry all their files. Restore with 'agents sessions import'.
62
+
63
+ --to-r2 uploads each session to the r2.backups bucket instead of a local file,
64
+ one encrypted object per transcript keyed by machine/agent/session. Bodies are
65
+ sealed with the shared R2_SYNC_ENC_KEY when present. Restore on any box on the
66
+ same bundle with 'agents sessions import --from-r2'.`,
57
67
  });
58
68
  cmd.action(async (selectors, _options, command) => {
59
69
  await runExport(selectors, command);
@@ -61,12 +71,25 @@ line per transcript file. Secrets are redacted by default. Dir-shaped sessions
61
71
  }
62
72
  async function runExport(selectors, command) {
63
73
  const g = command.optsWithGlobals();
74
+ // --to-r2 preflight: reject the impossible --host combo and fail loud when the
75
+ // backup target is not configured, rather than silently producing a local file.
76
+ // isSyncConfigured() is consulted only on the --to-r2 path (it reads the keychain).
77
+ if (g.toR2) {
78
+ const gateErr = r2ExportGateError(g, isSyncConfigured());
79
+ if (gateErr) {
80
+ process.stderr.write(chalk.red(gateErr + '\n'));
81
+ process.exit(1);
82
+ }
83
+ }
64
84
  // --host: export sessions that live on remote peer(s) — run export there and
65
85
  // stream the bundle back over the existing SSH transport (RUSH-1712).
66
86
  if (g.host && g.host.length > 0) {
67
87
  await runRemoteExport(g, selectors, command);
68
88
  return;
69
89
  }
90
+ if (g.toR2 && (g.output || g.stdout)) {
91
+ process.stderr.write(chalk.yellow('Note: --to-r2 uploads to R2; -o/--stdout are ignored.\n'));
92
+ }
70
93
  const explicitLimit = command.parent?.getOptionValueSource?.('limit') === 'cli';
71
94
  const limit = explicitLimit ? Math.max(1, parseInt(String(g.limit), 10) || DEFAULT_LIMIT) : DEFAULT_LIMIT;
72
95
  const agentFilter = parseAgentFilter(resolveAgentShorthand(g));
@@ -119,7 +142,9 @@ async function runExport(selectors, command) {
119
142
  process.exit(1);
120
143
  }
121
144
  // 4. Resolve encryption key (opt-in) + redaction (default on via parent --no-redact).
122
- const encryptKey = g.encrypt ? resolveExportKey() : null;
145
+ // An R2 backup uses the fleet-shared R2_SYNC_ENC_KEY (an ephemeral key would be
146
+ // unrecoverable on a fresh box), so it never takes the resolveExportKey path.
147
+ const encryptKey = g.toR2 ? resolveR2BackupKey() : g.encrypt ? resolveExportKey() : null;
123
148
  const redact = g.redact !== false;
124
149
  // Value-aware redaction: mask live credential values already in the
125
150
  // environment (e.g. an injected secrets bundle) verbatim, whatever their
@@ -146,8 +171,93 @@ async function runExport(selectors, command) {
146
171
  redacted: redact,
147
172
  records,
148
173
  });
174
+ if (g.toR2) {
175
+ await uploadToR2(header, records);
176
+ return;
177
+ }
149
178
  emitBundle(header, records, g);
150
179
  }
180
+ /**
181
+ * Upload each record to R2 as its own self-describing one-record bundle, keyed by
182
+ * the surviving object layout (`sessions/<machine>/<agent>/<sessionId>.jsonl`, or
183
+ * `.../<sessionId>/<relKey>` for dir-shaped agents). Each object is independently
184
+ * a valid bundle, so `import --from-r2` restores it through the same parse/place
185
+ * path as a local bundle. Fails loud on the first upload error (no silent
186
+ * partial-success).
187
+ */
188
+ /**
189
+ * `--to-r2` preflight as a pure, unit-testable function: the impossible-combo +
190
+ * not-configured gate. Returns the message to fail loud with, or null when the
191
+ * export may proceed. Only meaningful when `g.toR2` is set (the caller gates on
192
+ * that). Kept pure — no process.exit, no keychain read — so both the command and
193
+ * its test drive the exact same decision.
194
+ */
195
+ export function r2ExportGateError(g, isConfigured) {
196
+ if (!g.toR2)
197
+ return null;
198
+ if (g.host && g.host.length > 0) {
199
+ return "--to-r2 backs up THIS machine's sessions; it cannot be combined with --host.";
200
+ }
201
+ if (!isConfigured) {
202
+ return ('R2 backup is not configured: the r2.backups secrets bundle is missing or locked.\n' +
203
+ 'Add it with: agents secrets add r2.backups R2_ACCOUNT_ID R2_BUCKET_NAME R2_ACCESS_KEY_ID R2_SECRET_ACCESS_KEY\n' +
204
+ '(optionally R2_SYNC_ENC_KEY for client-side encryption).');
205
+ }
206
+ return null;
207
+ }
208
+ export async function uploadToR2(header, records) {
209
+ let client;
210
+ try {
211
+ client = new R2Client(loadR2Config());
212
+ }
213
+ catch (err) {
214
+ process.stderr.write(chalk.red(`R2 backup: ${err.message}\n`));
215
+ process.exit(1);
216
+ }
217
+ let uploaded = 0;
218
+ for (const rec of records) {
219
+ const recHeader = makeHeader({
220
+ origin: header.origin,
221
+ exportedAt: header.exportedAt,
222
+ encrypted: rec.encrypted,
223
+ redacted: header.redacted,
224
+ records: [rec],
225
+ });
226
+ const key = r2KeyForRecord(rec);
227
+ try {
228
+ await client.put(key, serializeBundle(recHeader, [rec]), 'application/json');
229
+ }
230
+ catch (err) {
231
+ process.stderr.write(chalk.red(`R2 backup failed at ${key}: ${err.message}\n`));
232
+ process.exit(1);
233
+ }
234
+ uploaded++;
235
+ }
236
+ process.stderr.write(chalk.green(`Backed up ${header.sessions} session${header.sessions === 1 ? '' : 's'} ` +
237
+ `(${uploaded} object${uploaded === 1 ? '' : 's'}${header.encrypted ? ', encrypted' : ', UNENCRYPTED'}) → R2.\n`));
238
+ }
239
+ /** R2 object key for one record — dir-shaped agents key by relKey, file-shaped by session. */
240
+ export function r2KeyForRecord(rec) {
241
+ const spec = specForAgent(rec.agent);
242
+ const relKey = spec?.dirShaped ? rec.relKey : undefined;
243
+ return objectKey(rec.machine, rec.agent, rec.sessionId, relKey);
244
+ }
245
+ /**
246
+ * Resolve the AES key for an R2 backup: the fleet-shared R2_SYNC_ENC_KEY from the
247
+ * r2.backups bundle so any box on the bundle can restore. Unlike a local bundle,
248
+ * an ephemeral key is useless here (it is never stored, so a fresh box could not
249
+ * decrypt), so a missing key means the objects go up unencrypted (R2 server-side
250
+ * encryption only) with a loud warning — never a silent weaker default.
251
+ */
252
+ export function resolveR2BackupKey() {
253
+ const key = resolveSyncEncKey(loadR2Config());
254
+ if (key)
255
+ return key;
256
+ process.stderr.write(chalk.yellow('R2_SYNC_ENC_KEY is not set in the r2.backups bundle — objects are stored WITHOUT client-side\n' +
257
+ 'encryption (R2 server-side only). Add a shared key so backups are zero-knowledge:\n' +
258
+ ' agents secrets add r2.backups R2_SYNC_ENC_KEY # value: openssl rand -base64 32\n'));
259
+ return null;
260
+ }
151
261
  /**
152
262
  * --host path: run `agents sessions export …` on each peer over SSH, stream the
153
263
  * bundles back, merge (dedup by origin machine) and emit one local bundle.
@@ -1,2 +1,19 @@
1
1
  import type { Command } from 'commander';
2
+ import { type ParsedBundle } from '../lib/session/bundle.js';
2
3
  export declare function registerSessionsImportCommand(sessionsCmd: Command): void;
4
+ /**
5
+ * --from-r2: download every session-backup object from the r2.backups bucket and
6
+ * assemble them into one bundle for the normal placement path. Each object is a
7
+ * self-describing one-record bundle written by `export --to-r2`, so it parses
8
+ * with the same parseBundle. A non-bundle object under the prefix (e.g. a legacy
9
+ * plaintext transcript from the retired sync) is skipped with a count, never
10
+ * silently swallowed. Fails loud when the bucket holds no restorable backups.
11
+ */
12
+ /**
13
+ * `--from-r2` preflight as a pure, unit-testable function: fail loud when the
14
+ * backup target is not configured. Returns the message to fail loud with, or null
15
+ * when the restore may proceed. Kept pure (no process.exit, no keychain read) so
16
+ * the command and its test drive the same decision.
17
+ */
18
+ export declare function r2ImportGateError(fromR2: boolean, isConfigured: boolean): string | null;
19
+ export declare function pullFromR2(): Promise<ParsedBundle>;
@@ -10,8 +10,10 @@
10
10
  */
11
11
  import * as fs from 'fs';
12
12
  import chalk from 'chalk';
13
- import { loadR2Config } from '../lib/session/sync/config.js';
13
+ import { isSyncConfigured, loadR2Config } from '../lib/session/sync/config.js';
14
14
  import { resolveSyncEncKey } from '../lib/session/sync/transcript-crypto.js';
15
+ import { R2Client } from '../lib/session/sync/r2.js';
16
+ import { SESSIONS_PREFIX } from '../lib/session/sync/agents.js';
15
17
  import { parseBundle, planImport, writeImport, mergeRecords, makeHeader, } from '../lib/session/bundle.js';
16
18
  import { pullBundlesFromHosts } from '../lib/session/remote-bundle.js';
17
19
  import { setHelpSections } from '../lib/help.js';
@@ -22,7 +24,8 @@ export function registerSessionsImportCommand(sessionsCmd) {
22
24
  .option('--dry-run', 'Show what would be placed without writing anything')
23
25
  .option('--overwrite', 'Replace local files that differ from the bundle (default: keep local)')
24
26
  .option('--decrypt [key]', 'Decrypt an encrypted bundle (key optional if the r2.backups sync key is configured)')
25
- .option('--from-host <target...>', 'Pull sessions live from remote peer(s) over SSH instead of a file (repeatable)');
27
+ .option('--from-host <target...>', 'Pull sessions live from remote peer(s) over SSH instead of a file (repeatable)')
28
+ .option('--from-r2', 'Restore session backups from Cloudflare R2 (requires the r2.backups bundle)');
26
29
  setHelpSections(cmd, {
27
30
  examples: `# Preview what a bundle would restore
28
31
  agents sessions import week.bundle --dry-run
@@ -34,11 +37,18 @@ agents sessions import week.bundle
34
37
  agents sessions import --from-host yosemite-s1 --since 7d
35
38
 
36
39
  # Or the equivalent raw pipe
37
- agents ssh boxB 'agents sessions export --since 7d --stdout' | agents sessions import -`,
40
+ agents ssh boxB 'agents sessions export --since 7d --stdout' | agents sessions import -
41
+
42
+ # Restore everything backed up to R2 (e.g. on a fresh box)
43
+ agents sessions import --from-r2`,
38
44
  notes: `Sessions land under the cross-machine mirror keyed by their origin machine, so
39
45
  they show up in 'agents sessions' tagged with that machine and never overwrite
40
46
  your own local sessions. Byte-exact duplicates are skipped. --from-host reuses
41
- the same SSH transport as the cross-machine listing (no R2, no daemon).`,
47
+ the same SSH transport as the cross-machine listing (no R2, no daemon).
48
+
49
+ --from-r2 downloads every session backup in the r2.backups bucket and restores
50
+ it through the same placement, using the shared R2_SYNC_ENC_KEY to decrypt (or
51
+ pass --decrypt <key>). It is the inverse of 'sessions export --to-r2'.`,
42
52
  });
43
53
  cmd.action(async (bundlePath, options, command) => {
44
54
  const g = command.optsWithGlobals();
@@ -46,9 +56,17 @@ the same SSH transport as the cross-machine listing (no R2, no daemon).`,
46
56
  });
47
57
  }
48
58
  async function runImport(bundlePath, options, g, command) {
49
- // 1. Obtain the bundle — from remote peer(s), stdin, or a file.
59
+ // 1. Obtain the bundle — from R2, remote peer(s), stdin, or a file.
50
60
  let bundle;
51
- if (options.fromHost && options.fromHost.length > 0) {
61
+ if (options.fromR2) {
62
+ const gateErr = r2ImportGateError(true, isSyncConfigured());
63
+ if (gateErr) {
64
+ process.stderr.write(chalk.red(gateErr + '\n'));
65
+ process.exit(1);
66
+ }
67
+ bundle = await pullFromR2();
68
+ }
69
+ else if (options.fromHost && options.fromHost.length > 0) {
52
70
  bundle = await pullForImport(options.fromHost, bundlePath, g, command);
53
71
  }
54
72
  else {
@@ -154,6 +172,96 @@ async function pullForImport(hosts, selector, g, command) {
154
172
  });
155
173
  return { header, records };
156
174
  }
175
+ /**
176
+ * --from-r2: download every session-backup object from the r2.backups bucket and
177
+ * assemble them into one bundle for the normal placement path. Each object is a
178
+ * self-describing one-record bundle written by `export --to-r2`, so it parses
179
+ * with the same parseBundle. A non-bundle object under the prefix (e.g. a legacy
180
+ * plaintext transcript from the retired sync) is skipped with a count, never
181
+ * silently swallowed. Fails loud when the bucket holds no restorable backups.
182
+ */
183
+ /**
184
+ * `--from-r2` preflight as a pure, unit-testable function: fail loud when the
185
+ * backup target is not configured. Returns the message to fail loud with, or null
186
+ * when the restore may proceed. Kept pure (no process.exit, no keychain read) so
187
+ * the command and its test drive the same decision.
188
+ */
189
+ export function r2ImportGateError(fromR2, isConfigured) {
190
+ if (fromR2 && !isConfigured) {
191
+ return ('R2 restore is not configured: the r2.backups secrets bundle is missing or locked.\n' +
192
+ 'Add it with: agents secrets add r2.backups R2_ACCOUNT_ID R2_BUCKET_NAME R2_ACCESS_KEY_ID R2_SECRET_ACCESS_KEY');
193
+ }
194
+ return null;
195
+ }
196
+ export async function pullFromR2() {
197
+ let client;
198
+ let bucket;
199
+ try {
200
+ const cfg = loadR2Config();
201
+ bucket = cfg.bucket;
202
+ client = new R2Client(cfg);
203
+ }
204
+ catch (err) {
205
+ process.stderr.write(chalk.red(`R2 restore: ${err.message}\n`));
206
+ process.exit(1);
207
+ }
208
+ let keys;
209
+ try {
210
+ keys = await client.list(SESSIONS_PREFIX);
211
+ }
212
+ catch (err) {
213
+ process.stderr.write(chalk.red(`R2 restore: listing failed: ${err.message}\n`));
214
+ process.exit(1);
215
+ }
216
+ const records = [];
217
+ let encryptedAny = false;
218
+ let redactedAll = true;
219
+ let skipped = 0;
220
+ for (const key of keys) {
221
+ // The retired sync wrote a per-machine manifest.json; it is not a bundle.
222
+ if (key.endsWith('/manifest.json'))
223
+ continue;
224
+ let body;
225
+ try {
226
+ body = await client.get(key);
227
+ }
228
+ catch (err) {
229
+ process.stderr.write(chalk.red(`R2 restore: fetching ${key} failed: ${err.message}\n`));
230
+ process.exit(1);
231
+ }
232
+ if (body === null)
233
+ continue;
234
+ let parsed;
235
+ try {
236
+ parsed = parseBundle(body);
237
+ }
238
+ catch {
239
+ skipped++;
240
+ continue;
241
+ }
242
+ records.push(...parsed.records);
243
+ if (parsed.header.encrypted)
244
+ encryptedAny = true;
245
+ if (!parsed.header.redacted)
246
+ redactedAll = false;
247
+ }
248
+ if (skipped > 0) {
249
+ process.stderr.write(chalk.yellow(`Skipped ${skipped} object(s) under ${SESSIONS_PREFIX} that are not session bundles.\n`));
250
+ }
251
+ const deduped = mergeRecords([records]);
252
+ if (deduped.length === 0) {
253
+ process.stderr.write(chalk.red(`No session backups found in R2 bucket '${bucket}'.\n`));
254
+ process.exit(1);
255
+ }
256
+ const header = makeHeader({
257
+ origin: `r2:${bucket}`,
258
+ exportedAt: new Date().toISOString(),
259
+ encrypted: encryptedAny,
260
+ redacted: redactedAll,
261
+ records: deduped,
262
+ });
263
+ return { header, records: deduped };
264
+ }
157
265
  /**
158
266
  * Decrypt-key resolution: an explicit `--decrypt <key>` (base64 or hex) wins;
159
267
  * otherwise fall back to the fleet-shared R2_SYNC_ENC_KEY from the r2.backups
@@ -15,6 +15,33 @@ export declare function effectiveMode(source: SessionMeta, requested: MigrateMod
15
15
  mode: MigrateMode;
16
16
  downgraded: boolean;
17
17
  };
18
+ /**
19
+ * Pure. The remote shell commands that create the target's tmux session on the
20
+ * SAME socket its reaper queries, and that probe its liveness afterward.
21
+ *
22
+ * `homeRelSocketPath` is `getDefaultSocketPath()` expressed relative to the
23
+ * LOCAL home dir (`path.relative(os.homedir(), getDefaultSocketPath())`) — a
24
+ * portable suffix, not a resolved absolute path, because the local and remote
25
+ * HOME can differ (different user, different OS). It is spliced in after a
26
+ * literal, unquoted `$HOME/` so the REMOTE shell resolves it; `$HOME` MUST
27
+ * NEVER be resolved locally (see AGENTS.md's remote-path guidance).
28
+ *
29
+ * Extracted as a pure function so the "does the migrated session land on the
30
+ * agents socket, not tmux's bare default socket" invariant is unit-testable
31
+ * without an SSH round-trip (RUSH-2521 review — a bare `tmux` here made the
32
+ * migrated agent's helpers invisible to `readAllPaneOwners`, so the reaper's
33
+ * next tick killed the still-live, just-migrated agent as `tmux-session-gone`).
34
+ */
35
+ export declare function buildMigrateResumeCommands(opts: {
36
+ sessionName: string;
37
+ homeRelSocketPath: string;
38
+ inner: string;
39
+ cwd?: string;
40
+ }): {
41
+ launchCmd: string;
42
+ probeCmd: string;
43
+ socketFlag: string;
44
+ };
18
45
  /**
19
46
  * Rehydrate command: launch a fresh agent that reads the transcript. For a
20
47
  * non-resumable agent there is no faithful --resume, so we start the agent with