@phnx-labs/agents-cli 1.22.39 → 1.22.40

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 (554) hide show
  1. package/CHANGELOG.md +294 -0
  2. package/README.md +111 -70
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.js +45 -26
  5. package/dist/browser.js +2 -2
  6. package/dist/cli/command-registry.d.ts +135 -0
  7. package/dist/cli/command-registry.js +260 -0
  8. package/dist/commands/accounts.d.ts +20 -0
  9. package/dist/commands/accounts.js +186 -25
  10. package/dist/commands/apply.d.ts +3 -3
  11. package/dist/commands/apply.js +20 -63
  12. package/dist/commands/browser.js +206 -94
  13. package/dist/commands/budget.d.ts +7 -6
  14. package/dist/commands/budget.js +8 -7
  15. package/dist/commands/cloud.js +17 -20
  16. package/dist/commands/commands.js +1 -1
  17. package/dist/commands/computer-actions.d.ts +1 -35
  18. package/dist/commands/computer-actions.js +13 -181
  19. package/dist/commands/computer.d.ts +28 -3
  20. package/dist/commands/computer.js +93 -65
  21. package/dist/commands/config.js +16 -3
  22. package/dist/commands/cost.d.ts +2 -1
  23. package/dist/commands/cost.js +7 -6
  24. package/dist/commands/daemon.js +151 -4
  25. package/dist/commands/detach.js +1 -1
  26. package/dist/commands/doctor.js +19 -11
  27. package/dist/commands/events.js +1 -1
  28. package/dist/commands/exec.d.ts +2 -50
  29. package/dist/commands/exec.js +160 -215
  30. package/dist/commands/factory.js +1 -1
  31. package/dist/commands/feed.d.ts +2 -2
  32. package/dist/commands/feed.js +9 -12
  33. package/dist/commands/fleet-capture.js +25 -27
  34. package/dist/commands/focus.d.ts +2 -3
  35. package/dist/commands/focus.js +34 -9
  36. package/dist/commands/go.js +15 -5
  37. package/dist/commands/harness.d.ts +3 -3
  38. package/dist/commands/harness.js +53 -5
  39. package/dist/commands/hooks.js +1 -1
  40. package/dist/commands/import.js +2 -2
  41. package/dist/commands/insights.d.ts +5 -5
  42. package/dist/commands/insights.js +15 -9
  43. package/dist/commands/inspect.d.ts +2 -3
  44. package/dist/commands/inspect.js +4 -16
  45. package/dist/commands/logs.d.ts +2 -2
  46. package/dist/commands/logs.js +9 -9
  47. package/dist/commands/mcp.js +2 -2
  48. package/dist/commands/memory.js +1 -1
  49. package/dist/commands/message.d.ts +1 -1
  50. package/dist/commands/message.js +7 -7
  51. package/dist/commands/mine.d.ts +11 -8
  52. package/dist/commands/mine.js +11 -10
  53. package/dist/commands/models.js +1 -1
  54. package/dist/commands/modes.js +1 -1
  55. package/dist/commands/monitors.js +23 -6
  56. package/dist/commands/output.d.ts +7 -6
  57. package/dist/commands/output.js +11 -10
  58. package/dist/commands/packages.js +4 -4
  59. package/dist/commands/perf.js +1 -1
  60. package/dist/commands/permissions.js +49 -32
  61. package/dist/commands/plugins.d.ts +1 -1
  62. package/dist/commands/plugins.js +3 -3
  63. package/dist/commands/profiles.d.ts +6 -9
  64. package/dist/commands/profiles.js +8 -321
  65. package/dist/commands/projects.d.ts +9 -0
  66. package/dist/commands/projects.js +140 -8
  67. package/dist/commands/prune.js +5 -13
  68. package/dist/commands/refresh-rules.js +1 -1
  69. package/dist/commands/repo.d.ts +5 -7
  70. package/dist/commands/repo.js +34 -18
  71. package/dist/commands/resource-view.js +4 -0
  72. package/dist/commands/resume.d.ts +11 -7
  73. package/dist/commands/resume.js +82 -98
  74. package/dist/commands/route.d.ts +12 -0
  75. package/dist/commands/route.js +261 -0
  76. package/dist/commands/routines.d.ts +2 -3
  77. package/dist/commands/routines.js +120 -145
  78. package/dist/commands/rules.js +1 -1
  79. package/dist/commands/run-account-picker.d.ts +1 -1
  80. package/dist/commands/run-account-picker.js +2 -2
  81. package/dist/commands/run-broadcast.d.ts +21 -0
  82. package/dist/commands/run-broadcast.js +89 -0
  83. package/dist/commands/run-cloud.js +1 -1
  84. package/dist/commands/secrets-vault.d.ts +10 -0
  85. package/dist/commands/{login.js → secrets-vault.js} +34 -14
  86. package/dist/commands/secrets.d.ts +4 -4
  87. package/dist/commands/secrets.js +72 -63
  88. package/dist/commands/sessions-backfill.js +1 -1
  89. package/dist/commands/sessions-browser.d.ts +1 -1
  90. package/dist/commands/sessions-browser.js +9 -5
  91. package/dist/commands/sessions-export.js +2 -2
  92. package/dist/commands/sessions-inject.js +4 -5
  93. package/dist/commands/sessions-migrate.js +16 -27
  94. package/dist/commands/sessions-picker.d.ts +13 -0
  95. package/dist/commands/sessions-picker.js +182 -8
  96. package/dist/commands/sessions-resume.d.ts +7 -3
  97. package/dist/commands/sessions-resume.js +81 -33
  98. package/dist/commands/sessions-stats.js +2 -2
  99. package/dist/commands/sessions.d.ts +75 -68
  100. package/dist/commands/sessions.js +142 -180
  101. package/dist/commands/setup-computer.js +1 -1
  102. package/dist/commands/setup-fleet.js +3 -0
  103. package/dist/commands/setup-mine.d.ts +7 -3
  104. package/dist/commands/setup-mine.js +13 -8
  105. package/dist/commands/setup-secrets.js +2 -2
  106. package/dist/commands/setup-watchdog.js +2 -2
  107. package/dist/commands/setup.js +7 -2
  108. package/dist/commands/share.d.ts +65 -0
  109. package/dist/commands/share.js +253 -20
  110. package/dist/commands/skills.js +3 -3
  111. package/dist/commands/snapshot.d.ts +2 -2
  112. package/dist/commands/snapshot.js +9 -9
  113. package/dist/commands/ssh.d.ts +1 -1
  114. package/dist/commands/ssh.js +186 -64
  115. package/dist/commands/subagents.js +1 -1
  116. package/dist/commands/sync.js +24 -9
  117. package/dist/commands/teams.d.ts +2 -2
  118. package/dist/commands/teams.js +9 -18
  119. package/dist/commands/tmux.js +5 -1
  120. package/dist/commands/trash.js +1 -1
  121. package/dist/commands/usage.d.ts +1 -1
  122. package/dist/commands/usage.js +2 -2
  123. package/dist/commands/utils.d.ts +4 -14
  124. package/dist/commands/utils.js +6 -27
  125. package/dist/commands/versions.js +2 -2
  126. package/dist/commands/view.d.ts +9 -77
  127. package/dist/commands/view.js +23 -8
  128. package/dist/commands/watchdog.js +1 -1
  129. package/dist/commands/webhook.js +11 -25
  130. package/dist/commands/workflows.js +1 -1
  131. package/dist/index.js +1 -1
  132. package/dist/lib/account-capabilities.d.ts +39 -0
  133. package/dist/lib/account-capabilities.js +59 -0
  134. package/dist/lib/account-catalog.js +1 -1
  135. package/dist/lib/account-registry.d.ts +66 -0
  136. package/dist/lib/account-registry.js +151 -5
  137. package/dist/lib/{rotate.d.ts → accounting/rotate.d.ts} +4 -4
  138. package/dist/lib/{rotate.js → accounting/rotate.js} +8 -8
  139. package/dist/lib/{usage.d.ts → accounting/usage.d.ts} +4 -4
  140. package/dist/lib/{usage.js → accounting/usage.js} +11 -11
  141. package/dist/lib/agent-spec/provider.js +1 -1
  142. package/dist/lib/agents.d.ts +72 -4
  143. package/dist/lib/agents.js +231 -199
  144. package/dist/lib/answer-router.d.ts +2 -2
  145. package/dist/lib/ask-classifier.d.ts +1 -1
  146. package/dist/lib/ask-classifier.js +1 -1
  147. package/dist/lib/audit/log.js +1 -1
  148. package/dist/lib/auth-health.d.ts +1 -1
  149. package/dist/lib/auth-health.js +2 -2
  150. package/dist/lib/brand.js +1 -1
  151. package/dist/lib/browser/caller-identity.d.ts +28 -0
  152. package/dist/lib/browser/caller-identity.js +156 -0
  153. package/dist/lib/browser/cdp.js +1 -1
  154. package/dist/lib/browser/drivers/ssh.d.ts +1 -1
  155. package/dist/lib/browser/drivers/ssh.js +2 -2
  156. package/dist/lib/browser/hygiene.d.ts +10 -2
  157. package/dist/lib/browser/hygiene.js +7 -4
  158. package/dist/lib/browser/ipc.d.ts +22 -2
  159. package/dist/lib/browser/ipc.js +185 -21
  160. package/dist/lib/browser/profiles.d.ts +67 -3
  161. package/dist/lib/browser/profiles.js +129 -7
  162. package/dist/lib/browser/remote-control.d.ts +2 -2
  163. package/dist/lib/browser/remote-control.js +3 -3
  164. package/dist/lib/browser/runtime-state.d.ts +79 -0
  165. package/dist/lib/browser/runtime-state.js +119 -1
  166. package/dist/lib/browser/service.d.ts +113 -1
  167. package/dist/lib/browser/service.js +454 -40
  168. package/dist/lib/browser/sessions-list.d.ts +2 -1
  169. package/dist/lib/browser/sessions-list.js +4 -13
  170. package/dist/lib/browser/types.d.ts +18 -0
  171. package/dist/lib/budget/config.js +1 -1
  172. package/dist/lib/budget/ledger.js +1 -1
  173. package/dist/lib/byok-usage.js +1 -1
  174. package/dist/lib/capabilities.js +3 -15
  175. package/dist/lib/catchup.d.ts +1 -1
  176. package/dist/lib/catchup.js +1 -1
  177. package/dist/lib/cloud/dispatch.js +1 -1
  178. package/dist/lib/cloud/host.d.ts +3 -3
  179. package/dist/lib/cloud/host.js +5 -5
  180. package/dist/lib/cloud/rush.d.ts +9 -21
  181. package/dist/lib/cloud/rush.js +33 -120
  182. package/dist/lib/cloud/types.d.ts +2 -2
  183. package/dist/lib/commands.js +3 -13
  184. package/dist/lib/computer/actions.d.ts +36 -0
  185. package/dist/lib/computer/actions.js +162 -0
  186. package/dist/lib/computer/dispatch.d.ts +2 -2
  187. package/dist/lib/computer/dispatch.js +2 -2
  188. package/dist/lib/computer/sessions-list.d.ts +3 -3
  189. package/dist/lib/computer/sessions-list.js +2 -2
  190. package/dist/lib/config-keys.d.ts +2 -1
  191. package/dist/lib/config-keys.js +8 -4
  192. package/dist/lib/config-machine-keys.d.ts +4 -2
  193. package/dist/lib/config-machine-keys.js +5 -2
  194. package/dist/lib/crabbox/lease.js +9 -1
  195. package/dist/lib/crabbox/runtimes.d.ts +32 -7
  196. package/dist/lib/crabbox/runtimes.js +72 -30
  197. package/dist/lib/{daemon.d.ts → daemon/daemon.d.ts} +59 -1
  198. package/dist/lib/{daemon.js → daemon/daemon.js} +237 -49
  199. package/dist/lib/daemon-services.d.ts +1 -1
  200. package/dist/lib/daemon-services.js +5 -0
  201. package/dist/lib/daemon-ticks.d.ts +25 -0
  202. package/dist/lib/daemon-ticks.js +31 -1
  203. package/dist/lib/daemon-webhooks.d.ts +102 -0
  204. package/dist/lib/daemon-webhooks.js +210 -0
  205. package/dist/lib/device-config.d.ts +85 -75
  206. package/dist/lib/device-config.js +279 -141
  207. package/dist/lib/devices/config-migration.d.ts +29 -34
  208. package/dist/lib/devices/config-migration.js +209 -130
  209. package/dist/lib/devices/connect.d.ts +2 -2
  210. package/dist/lib/devices/connect.js +2 -2
  211. package/dist/lib/devices/discovery-policy.d.ts +30 -0
  212. package/dist/lib/devices/discovery-policy.js +101 -0
  213. package/dist/lib/devices/fleet-inventory.js +8 -3
  214. package/dist/lib/devices/harness-inventory.d.ts +1 -1
  215. package/dist/lib/devices/harness-inventory.js +12 -3
  216. package/dist/lib/devices/known-hosts.js +1 -1
  217. package/dist/lib/devices/pool.d.ts +10 -5
  218. package/dist/lib/devices/pool.js +5 -7
  219. package/dist/lib/devices/registry.d.ts +1 -1
  220. package/dist/lib/devices/registry.js +1 -1
  221. package/dist/lib/devices/resolve-profile.d.ts +2 -2
  222. package/dist/lib/devices/resolve-profile.js +2 -2
  223. package/dist/lib/devices/resolve-target.d.ts +1 -1
  224. package/dist/lib/devices/resolve-target.js +4 -4
  225. package/dist/lib/devices/self-host.d.ts +1 -1
  226. package/dist/lib/devices/self-host.js +3 -3
  227. package/dist/lib/devices/ssh-config.d.ts +1 -1
  228. package/dist/lib/devices/stats-cache.js +1 -1
  229. package/dist/lib/devices/sync.d.ts +3 -1
  230. package/dist/lib/devices/sync.js +3 -3
  231. package/dist/lib/doctor-diff.js +4 -4
  232. package/dist/lib/drift-sync.js +2 -2
  233. package/dist/lib/drift.js +2 -2
  234. package/dist/lib/event-families.d.ts +1 -1
  235. package/dist/lib/event-stream.d.ts +1 -1
  236. package/dist/lib/event-stream.js +2 -2
  237. package/dist/lib/events-ingest.js +2 -2
  238. package/dist/lib/exec.d.ts +4 -4
  239. package/dist/lib/exec.js +11 -11
  240. package/dist/lib/factory/snapshot.d.ts +1 -1
  241. package/dist/lib/factory/snapshot.js +1 -1
  242. package/dist/lib/{activity.d.ts → feed/activity.d.ts} +1 -1
  243. package/dist/lib/{activity.js → feed/activity.js} +7 -7
  244. package/dist/lib/{events.d.ts → feed/events.d.ts} +2 -2
  245. package/dist/lib/{events.js → feed/events.js} +5 -5
  246. package/dist/lib/{feed.js → feed/feed.js} +4 -4
  247. package/dist/lib/feed-outcome.d.ts +1 -1
  248. package/dist/lib/feed-policy.d.ts +1 -1
  249. package/dist/lib/feed-policy.js +1 -1
  250. package/dist/lib/feed-post.d.ts +1 -1
  251. package/dist/lib/feed-post.js +1 -1
  252. package/dist/lib/feed-ranking.d.ts +1 -1
  253. package/dist/lib/fleet/apply.d.ts +0 -6
  254. package/dist/lib/fleet/apply.js +18 -45
  255. package/dist/lib/fleet/auth-sync.d.ts +16 -35
  256. package/dist/lib/fleet/auth-sync.js +16 -75
  257. package/dist/lib/fleet/capture.js +8 -6
  258. package/dist/lib/fleet/manifest.js +23 -11
  259. package/dist/lib/fleet/types.d.ts +19 -7
  260. package/dist/lib/fleet-cache.d.ts +1 -1
  261. package/dist/lib/fleet-cache.js +1 -1
  262. package/dist/lib/format.d.ts +19 -0
  263. package/dist/lib/format.js +44 -1
  264. package/dist/lib/friction-heuristics.d.ts +1 -1
  265. package/dist/lib/git.d.ts +24 -1
  266. package/dist/lib/git.js +82 -38
  267. package/dist/lib/heal.js +3 -3
  268. package/dist/lib/hooks.d.ts +18 -0
  269. package/dist/lib/hooks.js +113 -1
  270. package/dist/lib/hosts/credentials.d.ts +30 -10
  271. package/dist/lib/hosts/credentials.js +38 -26
  272. package/dist/lib/hosts/dispatch.d.ts +3 -3
  273. package/dist/lib/hosts/dispatch.js +3 -3
  274. package/dist/lib/hosts/option.d.ts +3 -3
  275. package/dist/lib/hosts/option.js +7 -8
  276. package/dist/lib/hosts/passthrough.d.ts +17 -17
  277. package/dist/lib/hosts/passthrough.js +67 -50
  278. package/dist/lib/hosts/providers/devices.d.ts +1 -1
  279. package/dist/lib/hosts/providers/devices.js +1 -1
  280. package/dist/lib/hosts/reconcile.d.ts +1 -1
  281. package/dist/lib/hosts/reconcile.js +1 -1
  282. package/dist/lib/hosts/reconnect.d.ts +1 -1
  283. package/dist/lib/hosts/reconnect.js +2 -2
  284. package/dist/lib/hosts/registry.d.ts +5 -5
  285. package/dist/lib/hosts/registry.js +5 -5
  286. package/dist/lib/hosts/remote-cmd.d.ts +10 -10
  287. package/dist/lib/hosts/remote-cmd.js +25 -18
  288. package/dist/lib/hosts/remote-os.d.ts +4 -4
  289. package/dist/lib/hosts/remote-os.js +4 -4
  290. package/dist/lib/hosts/remote-session-id.d.ts +2 -2
  291. package/dist/lib/hosts/remote-session-id.js +2 -2
  292. package/dist/lib/hosts/routing-flag.d.ts +3 -3
  293. package/dist/lib/hosts/routing-flag.js +4 -5
  294. package/dist/lib/hosts/run-target.d.ts +4 -4
  295. package/dist/lib/hosts/run-target.js +4 -4
  296. package/dist/lib/hosts/session-index.d.ts +2 -2
  297. package/dist/lib/hosts/session-index.js +1 -1
  298. package/dist/lib/hosts/session-marker.d.ts +1 -1
  299. package/dist/lib/hosts/session-marker.js +1 -1
  300. package/dist/lib/hosts/types.d.ts +2 -2
  301. package/dist/lib/hosts/types.js +2 -2
  302. package/dist/lib/import.js +1 -1
  303. package/dist/lib/{migrate.d.ts → installations/migrate.d.ts} +13 -1
  304. package/dist/lib/{migrate.js → installations/migrate.js} +160 -40
  305. package/dist/lib/installations/resolve.js +1 -1
  306. package/dist/lib/installations/strategies.js +1 -1
  307. package/dist/lib/installations/update.js +2 -2
  308. package/dist/lib/{versions.d.ts → installations/versions.d.ts} +12 -3
  309. package/dist/lib/{versions.js → installations/versions.js} +51 -31
  310. package/dist/lib/isolation-boundary-report.js +1 -1
  311. package/dist/lib/linear-projects.d.ts +28 -0
  312. package/dist/lib/linear-projects.js +30 -0
  313. package/dist/lib/loop.js +1 -1
  314. package/dist/lib/mailbox-gc.js +1 -1
  315. package/dist/lib/mailbox.js +1 -1
  316. package/dist/lib/mcp-registry.d.ts +62 -0
  317. package/dist/lib/mcp-registry.js +177 -0
  318. package/dist/lib/mcp.d.ts +5 -0
  319. package/dist/lib/mcp.js +124 -307
  320. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  321. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  322. package/dist/lib/menubar/install-menubar.d.ts +7 -0
  323. package/dist/lib/menubar/install-menubar.js +24 -6
  324. package/dist/lib/menubar/snapshot.d.ts +7 -0
  325. package/dist/lib/menubar/snapshot.js +8 -6
  326. package/dist/lib/models.js +1 -1
  327. package/dist/lib/monitors/config.d.ts +12 -0
  328. package/dist/lib/monitors/config.js +3 -0
  329. package/dist/lib/monitors/dispatch.js +26 -5
  330. package/dist/lib/monitors/engine.js +10 -0
  331. package/dist/lib/monitors/sources/webhook.d.ts +1 -1
  332. package/dist/lib/monitors/state.d.ts +42 -0
  333. package/dist/lib/monitors/state.js +30 -0
  334. package/dist/lib/notify.d.ts +1 -1
  335. package/dist/lib/observe-aliases.d.ts +2 -2
  336. package/dist/lib/observe-aliases.js +2 -11
  337. package/dist/lib/output/git-output.js +2 -2
  338. package/dist/lib/overdue.d.ts +1 -1
  339. package/dist/lib/overdue.js +1 -1
  340. package/dist/lib/permissions-registry.d.ts +65 -0
  341. package/dist/lib/permissions-registry.js +599 -0
  342. package/dist/lib/permissions.d.ts +24 -13
  343. package/dist/lib/permissions.js +52 -271
  344. package/dist/lib/picker.d.ts +16 -0
  345. package/dist/lib/picker.js +33 -3
  346. package/dist/lib/placement.d.ts +1 -1
  347. package/dist/lib/placement.js +4 -4
  348. package/dist/lib/platform/paths.d.ts +8 -0
  349. package/dist/lib/platform/paths.js +11 -0
  350. package/dist/lib/platform/posixpath.js +3 -3
  351. package/dist/lib/{plugin-marketplace.d.ts → plugins/plugin-marketplace.d.ts} +1 -1
  352. package/dist/lib/{plugin-marketplace.js → plugins/plugin-marketplace.js} +2 -2
  353. package/dist/lib/{plugins.d.ts → plugins/plugins.d.ts} +1 -1
  354. package/dist/lib/{plugins.js → plugins/plugins.js} +7 -7
  355. package/dist/lib/{skills.d.ts → plugins/skills.d.ts} +1 -1
  356. package/dist/lib/{skills.js → plugins/skills.js} +7 -7
  357. package/dist/lib/pricing/cost.d.ts +1 -1
  358. package/dist/lib/pricing/cost.js +1 -1
  359. package/dist/lib/profiles-presets.d.ts +1 -1
  360. package/dist/lib/profiles-presets.js +1 -1
  361. package/dist/lib/profiles.js +2 -2
  362. package/dist/lib/project-import.js +4 -1
  363. package/dist/lib/project-launch.d.ts +1 -1
  364. package/dist/lib/project-launch.js +3 -3
  365. package/dist/lib/project-pull.d.ts +128 -0
  366. package/dist/lib/project-pull.js +384 -0
  367. package/dist/lib/project-root.d.ts +2 -2
  368. package/dist/lib/project-root.js +2 -2
  369. package/dist/lib/project-status.js +1 -1
  370. package/dist/lib/projects.d.ts +23 -0
  371. package/dist/lib/projects.js +29 -6
  372. package/dist/lib/refresh.d.ts +12 -1
  373. package/dist/lib/refresh.js +16 -9
  374. package/dist/lib/registry.d.ts +9 -2
  375. package/dist/lib/registry.js +11 -4
  376. package/dist/lib/resources.d.ts +2 -2
  377. package/dist/lib/resources.js +2 -2
  378. package/dist/lib/routers.d.ts +81 -0
  379. package/dist/lib/routers.js +153 -0
  380. package/dist/lib/routine-context.d.ts +1 -0
  381. package/dist/lib/routine-context.js +7 -2
  382. package/dist/lib/routine-notify-owner.d.ts +1 -1
  383. package/dist/lib/routine-notify.d.ts +1 -1
  384. package/dist/lib/routine-notify.js +1 -1
  385. package/dist/lib/routine-process-cleanup.d.ts +1 -1
  386. package/dist/lib/routine-readiness.d.ts +1 -1
  387. package/dist/lib/routine-readiness.js +11 -4
  388. package/dist/lib/routines-placement.d.ts +12 -2
  389. package/dist/lib/routines-placement.js +20 -3
  390. package/dist/lib/routines-project.d.ts +1 -1
  391. package/dist/lib/routines-project.js +1 -1
  392. package/dist/lib/rules/compile.d.ts +1 -0
  393. package/dist/lib/rules/compile.js +10 -2
  394. package/dist/lib/rules/rules.js +1 -1
  395. package/dist/lib/run-notify.js +1 -1
  396. package/dist/lib/runner.d.ts +80 -2
  397. package/dist/lib/runner.js +241 -62
  398. package/dist/lib/sandbox.d.ts +1 -1
  399. package/dist/lib/scheduler.d.ts +1 -1
  400. package/dist/lib/scheduler.js +1 -1
  401. package/dist/lib/{routines.d.ts → scheduling/routines.d.ts} +54 -9
  402. package/dist/lib/{routines.js → scheduling/routines.js} +173 -14
  403. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  404. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  405. package/dist/lib/secrets/agent.js +2 -2
  406. package/dist/lib/secrets/audit.js +1 -1
  407. package/dist/lib/secrets/bundles.d.ts +3 -3
  408. package/dist/lib/secrets/bundles.js +7 -7
  409. package/dist/lib/secrets/index.js +1 -1
  410. package/dist/lib/secrets/list-filter.d.ts +1 -1
  411. package/dist/lib/secrets/list-filter.js +2 -2
  412. package/dist/lib/secrets/push.d.ts +1 -1
  413. package/dist/lib/secrets/push.js +10 -12
  414. package/dist/lib/secrets/remote.d.ts +12 -10
  415. package/dist/lib/secrets/remote.js +21 -14
  416. package/dist/lib/secrets/vault.js +2 -2
  417. package/dist/lib/self-heal/checks/shadowing.js +1 -1
  418. package/dist/lib/self-heal/checks/shims.js +1 -1
  419. package/dist/lib/self-update.d.ts +39 -4
  420. package/dist/lib/self-update.js +78 -25
  421. package/dist/lib/serve/control.d.ts +1 -1
  422. package/dist/lib/serve/control.js +4 -4
  423. package/dist/lib/serve/data.d.ts +1 -1
  424. package/dist/lib/serve/data.js +1 -1
  425. package/dist/lib/serve/stream.js +1 -1
  426. package/dist/lib/service-manifest.d.ts +29 -0
  427. package/dist/lib/service-manifest.js +80 -0
  428. package/dist/lib/session/active.d.ts +59 -7
  429. package/dist/lib/session/active.js +158 -17
  430. package/dist/lib/session/bash-command.js +1 -1
  431. package/dist/lib/session/claude-accounts.d.ts +1 -1
  432. package/dist/lib/session/claude-accounts.js +1 -1
  433. package/dist/lib/session/db.d.ts +10 -2
  434. package/dist/lib/session/db.js +25 -6
  435. package/dist/lib/session/discover.d.ts +77 -0
  436. package/dist/lib/session/discover.js +130 -25
  437. package/dist/lib/session/fork.js +1 -1
  438. package/dist/lib/session/inject.d.ts +1 -1
  439. package/dist/lib/session/insights.d.ts +1 -1
  440. package/dist/lib/session/insights.js +1 -1
  441. package/dist/lib/session/live-metadata.d.ts +15 -0
  442. package/dist/lib/session/live-metadata.js +76 -0
  443. package/dist/lib/session/migrate-targets.d.ts +1 -1
  444. package/dist/lib/session/recovery.d.ts +1 -1
  445. package/dist/lib/session/recovery.js +2 -2
  446. package/dist/lib/session/remote-active.d.ts +10 -1
  447. package/dist/lib/session/remote-active.js +8 -3
  448. package/dist/lib/session/remote-bundle.d.ts +1 -1
  449. package/dist/lib/session/remote-bundle.js +2 -2
  450. package/dist/lib/session/remote-list.d.ts +15 -4
  451. package/dist/lib/session/remote-list.js +40 -3
  452. package/dist/lib/session/remote.d.ts +5 -5
  453. package/dist/lib/session/remote.js +13 -17
  454. package/dist/lib/session/resume-command.d.ts +3 -2
  455. package/dist/lib/session/resume-command.js +4 -3
  456. package/dist/lib/session/resume-owner.d.ts +3 -3
  457. package/dist/lib/session/resume-owner.js +3 -3
  458. package/dist/lib/session/session-cache.d.ts +12 -0
  459. package/dist/lib/session/session-cache.js +2 -0
  460. package/dist/lib/session/shell-programs.js +1 -1
  461. package/dist/lib/session/state.js +5 -5
  462. package/dist/lib/session/team-filter.d.ts +1 -1
  463. package/dist/lib/session/team-filter.js +1 -1
  464. package/dist/lib/session/types.d.ts +3 -3
  465. package/dist/lib/session/width.d.ts +2 -2
  466. package/dist/lib/session/width.js +2 -2
  467. package/dist/lib/share/delete.d.ts +21 -0
  468. package/dist/lib/share/delete.js +42 -0
  469. package/dist/lib/share/publish.d.ts +82 -0
  470. package/dist/lib/share/publish.js +170 -18
  471. package/dist/lib/share/worker-template.js +172 -9
  472. package/dist/lib/shims.js +35 -7
  473. package/dist/lib/smart-launch.d.ts +4 -4
  474. package/dist/lib/smart-launch.js +6 -4
  475. package/dist/lib/snapshot.d.ts +4 -4
  476. package/dist/lib/snapshot.js +2 -2
  477. package/dist/lib/ssh-exec.d.ts +1 -1
  478. package/dist/lib/ssh-exec.js +2 -2
  479. package/dist/lib/ssh-tunnel.d.ts +10 -10
  480. package/dist/lib/ssh-tunnel.js +12 -12
  481. package/dist/lib/staleness/checkers/hooks.d.ts +1 -1
  482. package/dist/lib/staleness/checkers/hooks.js +1 -1
  483. package/dist/lib/staleness/detectors/permissions.js +0 -23
  484. package/dist/lib/staleness/detectors/plugins.js +1 -1
  485. package/dist/lib/staleness/fingerprint.js +1 -1
  486. package/dist/lib/staleness/layers.d.ts +1 -1
  487. package/dist/lib/staleness/layers.js +1 -1
  488. package/dist/lib/staleness/writers/mcp.js +3 -1
  489. package/dist/lib/staleness/writers/plugins.js +1 -1
  490. package/dist/lib/staleness/writers/types.d.ts +8 -0
  491. package/dist/lib/startup/command-registry.d.ts +5 -147
  492. package/dist/lib/startup/command-registry.js +38 -303
  493. package/dist/lib/startup/dev-build.d.ts +0 -21
  494. package/dist/lib/startup/dev-build.js +29 -8
  495. package/dist/lib/state.d.ts +19 -1
  496. package/dist/lib/state.js +120 -51
  497. package/dist/lib/subagents.js +1 -1
  498. package/dist/lib/sync-status.js +1 -1
  499. package/dist/lib/sync-umbrella.d.ts +6 -1
  500. package/dist/lib/sync-umbrella.js +9 -4
  501. package/dist/lib/teams/agents.js +2 -2
  502. package/dist/lib/teams/registry.js +1 -1
  503. package/dist/lib/terminal/backends/vscodium-agent.js +1 -1
  504. package/dist/lib/terminal/engine.js +1 -1
  505. package/dist/lib/terminal/index.d.ts +4 -0
  506. package/dist/lib/terminal/index.js +3 -0
  507. package/dist/lib/terminal/inject.d.ts +2 -2
  508. package/dist/lib/terminal/inject.js +2 -2
  509. package/dist/lib/terminal/transport.js +1 -1
  510. package/dist/lib/tmux/index.d.ts +1 -1
  511. package/dist/lib/tmux/index.js +1 -1
  512. package/dist/lib/tmux/session.d.ts +21 -5
  513. package/dist/lib/tmux/session.js +76 -22
  514. package/dist/lib/triggers/handlers.d.ts +1 -1
  515. package/dist/lib/triggers/handlers.js +2 -2
  516. package/dist/lib/triggers/webhook.d.ts +36 -2
  517. package/dist/lib/triggers/webhook.js +97 -42
  518. package/dist/lib/types.d.ts +18 -6
  519. package/dist/lib/usage-fleet.d.ts +1 -1
  520. package/dist/lib/usage-fleet.js +1 -1
  521. package/dist/lib/usage-refresh.d.ts +1 -1
  522. package/dist/lib/usage-refresh.js +3 -3
  523. package/dist/lib/version-duplicates.js +2 -2
  524. package/dist/lib/view-types.d.ts +56 -0
  525. package/dist/lib/view-types.js +1 -0
  526. package/dist/lib/watchdog/rotate.d.ts +2 -2
  527. package/dist/lib/watchdog/rotate.js +3 -3
  528. package/dist/lib/watchdog/runner.d.ts +2 -3
  529. package/dist/lib/watchdog/runner.js +3 -4
  530. package/dist/lib/workflows.js +1 -1
  531. package/package.json +2 -1
  532. package/scripts/postinstall.js +2 -2
  533. package/dist/commands/bench.d.ts +0 -2
  534. package/dist/commands/bench.js +0 -101
  535. package/dist/commands/cp.d.ts +0 -84
  536. package/dist/commands/cp.js +0 -231
  537. package/dist/commands/login.d.ts +0 -2
  538. package/dist/lib/resources/commands.d.ts +0 -46
  539. package/dist/lib/resources/commands.js +0 -211
  540. package/dist/lib/resources/hooks.d.ts +0 -16
  541. package/dist/lib/resources/hooks.js +0 -153
  542. package/dist/lib/resources/mcp.d.ts +0 -34
  543. package/dist/lib/resources/mcp.js +0 -250
  544. package/dist/lib/resources/memory.d.ts +0 -15
  545. package/dist/lib/resources/memory.js +0 -46
  546. package/dist/lib/resources/permissions.d.ts +0 -13
  547. package/dist/lib/resources/permissions.js +0 -202
  548. package/dist/lib/resources/rules.d.ts +0 -46
  549. package/dist/lib/resources/rules.js +0 -179
  550. package/dist/lib/resources/skills.d.ts +0 -37
  551. package/dist/lib/resources/skills.js +0 -247
  552. package/dist/lib/resources/subagents.d.ts +0 -46
  553. package/dist/lib/resources/subagents.js +0 -202
  554. /package/dist/lib/{feed.d.ts → feed/feed.d.ts} +0 -0
@@ -9,11 +9,108 @@ import { readFileSync } from 'node:fs';
9
9
  import { basename } from 'node:path';
10
10
  import { execFileSync } from 'node:child_process';
11
11
  import { randomBytes } from 'node:crypto';
12
+ import { hostname as osHostname } from 'node:os';
12
13
  import { readShareConfig, readWriteToken } from './config.js';
13
14
  import { resolveGitHubUsername } from '../git.js';
14
15
  import { captureCover, OG_WIDTH, OG_HEIGHT, OG_SCALE } from './capture.js';
15
16
  import { deriveMeta, injectOgMeta } from './og.js';
16
17
  import { injectAnalyticsBeacon } from './analytics.js';
18
+ /**
19
+ * Reserved `customMetadata` keys the CLI sets automatically from the exec env,
20
+ * git, and the local clock (plus `label`/`label-source`) — a `--meta key=value`
21
+ * may not target any of these (see {@link parseMetaEntries}).
22
+ */
23
+ export const RESERVED_META_KEYS = ['agent', 'session', 'host', 'repo', 'date', 'label', 'label-source'];
24
+ const META_KEY_RE = /^[a-z0-9-]{1,64}$/;
25
+ /**
26
+ * Auto-capture publish provenance from the exec env, git, and the local clock.
27
+ * Every field is present only when the environment genuinely carries it — a
28
+ * human running the command by hand outside a git repo yields `session`/`agent`/
29
+ * `repo` all undefined, never an invented value. `host` is always present
30
+ * (`os.hostname()` never fails to return something real about where the publish
31
+ * ran, so it isn't "invented" in the same sense).
32
+ */
33
+ export function resolveShareProvenance(opts = {}) {
34
+ const env = opts.env ?? process.env;
35
+ return {
36
+ session: env.AGENTS_SESSION_ID || env.AGENT_SESSION_ID || undefined,
37
+ agent: env.AGENTS_AGENT_NAME || undefined,
38
+ host: opts.hostname ?? osHostname(),
39
+ repo: gitRepoName(opts.dir ?? process.cwd()),
40
+ date: (opts.now ?? new Date()).toISOString().slice(0, 10),
41
+ };
42
+ }
43
+ /**
44
+ * Parse repeated `--meta key=value` CLI args into a validated metadata record.
45
+ * Keys are lowercase `[a-z0-9-]`, up to 64 characters, and may not collide with
46
+ * {@link RESERVED_META_KEYS} (the provenance the CLI sets automatically, plus
47
+ * label/label-source) — throws naming the offending pair on any violation.
48
+ */
49
+ export function parseMetaEntries(pairs) {
50
+ const meta = {};
51
+ for (const pair of pairs) {
52
+ const eq = pair.indexOf('=');
53
+ if (eq <= 0) {
54
+ throw new Error(`Bad --meta '${pair}'. Expected key=value.`);
55
+ }
56
+ const key = pair.slice(0, eq).trim();
57
+ const value = pair.slice(eq + 1);
58
+ if (!META_KEY_RE.test(key)) {
59
+ throw new Error(`Bad --meta key '${key}'. Keys are lowercase letters, digits, and hyphens, up to 64 characters.`);
60
+ }
61
+ if (RESERVED_META_KEYS.includes(key)) {
62
+ throw new Error(`--meta ${key}=… is reserved (set automatically from your session/git) — pass a different key.`);
63
+ }
64
+ meta[key] = value;
65
+ }
66
+ return meta;
67
+ }
68
+ /** S3's `x-amz-meta` convention caps user metadata around 2KB; R2 publishes no
69
+ * hard limit of its own, so stay under that ceiling to keep a share portable to
70
+ * an S3-compatible mirror. Checked over the FULL customMetadata payload
71
+ * (provenance + label + --meta combined), since that's what actually gets
72
+ * written to the object. */
73
+ const MAX_METADATA_BYTES = 2048;
74
+ /** Throws when the combined `customMetadata` payload would exceed {@link MAX_METADATA_BYTES}. */
75
+ export function assertMetadataSize(customMetadata) {
76
+ const bytes = Buffer.byteLength(JSON.stringify(customMetadata), 'utf8');
77
+ if (bytes > MAX_METADATA_BYTES) {
78
+ throw new Error(`Share metadata is ${bytes} bytes, over the ${MAX_METADATA_BYTES}-byte cap ` +
79
+ `(provenance + --label + --meta combined). Trim your --meta values.`);
80
+ }
81
+ }
82
+ /**
83
+ * Collapse a label to a single line before it goes into the `x-share-label`
84
+ * header or public customMetadata. `<title>[^<]{1,200}</title>` matches
85
+ * newlines (`[^<]` excludes only `<`), and `.trim()` only strips leading/
86
+ * trailing whitespace, not embedded newlines — a multi-line `<title>` (or an
87
+ * explicit `--label`/`--title` the caller typed with a literal newline) was
88
+ * previously passed straight into `Headers.set()` unsanitized, which throws
89
+ * an unhandled `TypeError: Invalid value` and crashes the publish outright.
90
+ */
91
+ export function sanitizeLabel(text) {
92
+ return text.replace(/\s+/g, ' ').trim();
93
+ }
94
+ /**
95
+ * Best-effort human title when `--label` is omitted: the HTML `<title>`, else a
96
+ * Markdown frontmatter `title:`, else the filename. Always returns something —
97
+ * a headless publish must never hang waiting on a prompt for one.
98
+ */
99
+ export function deriveLabel(filePath, body) {
100
+ const text = body.toString('utf8');
101
+ const htmlTitle = /<title[^>]*>([^<]{1,200})<\/title>/i.exec(text);
102
+ if (htmlTitle?.[1]?.trim())
103
+ return sanitizeLabel(htmlTitle[1]);
104
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
105
+ if (frontmatter) {
106
+ const titleLine = /^title:\s*(.+)$/m.exec(frontmatter[1]);
107
+ const cleaned = titleLine?.[1]?.trim().replace(/^["']|["']$/g, '').trim();
108
+ if (cleaned)
109
+ return sanitizeLabel(cleaned);
110
+ }
111
+ const base = basename(filePath).replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' ').trim();
112
+ return sanitizeLabel(base || basename(filePath));
113
+ }
17
114
  /** Default auto-expire for unflagged publishes — accidental links decay (RUSH-2443). */
18
115
  export const DEFAULT_SHARE_EXPIRE = '30d';
19
116
  const UNIT_MS = { s: 1e3, m: 6e4, h: 36e5, d: 864e5, w: 6048e5 };
@@ -118,7 +215,7 @@ export function formatSensitiveContentError(hits) {
118
215
  : kinds[0] === 'email'
119
216
  ? 'email addresses'
120
217
  : 'credential-shaped strings';
121
- return (`Refusing to publish: the file contains ${what} (${samples}). ` +
218
+ return (`Refusing to publish: found ${what} (${samples}) in the file, label, or metadata. ` +
122
219
  `Shares are world-readable by URL — pass --force to publish anyway, ` +
123
220
  `or --unlisted --expire 12h to bound the blast radius.`);
124
221
  }
@@ -134,8 +231,9 @@ export function slugify(name) {
134
231
  function sanitizeSlugPart(s) {
135
232
  return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
136
233
  }
137
- /** The project the file belongs to git repo name, else the cwd's basename. */
138
- export function detectProject(dir = process.cwd()) {
234
+ /** The current repo's name, or undefined outside a git checkout never a
235
+ * fallback guess, since callers that want one (detectProject) supply it themselves. */
236
+ function gitRepoName(dir) {
139
237
  try {
140
238
  const top = execFileSync('git', ['-C', dir, 'rev-parse', '--show-toplevel'], {
141
239
  stdio: ['ignore', 'pipe', 'ignore'],
@@ -143,12 +241,16 @@ export function detectProject(dir = process.cwd()) {
143
241
  .toString()
144
242
  .trim();
145
243
  if (top)
146
- return sanitizeSlugPart(basename(top)) || 'share';
244
+ return sanitizeSlugPart(basename(top)) || undefined;
147
245
  }
148
246
  catch {
149
- // not a git repo — fall through to cwd basename
247
+ // not a git repo
150
248
  }
151
- return sanitizeSlugPart(basename(dir)) || 'share';
249
+ return undefined;
250
+ }
251
+ /** The project the file belongs to — git repo name, else the cwd's basename. */
252
+ export function detectProject(dir = process.cwd()) {
253
+ return gitRepoName(dir) ?? (sanitizeSlugPart(basename(dir)) || 'share');
152
254
  }
153
255
  /**
154
256
  * Notion-style default slug: `<project>-<feature>-<16hex>`. Project scopes the link
@@ -273,31 +375,79 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
273
375
  const expiresAt = resolveExpire(opts.expire);
274
376
  const unlisted = opts.unlisted === true;
275
377
  const pageUrl = `${endpoint.baseUrl.replace(/\/+$/, '')}/${key}`;
378
+ const provenance = opts.provenance ?? resolveShareProvenance();
379
+ const meta = opts.meta ?? {};
276
380
  const put = opts.uploader ??
277
381
  (async (u, b, h) => {
278
382
  const res = await fetch(u, { method: 'PUT', headers: h, body: new Uint8Array(b) });
279
383
  return { ok: res.ok, status: res.status, url: u };
280
384
  });
281
- const authHeaders = (contentType) => {
282
- const h = { authorization: `Bearer ${endpoint.token}`, 'content-type': contentType };
283
- if (expiresAt)
284
- h['x-share-expires-at'] = expiresAt;
285
- if (unlisted)
286
- h['x-share-visibility'] = 'unlisted';
287
- return h;
288
- };
289
385
  let body = readFileSync(filePath);
290
386
  let coverUrl;
291
387
  const isHtml = /\.html?$/i.test(filePath);
292
- // Pre-publish scan (RUSH-2443): refuse emails / credential-shaped strings
293
- // unless --force. Runs on the raw file before analytics/cover mutation so a
294
- // beacon injection never triggers a false positive. Binary media is a no-op.
388
+ const explicitLabel = opts.label?.trim();
389
+ // sanitizeLabel here (not just inside deriveLabel) covers an explicit
390
+ // --label/--title the caller typed with an embedded newline deriveLabel
391
+ // is only reached when --label is omitted.
392
+ const label = explicitLabel ? sanitizeLabel(explicitLabel) : deriveLabel(filePath, body);
393
+ const labelSource = explicitLabel ? 'explicit' : 'derived';
394
+ // Pre-publish scan (RUSH-2443/RUSH-2683): refuse emails / credential-shaped
395
+ // strings unless --force. Runs on the raw file body AND on every piece of
396
+ // free-text metadata that lands in public customMetadata — --label (explicit
397
+ // or derived) and every --meta value. Metadata is visible in the gallery,
398
+ // `share list --json`, and `share revisions` just like the page itself, so a
399
+ // credential smuggled in there is exactly as exposed as one in the body; it
400
+ // must not have a free pass around this gate. Runs before analytics/cover
401
+ // mutation so a beacon injection never triggers a false positive on the body
402
+ // scan. Binary media bodies are a no-op for the body scan.
295
403
  if (opts.force !== true) {
296
- const hits = scanShareContent(body);
404
+ const hits = [
405
+ ...scanShareContent(body),
406
+ ...scanShareContent(label),
407
+ ...Object.values(meta).flatMap((v) => scanShareContent(v)),
408
+ ];
297
409
  if (hits.length > 0) {
298
410
  throw new Error(formatSensitiveContentError(hits));
299
411
  }
300
412
  }
413
+ // Validate the FULL customMetadata payload before any network call — fail
414
+ // fast, not mid-upload.
415
+ const metadataPreview = { ...meta, label, 'label-source': labelSource };
416
+ if (provenance.agent)
417
+ metadataPreview.agent = provenance.agent;
418
+ if (provenance.session)
419
+ metadataPreview.session = provenance.session;
420
+ if (provenance.host)
421
+ metadataPreview.host = provenance.host;
422
+ if (provenance.repo)
423
+ metadataPreview.repo = provenance.repo;
424
+ if (provenance.date)
425
+ metadataPreview.date = provenance.date;
426
+ assertMetadataSize(metadataPreview);
427
+ const authHeaders = (contentType) => {
428
+ const h = { authorization: `Bearer ${endpoint.token}`, 'content-type': contentType };
429
+ if (expiresAt)
430
+ h['x-share-expires-at'] = expiresAt;
431
+ if (unlisted)
432
+ h['x-share-visibility'] = 'unlisted';
433
+ if (provenance.agent)
434
+ h['x-share-agent'] = provenance.agent;
435
+ if (provenance.session)
436
+ h['x-share-session'] = provenance.session;
437
+ if (provenance.host)
438
+ h['x-share-host'] = provenance.host;
439
+ if (provenance.repo)
440
+ h['x-share-repo'] = provenance.repo;
441
+ if (provenance.date)
442
+ h['x-share-date'] = provenance.date;
443
+ h['x-share-label'] = label;
444
+ h['x-share-label-source'] = labelSource;
445
+ if (Object.keys(meta).length > 0)
446
+ h['x-share-meta'] = JSON.stringify(meta);
447
+ if (opts.noRevision)
448
+ h['x-share-no-revision'] = '1';
449
+ return h;
450
+ };
301
451
  // Analytics: cookieless CF Web Analytics beacon, injected for HTML by default.
302
452
  if (isHtml && opts.analytics !== false && opts.analyticsToken) {
303
453
  body = Buffer.from(injectAnalyticsBeacon(body.toString('utf8'), opts.analyticsToken), 'utf8');
@@ -324,6 +474,8 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
324
474
  url: r.url ?? pageUrl,
325
475
  expiresAt,
326
476
  coverUrl,
477
+ label,
478
+ labelSource,
327
479
  ...(unlisted ? { unlisted: true } : {}),
328
480
  };
329
481
  }
@@ -2,11 +2,16 @@
2
2
  //
3
3
  // One tiny Worker does both sides:
4
4
  // - PUT /<username>/<slug> — bearer-gated (WRITE_TOKEN secret); writes the body
5
- // to R2 via the BUCKET binding, storing an optional expires-at value in
6
- // object metadata.
5
+ // to R2 via the BUCKET binding, storing an optional expires-at value plus
6
+ // provenance (agent/session/host/repo/date), a label, and any `--meta`
7
+ // entries in object metadata. Overwriting an existing slug first copies the
8
+ // current object to <slug>/rev-<ts>-<rand> (revision history) unless
9
+ // x-share-no-revision is set.
7
10
  // - GET /<username>/<slug> — public; streams the object from R2, 410s (and lazily
8
11
  // deletes) once its expiry has passed. A bucket lifecycle rule is the durable
9
12
  // sweeper; this is the immediate gate.
13
+ // - GET /<username>/<slug>?revisions=json — machine-readable history of the
14
+ // retained prior versions under that slug, newest first.
10
15
  // - GET /<username> — public gallery of that user's shares (HTML).
11
16
  // - GET /<username>?format=json — public machine-readable listing of that user's
12
17
  // ACTIVE shares (`agents artifacts share list`). Same single-segment path as the HTML
@@ -55,9 +60,71 @@ export default {
55
60
  // keeping the direct URL world-readable (capability URL, not secret).
56
61
  const visibility = (request.headers.get('x-share-visibility') || '').toLowerCase();
57
62
  const contentType = request.headers.get('content-type') || 'text/html; charset=utf-8';
58
- const customMetadata = {};
63
+ // Provenance (RUSH-2683): captured client-side from the exec env/git/clock,
64
+ // never invented here — a header is simply absent when the CLI had nothing
65
+ // to say. --meta entries ride one JSON header; reserved keys are stripped
66
+ // from that JSON UNCONDITIONALLY (not just overwritten when a provenance
67
+ // header happens to be present) so a same-named x-share-meta entry can
68
+ // never smuggle through on a publish that carries no agent/session/host/
69
+ // repo/date at all — e.g. a human publishing outside an agent session, or
70
+ // outside a git checkout.
71
+ const agent = request.headers.get('x-share-agent') || '';
72
+ const session = request.headers.get('x-share-session') || '';
73
+ const host = request.headers.get('x-share-host') || '';
74
+ const repo = request.headers.get('x-share-repo') || '';
75
+ const date = request.headers.get('x-share-date') || '';
76
+ const label = request.headers.get('x-share-label') || '';
77
+ const labelSource = request.headers.get('x-share-label-source') || '';
78
+ let extraMeta = {};
79
+ const metaHeader = request.headers.get('x-share-meta');
80
+ if (metaHeader) {
81
+ try {
82
+ const parsed = JSON.parse(metaHeader);
83
+ if (parsed && typeof parsed === 'object') extraMeta = parsed;
84
+ } catch {
85
+ // malformed --meta header — ignore rather than fail the whole publish
86
+ }
87
+ }
88
+ const customMetadata = { ...extraMeta };
89
+ // Strip every reserved key UNCONDITIONALLY before re-applying the real
90
+ // provenance below — an if(value)-guarded overwrite alone leaves a
91
+ // same-named --meta entry in place whenever the real header is absent.
92
+ for (const reservedKey of RESERVED_METADATA_KEYS) {
93
+ delete customMetadata[reservedKey];
94
+ }
59
95
  if (expiresAt) customMetadata['expires-at'] = expiresAt;
60
96
  if (visibility === 'unlisted') customMetadata['visibility'] = 'unlisted';
97
+ if (agent) customMetadata['agent'] = agent;
98
+ if (session) customMetadata['session'] = session;
99
+ if (host) customMetadata['host'] = host;
100
+ if (repo) customMetadata['repo'] = repo;
101
+ if (date) customMetadata['date'] = date;
102
+ if (label) customMetadata['label'] = label;
103
+ if (labelSource) customMetadata['label-source'] = labelSource;
104
+
105
+ // Revision retention (RUSH-2683): R2 has no native object versioning, so a
106
+ // republish over an EXISTING key first copies the current object to
107
+ // <key>/rev-<ts>-<rand> before the canonical key is overwritten. Default
108
+ // keep-all; --no-revision (x-share-no-revision) skips it. The random
109
+ // suffix guards against two rapid overwrites of the same slug colliding on
110
+ // the millisecond, and against a slug whose canonical key already sits 3+
111
+ // segments deep (an unsupported shape outside the CLI) landing on the same
112
+ // literal revision key.
113
+ const noRevision = !!request.headers.get('x-share-no-revision');
114
+ if (!noRevision) {
115
+ const existing = await env.BUCKET.get(path);
116
+ if (existing) {
117
+ const existingHeaders = new Headers();
118
+ if (typeof existing.writeHttpMetadata === 'function') existing.writeHttpMetadata(existingHeaders);
119
+ const existingContentType = existingHeaders.get('content-type');
120
+ const revKey = path + '/rev-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8);
121
+ await env.BUCKET.put(revKey, existing.body, {
122
+ httpMetadata: existingContentType ? { contentType: existingContentType } : undefined,
123
+ customMetadata: existing.customMetadata || {},
124
+ });
125
+ }
126
+ }
127
+
61
128
  await env.BUCKET.put(path, request.body, {
62
129
  httpMetadata: { contentType },
63
130
  customMetadata,
@@ -88,6 +155,13 @@ export default {
88
155
  // signal rather than a missing route).
89
156
  }
90
157
 
158
+ // Revision history for one canonical <user>/<slug> key (RUSH-2683). Checked
159
+ // before the plain object GET below so the query param routes even though
160
+ // the canonical key itself resolves to a real object.
161
+ if (segments.length === 2 && url.searchParams.get('revisions') === 'json') {
162
+ return renderRevisions(env.BUCKET, url.origin, path, request.method);
163
+ }
164
+
91
165
  const obj = await env.BUCKET.get(path);
92
166
  if (!obj) return new Response('not found', { status: 404, headers: { 'content-type': 'text/plain' } });
93
167
  const expiresAt = obj.customMetadata && obj.customMetadata['expires-at'];
@@ -114,6 +188,16 @@ export default {
114
188
  },
115
189
  };
116
190
 
191
+ // A canonical share object's key is exactly 2 segments (<user>/<slug>, or the
192
+ // sibling <user>/<slug>.png OG cover). A retained revision lives a 3rd segment
193
+ // deeper (<user>/<slug>/rev-<ts>-<rand>) — history, not a page in its own
194
+ // right, so every gallery/listing view hides it. A raw external PUT to an
195
+ // already-3-segment-deep path is treated the same way for the same reason:
196
+ // there is no ambiguity to resolve, and the CLI never produces such a key.
197
+ function isRevisionKey(key) {
198
+ return key.split('/').length > 2;
199
+ }
200
+
117
201
  async function renderGallery(bucket, origin, user, method) {
118
202
  const objects = [];
119
203
  let cursor;
@@ -124,6 +208,7 @@ async function renderGallery(bucket, origin, user, method) {
124
208
  } while (cursor);
125
209
 
126
210
  const activeObjects = objects.filter(o => {
211
+ if (isRevisionKey(o.key)) return false;
127
212
  if (o.key.endsWith('.png')) return false;
128
213
  // Unlisted pages are reachable by direct URL only — never on the gallery.
129
214
  if (o.customMetadata && o.customMetadata.visibility === 'unlisted') return false;
@@ -133,7 +218,9 @@ async function renderGallery(bucket, origin, user, method) {
133
218
  const items = activeObjects.map(o => {
134
219
  const slug = o.key.slice(o.key.indexOf('/') + 1);
135
220
  const url = origin + '/' + o.key;
136
- return { slug, url, updated: o.uploaded };
221
+ const label = (o.customMetadata && o.customMetadata['label']) || '';
222
+ const agent = (o.customMetadata && o.customMetadata['agent']) || '';
223
+ return { slug, url, updated: o.uploaded, label, agent };
137
224
  });
138
225
 
139
226
  if (method === 'HEAD') {
@@ -148,14 +235,17 @@ async function renderGallery(bucket, origin, user, method) {
148
235
  'body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;max-width:720px;margin:48px auto;padding:0 24px;color:#111;background:#fafafa}' +
149
236
  'h1{font-size:28px;margin-bottom:8px}a{color:#0a0a0a;text-decoration:none;border-bottom:1px solid #999}' +
150
237
  'a:hover{border-color:#111}ul{list-style:none;padding:0}li{margin:16px 0}' +
151
- '.slug{font-weight:600}.meta{color:#666;font-size:14px}' +
238
+ '.slug{font-weight:600}.title{font-weight:600}.meta{color:#666;font-size:14px}' +
152
239
  '</style></head><body>' +
153
240
  '<h1>@' + escapeHtml(user) + '</h1>' +
154
241
  '<p class="meta">' + items.length + ' shared ' + (items.length === 1 ? 'page' : 'pages') + '</p>' +
155
242
  '<ul>' +
156
243
  items.map(i =>
157
- '<li><a class="slug" href="' + escapeHtml(i.url) + '">' + escapeHtml(i.slug) + '</a>' +
158
- '<br><span class="meta">' + new Date(i.updated).toISOString().slice(0, 10) + '</span></li>'
244
+ '<li>' +
245
+ (i.label ? '<span class="title">' + escapeHtml(i.label) + '</span><br>' : '') +
246
+ '<a class="slug" href="' + escapeHtml(i.url) + '">' + escapeHtml(i.slug) + '</a>' +
247
+ '<br><span class="meta">' + new Date(i.updated).toISOString().slice(0, 10) +
248
+ (i.agent ? ' · ' + escapeHtml(i.agent) : '') + '</span></li>'
159
249
  ).join('') +
160
250
  '</ul></body></html>';
161
251
  return new Response(html, { status: 200, headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'public, max-age=60' } });
@@ -170,11 +260,21 @@ async function renderListing(bucket, origin, user, method) {
170
260
  cursor = list.truncated ? list.cursor : undefined;
171
261
  } while (cursor);
172
262
 
263
+ // Revisions never appear as their own listing row, but they count toward
264
+ // their canonical object's revisionCount.
265
+ const revisionCounts = {};
266
+ for (const o of objects) {
267
+ if (!isRevisionKey(o.key)) continue;
268
+ const canonicalKey = o.key.split('/').slice(0, 2).join('/');
269
+ revisionCounts[canonicalKey] = (revisionCounts[canonicalKey] || 0) + 1;
270
+ }
271
+
173
272
  const now = Date.now();
174
273
  const items = objects
175
274
  .filter(o => {
176
- // Mirror the gallery: hide sibling .png OG covers, expired pages, and
177
- // unlisted pages — the listing is the machine-readable public surface.
275
+ // Mirror the gallery: hide revisions, sibling .png OG covers, expired
276
+ // pages, and unlisted pages — the listing is the machine-readable public surface.
277
+ if (isRevisionKey(o.key)) return false;
178
278
  if (o.key.endsWith('.png')) return false;
179
279
  if (o.customMetadata && o.customMetadata.visibility === 'unlisted') return false;
180
280
  const expiresAt = o.customMetadata && o.customMetadata['expires-at'];
@@ -187,6 +287,13 @@ async function renderListing(bucket, origin, user, method) {
187
287
  contentType: (o.httpMetadata && o.httpMetadata.contentType) || null,
188
288
  publishedAt: new Date(o.uploaded).toISOString(),
189
289
  expiresAt: (o.customMetadata && o.customMetadata['expires-at']) || null,
290
+ label: (o.customMetadata && o.customMetadata['label']) || null,
291
+ agent: (o.customMetadata && o.customMetadata['agent']) || null,
292
+ session: (o.customMetadata && o.customMetadata['session']) || null,
293
+ host: (o.customMetadata && o.customMetadata['host']) || null,
294
+ repo: (o.customMetadata && o.customMetadata['repo']) || null,
295
+ revisionCount: revisionCounts[o.key] || 0,
296
+ meta: extraMetaOf(o.customMetadata),
190
297
  }));
191
298
  // Newest first, so the human table and any script reads the freshest share top.
192
299
  items.sort((a, b) => (a.publishedAt < b.publishedAt ? 1 : a.publishedAt > b.publishedAt ? -1 : 0));
@@ -200,6 +307,62 @@ async function renderListing(bucket, origin, user, method) {
200
307
  });
201
308
  }
202
309
 
310
+ async function renderRevisions(bucket, origin, key, method) {
311
+ const objects = [];
312
+ let cursor;
313
+ do {
314
+ const list = await bucket.list({ prefix: key + '/rev-', limit: 1000, cursor, include: ['httpMetadata', 'customMetadata'] });
315
+ objects.push(...(list.objects || []));
316
+ cursor = list.truncated ? list.cursor : undefined;
317
+ } while (cursor);
318
+
319
+ const items = objects.map(o => ({
320
+ key: o.key,
321
+ url: origin + '/' + o.key,
322
+ size: typeof o.size === 'number' ? o.size : 0,
323
+ contentType: (o.httpMetadata && o.httpMetadata.contentType) || null,
324
+ uploadedAt: new Date(o.uploaded).toISOString(),
325
+ expiresAt: (o.customMetadata && o.customMetadata['expires-at']) || null,
326
+ label: (o.customMetadata && o.customMetadata['label']) || null,
327
+ agent: (o.customMetadata && o.customMetadata['agent']) || null,
328
+ session: (o.customMetadata && o.customMetadata['session']) || null,
329
+ host: (o.customMetadata && o.customMetadata['host']) || null,
330
+ repo: (o.customMetadata && o.customMetadata['repo']) || null,
331
+ meta: extraMetaOf(o.customMetadata),
332
+ }));
333
+ // Newest first — the most recently replaced version leads.
334
+ items.sort((a, b) => (a.uploadedAt < b.uploadedAt ? 1 : a.uploadedAt > b.uploadedAt ? -1 : 0));
335
+
336
+ if (method === 'HEAD') {
337
+ return new Response(null, { status: 200, headers: { 'content-type': 'application/json; charset=utf-8' } });
338
+ }
339
+ return new Response(JSON.stringify({ key, count: items.length, revisions: items }), {
340
+ status: 200,
341
+ headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'public, max-age=30' },
342
+ });
343
+ }
344
+
345
+ // The customMetadata keys the CLI sets automatically (provenance + label) —
346
+ // never a real \`--meta key=value\` entry (the CLI rejects a colliding key
347
+ // before it ever reaches this Worker; see RESERVED_META_KEYS in publish.ts).
348
+ // One list, reused both to strip a same-named --meta collision on write and
349
+ // to split arbitrary --meta entries back out on read.
350
+ var RESERVED_METADATA_KEYS = ['expires-at', 'visibility', 'agent', 'session', 'host', 'repo', 'date', 'label', 'label-source'];
351
+
352
+ // Everything in customMetadata that ISN'T one of the reserved provenance/label
353
+ // keys above — i.e. the caller's own \`--meta key=value\` entries. Surfaced on
354
+ // every read route (listing, revisions) so a value stored with \`--meta
355
+ // kind=plan --meta ticket=RUSH-2683\` is actually visible again, not just
356
+ // write-only (RUSH-2683 review fix).
357
+ function extraMetaOf(customMetadata) {
358
+ var out = {};
359
+ if (!customMetadata) return out;
360
+ for (var k in customMetadata) {
361
+ if (RESERVED_METADATA_KEYS.indexOf(k) === -1) out[k] = customMetadata[k];
362
+ }
363
+ return out;
364
+ }
365
+
203
366
  function escapeHtml(s) {
204
367
  return String(s)
205
368
  .replace(/&/g, '&amp;')
package/dist/lib/shims.js CHANGED
@@ -504,6 +504,32 @@ parse_agents_default() {
504
504
  ' "$meta"
505
505
  }
506
506
 
507
+ # Parse the pins JSON (~/.agents/.history/devices/pins-<machine>.json) for this
508
+ # AGENT's pinned version. The file is JSON.stringify(…, 2) output, so the
509
+ # "agents" map sits at 2-space indent and its entries at 4 — a stable shape the
510
+ # awk below scrapes without a JSON parser (shims must stay dependency-free).
511
+ parse_pins_default() {
512
+ local pins="$1"
513
+ [ -f "$pins" ] || return 0
514
+ # Scope carefully: enter ONLY on a line that opens the agents block
515
+ # ( "agents": {) — an inline-empty map ("agents": {}) must NOT enter,
516
+ # or the needle would leak into a following "isolatedAgents" block and an
517
+ # isolated pin would masquerade as the global default. Exit at the block's
518
+ # closing brace (a trailing comma is fine). Entries sit at exactly 4 spaces.
519
+ awk -v agent="$AGENT" '
520
+ /^ "agents": [{]$/ { in_agents=1; next }
521
+ in_agents && /^ }/ { exit }
522
+ in_agents && /^ "/ {
523
+ needle = "\\"" agent "\\":"
524
+ if (index($0, needle) > 0) {
525
+ line = substr($0, index($0, needle) + length(needle))
526
+ gsub(/[[:space:],"]/, "", line)
527
+ print line; exit
528
+ }
529
+ }
530
+ ' "$pins"
531
+ }
532
+
507
533
  # This machine's device id — mirrors machineId()/normalizeHost() in
508
534
  # src/lib/machine-id.ts: first hostname label, lowercased, non-[a-z0-9_-] -> '-'.
509
535
  # MUST stay in sync or the shim reads the wrong device folder.
@@ -514,15 +540,17 @@ machine_id() {
514
540
  [ -n "$raw" ] && printf '%s' "$raw" || printf 'unknown'
515
541
  }
516
542
 
517
- # Resolve the default version. The agents: version pins are stored PER-DEVICE at
518
- # devices/<machine>/agents.yaml (moved there so multi-machine syncs never
519
- # conflict); read that first, then fall back to the central agents.yaml for
520
- # pre-split installs. Must match readMeta()'s central+device merge in state.ts --
521
- # reading only the central file (the old behavior) missed every device pin and
522
- # made the shim re-prompt "no default set" on every launch.
543
+ # Resolve the default version. The agents: version pins are MACHINE-LOCAL runtime
544
+ # state at .history/devices/pins-<machine>.json (untracked auto-written pins in
545
+ # the tracked device doc caused commit churn); read that first, then fall back to
546
+ # the tracked device doc (installs not yet migrated) and finally the central
547
+ # agents.yaml (pre-split installs). Must match readMeta()'s central+pins merge in
548
+ # state.ts -- reading only the central file (the old behavior) missed every device
549
+ # pin and made the shim re-prompt "no default set" on every launch.
523
550
  resolve_default_version() {
524
551
  local v
525
- v=$(parse_agents_default "$AGENTS_USER_DIR/devices/$(machine_id)/agents.yaml")
552
+ v=$(parse_pins_default "$AGENTS_USER_DIR/.history/devices/pins-$(machine_id).json")
553
+ [ -n "$v" ] || v=$(parse_agents_default "$AGENTS_USER_DIR/devices/$(machine_id)/agents.yaml")
526
554
  [ -n "$v" ] || v=$(parse_agents_default "$AGENTS_USER_DIR/agents.yaml")
527
555
  printf '%s' "$v"
528
556
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Device placement for `--device auto` / `--host auto`.
2
+ * Device placement for `--device auto`.
3
3
  *
4
4
  * Explicit auto placement probes live fleet health and harness readiness.
5
5
  * Historical affinity remains for generic host resolution callers.
@@ -60,7 +60,7 @@ export interface DeviceAffinityOptions {
60
60
  project?: string;
61
61
  }
62
62
  export interface DeviceAffinityPlan {
63
- /** null means run locally (do not pass --host). */
63
+ /** null means run locally (do not pass --device). */
64
64
  host: string | null;
65
65
  deviceCandidates: WeightedCandidate[];
66
66
  pickedDeviceKey: string | null;
@@ -100,7 +100,7 @@ export declare function resolveDeviceAuto(agent?: string, opts?: {
100
100
  * Resolve host for `--device auto`. Does NOT pick harness or accounts.
101
101
  *
102
102
  * Draws from the same automatic-placement pool as {@link resolveDeviceAuto}, so
103
- * `agents ssh auto`, the generic `--host auto` passthrough, and `matchHost`'s
103
+ * `agents ssh auto`, the generic `--device auto` passthrough, and `matchHost`'s
104
104
  * `auto` sentinel honour device roles too. Throws when roles leave the pool
105
105
  * empty — a `null` host here means "run locally", which for a box marked
106
106
  * `personal` is the outcome the mark exists to prevent.
@@ -131,7 +131,7 @@ export type DeviceAutoApplyResult = {
131
131
  };
132
132
  };
133
133
  /**
134
- * Apply `--device auto` / `--host auto` (and deprecated `--smart`) onto run options.
134
+ * Apply `--device auto` (and deprecated `--smart`) onto run options.
135
135
  * Mutates `options` in place. Placement failures propagate without rewriting
136
136
  * `auto`, so callers fail loud instead of silently launching locally.
137
137
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Device placement for `--device auto` / `--host auto`.
2
+ * Device placement for `--device auto`.
3
3
  *
4
4
  * Explicit auto placement probes live fleet health and harness readiness.
5
5
  * Historical affinity remains for generic host resolution callers.
@@ -111,7 +111,9 @@ export function formatNoHealthyDeviceError(pool, signals, agent) {
111
111
  const target = agent ? `can run ${agent}` : "for 'run auto'";
112
112
  // Name the role narrowing when there is one: a fleet where every box but two
113
113
  // is filtered out by a worker mark reads as "the fleet is down" without it.
114
- const marked = describeAutoPool();
114
+ // Roster on `pool` so a fleet-wide role default reaches a doc-less device
115
+ // in this error's own candidate set, not just the ones with a doc.
116
+ const marked = describeAutoPool({ roster: pool });
115
117
  const poolNote = marked ? ` [pool: ${marked}]` : '';
116
118
  return `agents: no healthy device ${target}${poolNote} — excluded: ${excluded}; earliest window resets unknown`;
117
119
  }
@@ -177,7 +179,7 @@ export async function resolveDeviceAuto(agent, opts = {}) {
177
179
  * Resolve host for `--device auto`. Does NOT pick harness or accounts.
178
180
  *
179
181
  * Draws from the same automatic-placement pool as {@link resolveDeviceAuto}, so
180
- * `agents ssh auto`, the generic `--host auto` passthrough, and `matchHost`'s
182
+ * `agents ssh auto`, the generic `--device auto` passthrough, and `matchHost`'s
181
183
  * `auto` sentinel honour device roles too. Throws when roles leave the pool
182
184
  * empty — a `null` host here means "run locally", which for a box marked
183
185
  * `personal` is the outcome the mark exists to prevent.
@@ -242,7 +244,7 @@ export function isDeviceAuto(value) {
242
244
  }
243
245
  const HOST_SLOTS = ['host', 'device', 'on', 'computer'];
244
246
  /**
245
- * Apply `--device auto` / `--host auto` (and deprecated `--smart`) onto run options.
247
+ * Apply `--device auto` (and deprecated `--smart`) onto run options.
246
248
  * Mutates `options` in place. Placement failures propagate without rewriting
247
249
  * `auto`, so callers fail loud instead of silently launching locally.
248
250
  */