@vibesdotdev/plugins 0.0.0-stage → 1.0.0-rc.0

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 (330) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +54 -2
  3. package/VIBES.md +165 -0
  4. package/dist/ai/index.d.ts +4 -0
  5. package/dist/ai/index.d.ts.map +1 -0
  6. package/dist/ai/index.js +3 -0
  7. package/dist/ai/index.js.map +1 -0
  8. package/dist/ai/plugin.impl.cloud.d.ts +4 -0
  9. package/dist/ai/plugin.impl.cloud.d.ts.map +1 -0
  10. package/dist/ai/plugin.impl.cloud.js +32 -0
  11. package/dist/ai/plugin.impl.cloud.js.map +1 -0
  12. package/dist/ai/plugin.impl.consumer.d.ts +7 -0
  13. package/dist/ai/plugin.impl.consumer.d.ts.map +1 -0
  14. package/dist/ai/plugin.impl.consumer.js +222 -0
  15. package/dist/ai/plugin.impl.consumer.js.map +1 -0
  16. package/dist/auth/consumer.plugin.d.ts +3 -0
  17. package/dist/auth/consumer.plugin.d.ts.map +1 -0
  18. package/dist/auth/consumer.plugin.js +105 -0
  19. package/dist/auth/consumer.plugin.js.map +1 -0
  20. package/dist/auth/index.d.ts +2 -0
  21. package/dist/auth/index.d.ts.map +1 -0
  22. package/dist/auth/index.js +2 -0
  23. package/dist/auth/index.js.map +1 -0
  24. package/dist/auth/plugin.d.ts +4 -0
  25. package/dist/auth/plugin.d.ts.map +1 -0
  26. package/dist/auth/plugin.js +33 -0
  27. package/dist/auth/plugin.js.map +1 -0
  28. package/dist/automation/consumer.plugin.d.ts +3 -0
  29. package/dist/automation/consumer.plugin.d.ts.map +1 -0
  30. package/dist/automation/consumer.plugin.js +92 -0
  31. package/dist/automation/consumer.plugin.js.map +1 -0
  32. package/dist/automation/index.d.ts +2 -0
  33. package/dist/automation/index.d.ts.map +1 -0
  34. package/dist/automation/index.js +2 -0
  35. package/dist/automation/index.js.map +1 -0
  36. package/dist/automation/plugin.d.ts +4 -0
  37. package/dist/automation/plugin.d.ts.map +1 -0
  38. package/dist/automation/plugin.js +30 -0
  39. package/dist/automation/plugin.js.map +1 -0
  40. package/dist/bun/host.d.ts +44 -0
  41. package/dist/bun/host.d.ts.map +1 -0
  42. package/dist/bun/host.js +71 -0
  43. package/dist/bun/host.js.map +1 -0
  44. package/dist/cli/child-failure.d.ts +37 -0
  45. package/dist/cli/child-failure.d.ts.map +1 -0
  46. package/dist/cli/child-failure.js +86 -0
  47. package/dist/cli/child-failure.js.map +1 -0
  48. package/dist/cli/context.d.ts +12 -0
  49. package/dist/cli/context.d.ts.map +1 -0
  50. package/dist/cli/context.js +11 -0
  51. package/dist/cli/context.js.map +1 -0
  52. package/dist/cli/execution.d.ts +10 -0
  53. package/dist/cli/execution.d.ts.map +1 -0
  54. package/dist/cli/execution.js +20 -0
  55. package/dist/cli/execution.js.map +1 -0
  56. package/dist/cli/failure-facts.d.ts +16 -0
  57. package/dist/cli/failure-facts.d.ts.map +1 -0
  58. package/dist/cli/failure-facts.js +39 -0
  59. package/dist/cli/failure-facts.js.map +1 -0
  60. package/dist/cli/package-manager.consumer.d.ts +106 -0
  61. package/dist/cli/package-manager.consumer.d.ts.map +1 -0
  62. package/dist/cli/package-manager.consumer.js +258 -0
  63. package/dist/cli/package-manager.consumer.js.map +1 -0
  64. package/dist/cli/plugin.d.ts +9 -0
  65. package/dist/cli/plugin.d.ts.map +1 -0
  66. package/dist/cli/plugin.js +34 -0
  67. package/dist/cli/plugin.js.map +1 -0
  68. package/dist/cli/plugins.add.cli-command.descriptor.d.ts +36 -0
  69. package/dist/cli/plugins.add.cli-command.descriptor.d.ts.map +1 -0
  70. package/dist/cli/plugins.add.cli-command.descriptor.js +31 -0
  71. package/dist/cli/plugins.add.cli-command.descriptor.js.map +1 -0
  72. package/dist/cli/plugins.add.cli-command.impl.consumer.d.ts +63 -0
  73. package/dist/cli/plugins.add.cli-command.impl.consumer.d.ts.map +1 -0
  74. package/dist/cli/plugins.add.cli-command.impl.consumer.js +206 -0
  75. package/dist/cli/plugins.add.cli-command.impl.consumer.js.map +1 -0
  76. package/dist/cli/plugins.cli-group.descriptor.d.ts +4 -0
  77. package/dist/cli/plugins.cli-group.descriptor.d.ts.map +1 -0
  78. package/dist/cli/plugins.cli-group.descriptor.js +10 -0
  79. package/dist/cli/plugins.cli-group.descriptor.js.map +1 -0
  80. package/dist/cli/plugins.host.cli-command.descriptor.d.ts +31 -0
  81. package/dist/cli/plugins.host.cli-command.descriptor.d.ts.map +1 -0
  82. package/dist/cli/plugins.host.cli-command.descriptor.js +37 -0
  83. package/dist/cli/plugins.host.cli-command.descriptor.js.map +1 -0
  84. package/dist/cli/plugins.host.cli-command.impl.consumer.d.ts +29 -0
  85. package/dist/cli/plugins.host.cli-command.impl.consumer.d.ts.map +1 -0
  86. package/dist/cli/plugins.host.cli-command.impl.consumer.js +109 -0
  87. package/dist/cli/plugins.host.cli-command.impl.consumer.js.map +1 -0
  88. package/dist/cli/plugins.link.cli-command.descriptor.d.ts +35 -0
  89. package/dist/cli/plugins.link.cli-command.descriptor.d.ts.map +1 -0
  90. package/dist/cli/plugins.link.cli-command.descriptor.js +32 -0
  91. package/dist/cli/plugins.link.cli-command.descriptor.js.map +1 -0
  92. package/dist/cli/plugins.link.cli-command.impl.consumer.d.ts +31 -0
  93. package/dist/cli/plugins.link.cli-command.impl.consumer.d.ts.map +1 -0
  94. package/dist/cli/plugins.link.cli-command.impl.consumer.js +104 -0
  95. package/dist/cli/plugins.link.cli-command.impl.consumer.js.map +1 -0
  96. package/dist/cli/plugins.list.cli-command.descriptor.d.ts +26 -0
  97. package/dist/cli/plugins.list.cli-command.descriptor.d.ts.map +1 -0
  98. package/dist/cli/plugins.list.cli-command.descriptor.js +22 -0
  99. package/dist/cli/plugins.list.cli-command.descriptor.js.map +1 -0
  100. package/dist/cli/plugins.list.cli-command.impl.consumer.d.ts +116 -0
  101. package/dist/cli/plugins.list.cli-command.impl.consumer.d.ts.map +1 -0
  102. package/dist/cli/plugins.list.cli-command.impl.consumer.js +343 -0
  103. package/dist/cli/plugins.list.cli-command.impl.consumer.js.map +1 -0
  104. package/dist/cli/plugins.remove.cli-command.descriptor.d.ts +31 -0
  105. package/dist/cli/plugins.remove.cli-command.descriptor.d.ts.map +1 -0
  106. package/dist/cli/plugins.remove.cli-command.descriptor.js +26 -0
  107. package/dist/cli/plugins.remove.cli-command.descriptor.js.map +1 -0
  108. package/dist/cli/plugins.remove.cli-command.impl.consumer.d.ts +30 -0
  109. package/dist/cli/plugins.remove.cli-command.impl.consumer.d.ts.map +1 -0
  110. package/dist/cli/plugins.remove.cli-command.impl.consumer.js +80 -0
  111. package/dist/cli/plugins.remove.cli-command.impl.consumer.js.map +1 -0
  112. package/dist/cli/plugins.unlink.cli-command.descriptor.d.ts +31 -0
  113. package/dist/cli/plugins.unlink.cli-command.descriptor.d.ts.map +1 -0
  114. package/dist/cli/plugins.unlink.cli-command.descriptor.js +21 -0
  115. package/dist/cli/plugins.unlink.cli-command.descriptor.js.map +1 -0
  116. package/dist/cli/plugins.unlink.cli-command.impl.consumer.d.ts +76 -0
  117. package/dist/cli/plugins.unlink.cli-command.impl.consumer.d.ts.map +1 -0
  118. package/dist/cli/plugins.unlink.cli-command.impl.consumer.js +190 -0
  119. package/dist/cli/plugins.unlink.cli-command.impl.consumer.js.map +1 -0
  120. package/dist/cli/registry-sign-in.consumer.d.ts +80 -0
  121. package/dist/cli/registry-sign-in.consumer.d.ts.map +1 -0
  122. package/dist/cli/registry-sign-in.consumer.js +199 -0
  123. package/dist/cli/registry-sign-in.consumer.js.map +1 -0
  124. package/dist/cli/roll.d.ts +230 -0
  125. package/dist/cli/roll.d.ts.map +1 -0
  126. package/dist/cli/roll.js +569 -0
  127. package/dist/cli/roll.js.map +1 -0
  128. package/dist/cli/schemas/context.d.ts +10 -0
  129. package/dist/cli/schemas/context.d.ts.map +1 -0
  130. package/dist/cli/schemas/context.js +3 -0
  131. package/dist/cli/schemas/context.js.map +1 -0
  132. package/dist/cli/schemas/scopes.d.ts +37 -0
  133. package/dist/cli/schemas/scopes.d.ts.map +1 -0
  134. package/dist/cli/schemas/scopes.js +11 -0
  135. package/dist/cli/schemas/scopes.js.map +1 -0
  136. package/dist/cli/schemas/unlink.d.ts +18 -0
  137. package/dist/cli/schemas/unlink.d.ts.map +1 -0
  138. package/dist/cli/schemas/unlink.js +3 -0
  139. package/dist/cli/schemas/unlink.js.map +1 -0
  140. package/dist/cli/schemas/update.d.ts +77 -0
  141. package/dist/cli/schemas/update.d.ts.map +1 -0
  142. package/dist/cli/schemas/update.js +3 -0
  143. package/dist/cli/schemas/update.js.map +1 -0
  144. package/dist/cli/scopes.d.ts +50 -0
  145. package/dist/cli/scopes.d.ts.map +1 -0
  146. package/dist/cli/scopes.js +90 -0
  147. package/dist/cli/scopes.js.map +1 -0
  148. package/dist/cli/seed.d.ts +232 -0
  149. package/dist/cli/seed.d.ts.map +1 -0
  150. package/dist/cli/seed.js +590 -0
  151. package/dist/cli/seed.js.map +1 -0
  152. package/dist/cli/self-update.d.ts +120 -0
  153. package/dist/cli/self-update.d.ts.map +1 -0
  154. package/dist/cli/self-update.js +274 -0
  155. package/dist/cli/self-update.js.map +1 -0
  156. package/dist/cli/update.cli-command.descriptor.d.ts +62 -0
  157. package/dist/cli/update.cli-command.descriptor.d.ts.map +1 -0
  158. package/dist/cli/update.cli-command.descriptor.js +49 -0
  159. package/dist/cli/update.cli-command.descriptor.js.map +1 -0
  160. package/dist/cli/update.cli-command.impl.consumer.d.ts +178 -0
  161. package/dist/cli/update.cli-command.impl.consumer.d.ts.map +1 -0
  162. package/dist/cli/update.cli-command.impl.consumer.js +750 -0
  163. package/dist/cli/update.cli-command.impl.consumer.js.map +1 -0
  164. package/dist/comms/consumer.plugin.d.ts +3 -0
  165. package/dist/comms/consumer.plugin.d.ts.map +1 -0
  166. package/dist/comms/consumer.plugin.js +108 -0
  167. package/dist/comms/consumer.plugin.js.map +1 -0
  168. package/dist/comms/index.d.ts +2 -0
  169. package/dist/comms/index.d.ts.map +1 -0
  170. package/dist/comms/index.js +2 -0
  171. package/dist/comms/index.js.map +1 -0
  172. package/dist/comms/plugin.d.ts +4 -0
  173. package/dist/comms/plugin.d.ts.map +1 -0
  174. package/dist/comms/plugin.js +30 -0
  175. package/dist/comms/plugin.js.map +1 -0
  176. package/dist/consumer.d.ts +3 -0
  177. package/dist/consumer.d.ts.map +1 -0
  178. package/dist/consumer.js +3 -0
  179. package/dist/consumer.js.map +1 -0
  180. package/dist/consumer.plugin.d.ts +63 -0
  181. package/dist/consumer.plugin.d.ts.map +1 -0
  182. package/dist/consumer.plugin.js +89 -0
  183. package/dist/consumer.plugin.js.map +1 -0
  184. package/dist/core/consumer.plugin.d.ts +4 -0
  185. package/dist/core/consumer.plugin.d.ts.map +1 -0
  186. package/dist/core/consumer.plugin.js +358 -0
  187. package/dist/core/consumer.plugin.js.map +1 -0
  188. package/dist/core/index.d.ts +2 -0
  189. package/dist/core/index.d.ts.map +1 -0
  190. package/dist/core/index.js +2 -0
  191. package/dist/core/index.js.map +1 -0
  192. package/dist/core/optional-member.d.ts +11 -0
  193. package/dist/core/optional-member.d.ts.map +1 -0
  194. package/dist/core/optional-member.js +68 -0
  195. package/dist/core/optional-member.js.map +1 -0
  196. package/dist/core/optional-plugin.d.ts +40 -0
  197. package/dist/core/optional-plugin.d.ts.map +1 -0
  198. package/dist/core/optional-plugin.js +107 -0
  199. package/dist/core/optional-plugin.js.map +1 -0
  200. package/dist/core/plugin.d.ts +10 -0
  201. package/dist/core/plugin.d.ts.map +1 -0
  202. package/dist/core/plugin.js +64 -0
  203. package/dist/core/plugin.js.map +1 -0
  204. package/dist/core/setup-requirements.descriptors.d.ts +7 -0
  205. package/dist/core/setup-requirements.descriptors.d.ts.map +1 -0
  206. package/dist/core/setup-requirements.descriptors.js +117 -0
  207. package/dist/core/setup-requirements.descriptors.js.map +1 -0
  208. package/dist/data/consumer.plugin.d.ts +3 -0
  209. package/dist/data/consumer.plugin.d.ts.map +1 -0
  210. package/dist/data/consumer.plugin.js +99 -0
  211. package/dist/data/consumer.plugin.js.map +1 -0
  212. package/dist/data/index.d.ts +2 -0
  213. package/dist/data/index.d.ts.map +1 -0
  214. package/dist/data/index.js +2 -0
  215. package/dist/data/index.js.map +1 -0
  216. package/dist/data/plugin.d.ts +4 -0
  217. package/dist/data/plugin.d.ts.map +1 -0
  218. package/dist/data/plugin.js +37 -0
  219. package/dist/data/plugin.js.map +1 -0
  220. package/dist/devtools/consumer.plugin.d.ts +3 -0
  221. package/dist/devtools/consumer.plugin.d.ts.map +1 -0
  222. package/dist/devtools/consumer.plugin.js +323 -0
  223. package/dist/devtools/consumer.plugin.js.map +1 -0
  224. package/dist/devtools/index.d.ts +2 -0
  225. package/dist/devtools/index.d.ts.map +1 -0
  226. package/dist/devtools/index.js +2 -0
  227. package/dist/devtools/index.js.map +1 -0
  228. package/dist/devtools/plugin.d.ts +4 -0
  229. package/dist/devtools/plugin.d.ts.map +1 -0
  230. package/dist/devtools/plugin.js +34 -0
  231. package/dist/devtools/plugin.js.map +1 -0
  232. package/dist/filesystem/index.d.ts +2 -0
  233. package/dist/filesystem/index.d.ts.map +1 -0
  234. package/dist/filesystem/index.js +2 -0
  235. package/dist/filesystem/index.js.map +1 -0
  236. package/dist/filesystem/plugin.d.ts +4 -0
  237. package/dist/filesystem/plugin.d.ts.map +1 -0
  238. package/dist/filesystem/plugin.js +21 -0
  239. package/dist/filesystem/plugin.js.map +1 -0
  240. package/dist/index.d.ts +3 -0
  241. package/dist/index.d.ts.map +1 -0
  242. package/dist/index.js +4 -0
  243. package/dist/index.js.map +1 -0
  244. package/dist/plugin.d.ts +7 -0
  245. package/dist/plugin.d.ts.map +1 -0
  246. package/dist/plugin.js +16 -0
  247. package/dist/plugin.js.map +1 -0
  248. package/dist/schemas/ai.d.ts +119 -0
  249. package/dist/schemas/ai.d.ts.map +1 -0
  250. package/dist/schemas/ai.js +84 -0
  251. package/dist/schemas/ai.js.map +1 -0
  252. package/dist/schemas/auth.d.ts +15 -0
  253. package/dist/schemas/auth.d.ts.map +1 -0
  254. package/dist/schemas/auth.js +19 -0
  255. package/dist/schemas/auth.js.map +1 -0
  256. package/dist/schemas/automation.d.ts +66 -0
  257. package/dist/schemas/automation.d.ts.map +1 -0
  258. package/dist/schemas/automation.js +36 -0
  259. package/dist/schemas/automation.js.map +1 -0
  260. package/dist/schemas/comms.d.ts +13 -0
  261. package/dist/schemas/comms.d.ts.map +1 -0
  262. package/dist/schemas/comms.js +27 -0
  263. package/dist/schemas/comms.js.map +1 -0
  264. package/dist/schemas/core.d.ts +102 -0
  265. package/dist/schemas/core.d.ts.map +1 -0
  266. package/dist/schemas/core.js +124 -0
  267. package/dist/schemas/core.js.map +1 -0
  268. package/dist/schemas/data.d.ts +13 -0
  269. package/dist/schemas/data.d.ts.map +1 -0
  270. package/dist/schemas/data.js +25 -0
  271. package/dist/schemas/data.js.map +1 -0
  272. package/dist/schemas/devtools.d.ts +33 -0
  273. package/dist/schemas/devtools.d.ts.map +1 -0
  274. package/dist/schemas/devtools.js +56 -0
  275. package/dist/schemas/devtools.js.map +1 -0
  276. package/dist/schemas/family.d.ts +11 -0
  277. package/dist/schemas/family.d.ts.map +1 -0
  278. package/dist/schemas/family.js +21 -0
  279. package/dist/schemas/family.js.map +1 -0
  280. package/dist/schemas/filesystem.d.ts +25 -0
  281. package/dist/schemas/filesystem.d.ts.map +1 -0
  282. package/dist/schemas/filesystem.js +24 -0
  283. package/dist/schemas/filesystem.js.map +1 -0
  284. package/dist/schemas/host.d.ts +10 -0
  285. package/dist/schemas/host.d.ts.map +1 -0
  286. package/dist/schemas/host.js +2 -0
  287. package/dist/schemas/host.js.map +1 -0
  288. package/dist/schemas/optional-member.d.ts +67 -0
  289. package/dist/schemas/optional-member.d.ts.map +1 -0
  290. package/dist/schemas/optional-member.js +31 -0
  291. package/dist/schemas/optional-member.js.map +1 -0
  292. package/dist/schemas/roll.d.ts +47 -0
  293. package/dist/schemas/roll.d.ts.map +1 -0
  294. package/dist/schemas/roll.js +18 -0
  295. package/dist/schemas/roll.js.map +1 -0
  296. package/dist/schemas/tui.d.ts +33 -0
  297. package/dist/schemas/tui.d.ts.map +1 -0
  298. package/dist/schemas/tui.js +95 -0
  299. package/dist/schemas/tui.js.map +1 -0
  300. package/dist/schemas/ui.d.ts +15 -0
  301. package/dist/schemas/ui.d.ts.map +1 -0
  302. package/dist/schemas/ui.js +45 -0
  303. package/dist/schemas/ui.js.map +1 -0
  304. package/dist/tui/code-sessions-tui-plugin-shim.d.ts +7 -0
  305. package/dist/tui/code-sessions-tui-plugin-shim.d.ts.map +1 -0
  306. package/dist/tui/code-sessions-tui-plugin-shim.js +2 -0
  307. package/dist/tui/code-sessions-tui-plugin-shim.js.map +1 -0
  308. package/dist/tui/index.d.ts +8 -0
  309. package/dist/tui/index.d.ts.map +1 -0
  310. package/dist/tui/index.js +8 -0
  311. package/dist/tui/index.js.map +1 -0
  312. package/dist/tui/plugin.d.ts +53 -0
  313. package/dist/tui/plugin.d.ts.map +1 -0
  314. package/dist/tui/plugin.js +350 -0
  315. package/dist/tui/plugin.js.map +1 -0
  316. package/dist/ui/consumer.plugin.d.ts +3 -0
  317. package/dist/ui/consumer.plugin.d.ts.map +1 -0
  318. package/dist/ui/consumer.plugin.js +75 -0
  319. package/dist/ui/consumer.plugin.js.map +1 -0
  320. package/dist/ui/index.d.ts +2 -0
  321. package/dist/ui/index.d.ts.map +1 -0
  322. package/dist/ui/index.js +2 -0
  323. package/dist/ui/index.js.map +1 -0
  324. package/dist/ui/plugin.d.ts +4 -0
  325. package/dist/ui/plugin.d.ts.map +1 -0
  326. package/dist/ui/plugin.js +35 -0
  327. package/dist/ui/plugin.js.map +1 -0
  328. package/host-start.mjs +39 -0
  329. package/host.mjs +24 -0
  330. package/package.json +437 -4
@@ -0,0 +1,750 @@
1
+ import { resolveVibesAppOriginForEnvironment } from '@vibesdotdev/config/environment/resolve-app-origin';
2
+ import { resolveFileSystemAdapter } from '@vibesdotdev/filesystem/client/resolve-adapter';
3
+ import { getPathRoots } from '@vibesdotdev/paths';
4
+ import { LoggedErrorSchema } from '@vibesdotdev/logging';
5
+ import { CapabilityError, CaughtFailureSchema, composeChildEnv, getRuntimeHostIdentity, getRuntimePackagePlugins, isCapabilityError } from '@vibesdotdev/kernel';
6
+ import { readWorkspacePluginSurfaces } from '@vibesdotdev/kernel/bootstrap';
7
+ import { redactedExcerpt } from '@vibesdotdev/shared/utils';
8
+ import { z } from 'zod/v4';
9
+ import { unreadableSelectionsRefusal } from "./plugins.unlink.cli-command.impl.consumer.js";
10
+ import { summarizeChildFailure } from "./child-failure.js";
11
+ import { PluginsCLICommand } from "./context.js";
12
+ import { resolveRuntimeExecution } from "./execution.js";
13
+ import { planBun, readScopeDeclarations, runBunOrThrow } from "./package-manager.consumer.js";
14
+ import { bodiesRunningReplacedExecutable, censusBodies, markReplacedExecutables, rollBodies } from "./roll.js";
15
+ import { resolveScopes } from "./scopes.js";
16
+ import { formatRepairReceipt, repairGlobalScope, seedGlobalScope, GLOBAL_MANIFEST_NAME, HOST_PACKAGE_NAME } from "./seed.js";
17
+ import { runSelfUpdate, selfInstallTarget } from "./self-update.js";
18
+ /**
19
+ * Prefix the release artifacts are published under, on the `assets` origin.
20
+ *
21
+ * MEASURED CORRECTION (WU-12, 2026-08-31): this used to read
22
+ * `https://vibes.dev/downloads/cli/latest.json`, which is not where anything
23
+ * is published. `vibes infra release cli` uploads to R2 bucket
24
+ * `vibes-public-downloads` under key prefix `downloads/cli/`, served from
25
+ * `https://assets.vibes.dev/`, and `home-web`'s `scripts/install.sh` has
26
+ * always downloaded from `${VIBES_MIRROR:-https://assets.vibes.dev/downloads/cli}`.
27
+ * `--self` pointing at a different origin than the installer is a split every
28
+ * mirror user would hit, so the ONE base is shared. The installer's
29
+ * `VIBES_MIRROR` is a shell variable; here the mirror is the command's own
30
+ * declared `--mirror <url>` option, never an environment read.
31
+ */
32
+ const RELEASE_DOWNLOADS_PATH = 'downloads/cli';
33
+ /** The deadline on the shipping-path verification boot. */
34
+ const VERIFICATION_TIMEOUT_MS = 120_000;
35
+ /** Whether a scope declares anything bun could install: a dependency or a `workspaces` glob. A scope with no manifest declares nothing. */
36
+ function declaresAnything(declared) {
37
+ return declared !== null && (declared.dependencies.length > 0 || declared.workspaces.length > 0);
38
+ }
39
+ /**
40
+ * The published release base: the `assets` origin the production
41
+ * `config/environment` declares, under {@link RELEASE_DOWNLOADS_PATH}.
42
+ * Releases are published only to production, whatever environment this host
43
+ * calls (the desktop release reads its origin by the same rule), so this
44
+ * reads the production declaration rather than the active one; `--mirror`
45
+ * retargets. A runtime that declares no production `assets` origin refuses
46
+ * `release-origin-undeclared`.
47
+ */
48
+ export async function publishedReleaseBase(runtime) {
49
+ const origin = await resolveVibesAppOriginForEnvironment('assets', 'production', runtime);
50
+ if (origin === null) {
51
+ throw new CapabilityError('No production assets origin is declared in this runtime.', 'NOT_FOUND', {
52
+ reason: 'release-origin-undeclared',
53
+ retryable: false,
54
+ recoveryHint: 'Pass --mirror <url> naming the release base that holds latest.json.'
55
+ });
56
+ }
57
+ return `${origin}/${RELEASE_DOWNLOADS_PATH}`;
58
+ }
59
+ /** `latest.json` under a release base (the declared `--mirror`, else the published base), trailing slashes trimmed. */
60
+ export function latestManifestUrl(base) {
61
+ return `${base.replace(/\/+$/, '')}/latest.json`;
62
+ }
63
+ /**
64
+ * Which scope reports make `vibes update` FAIL.
65
+ *
66
+ * A command that reports `NOT VERIFIED` and exits 0 is the absence-looks-like-
67
+ * success defect: the shipped draft did exactly that, so a scope whose
68
+ * verification boot could not compose, and a `$VIBES_HOME` the command refused
69
+ * to touch at all, both read as "updated" to CI, to a script, and to the WU-07
70
+ * harness. Pure and exported so the rule is testable without booting a child.
71
+ *
72
+ * Not a failure: a benign skip (`no workspace plugin list to update`, a home
73
+ * delegated to a host workspace) — nothing was asked for and nothing broke.
74
+ *
75
+ * `--dry-run` suppresses only the VERIFICATION half: nothing was installed, so
76
+ * nothing was booted, so an unverified scope is not a defect. A REFUSAL is not
77
+ * suppressed — a foreign or fossil `$VIBES_HOME`, or a link glob whose parent
78
+ * is gone, is a fact about the scope as it stands, not about the install that
79
+ * did not run, and `vibes update --dry-run` in CI has to be able to see it.
80
+ */
81
+ /** A surface omission is verified because the kernel refused to import the declared export. */
82
+ export function isUnverifiedPluginRow(row) {
83
+ if (row.status === 'loaded' || row.status === 'deduped')
84
+ return false;
85
+ if (row.status === 'not-composed' && row.reason === 'surface-omitted')
86
+ return false;
87
+ return true;
88
+ }
89
+ export function collectUpdateFailures(reports, options) {
90
+ const failures = [];
91
+ for (const report of reports) {
92
+ // Deliberately BEFORE the dry-run short-circuit: a refusal happened
93
+ // whether or not anything was going to be installed.
94
+ if (report.failed === true) {
95
+ failures.push({
96
+ scope: report.scope,
97
+ root: report.root,
98
+ reason: report.skipped ?? 'refused'
99
+ });
100
+ continue;
101
+ }
102
+ if (report.skipped !== undefined)
103
+ continue;
104
+ // Nothing was installed, so nothing was booted: there is no verification
105
+ // to have failed.
106
+ if (options.dryRun)
107
+ continue;
108
+ if (!report.verified) {
109
+ failures.push({
110
+ scope: report.scope,
111
+ root: report.root,
112
+ reason: `NOT VERIFIED: ${report.verifyDetail ?? 'unknown'}`
113
+ });
114
+ }
115
+ }
116
+ return failures;
117
+ }
118
+ /** The first 12 hex digits of `bun.lock`'s sha256 under `root`, or `null` when the scope has no lock. */
119
+ async function lockDigest(runtime, root) {
120
+ const filesystem = await resolveFileSystemAdapter(runtime, { binding: false });
121
+ const path = filesystem.join(root, 'bun.lock');
122
+ if (!(await filesystem.exists(path)))
123
+ return null;
124
+ const read = await filesystem.readFile(path);
125
+ const bytes = read instanceof Uint8Array ? read.slice() : new TextEncoder().encode(read);
126
+ const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));
127
+ return Array.from(digest, (byte) => byte.toString(16).padStart(2, '0')).join('').slice(0, 12);
128
+ }
129
+ /**
130
+ * The command's inputs, each read on its own: a missing flag is off, a missing
131
+ * or non-text mirror is empty. commander maps `--no-roll` to `roll: false`, so
132
+ * only an explicit `false` turns the roll off.
133
+ */
134
+ const UpdateInputSchema = z.object({
135
+ dryRun: z.boolean().catch(false),
136
+ roll: z.boolean().catch(true),
137
+ latest: z.boolean().catch(false),
138
+ repair: z.boolean().catch(false),
139
+ self: z.boolean().catch(false),
140
+ allowDowngrade: z.boolean().catch(false),
141
+ mirror: z.string().trim().catch('')
142
+ });
143
+ /** The schema of the verification boot's `plugins list --json` envelope, each field on its own. */
144
+ const VerificationEnvelopeSchema = z.object({
145
+ plugins: z.array(z.object({ plugin: z.string(), status: z.string(), reason: z.literal('surface-omitted').optional() })).optional().catch(undefined),
146
+ resolutions: z
147
+ .array(z.object({ specifier: z.string(), linked: z.boolean().nullable(), realpath: z.string().nullable() }))
148
+ .optional()
149
+ .catch(undefined)
150
+ });
151
+ /** A failure as one bounded, credential-free line for a report. */
152
+ function reportLine(failure) {
153
+ return redactedExcerpt(LoggedErrorSchema.parse(failure).message) || 'unknown failure';
154
+ }
155
+ /**
156
+ * `vibes update` — seed, install, verify, roll (plan §5).
157
+ *
158
+ * ### Ordering: self FIRST, then the graph, then ONE roll
159
+ *
160
+ * `--self` runs before any scope work and the census roll runs exactly once at
161
+ * the end. Rolling before the binary is replaced re-stales every body just
162
+ * rolled — the executable freshness nerve fires on the new inode — so the
163
+ * draft's "roll, then swap" order produced two rolls and a `pm status` Build
164
+ * column reading STALE immediately after a successful update.
165
+ *
166
+ * ### What triggers the roll: THREE reasons, one nerve each
167
+ *
168
+ * 1. the module graph moved (a scope's `bun.lock` digest changed);
169
+ * 2. `--self` installed a new release over this executable;
170
+ * 3. a body in the census is **already** executing a replaced file.
171
+ *
172
+ * The third one is not redundant. A local `bun run build:install` swaps
173
+ * `~/.local/bin/vibes` by atomic rename: no lock moves and `--self` downloaded
174
+ * nothing, so on reasons 1–2 alone `vibes update` reported "nothing changed, so
175
+ * no body was rolled" in the same minute `vibes pm status` reported "⚠ PM
176
+ * daemon (pid …) runs a replaced vibes executable". Two surfaces, one fact,
177
+ * and the surface that could FIX it held the weaker predicate. The census now
178
+ * consumes the same executable nerve `pm status` and PM's
179
+ * `ExecutableFreshnessReconciler` consume (`markReplacedExecutables`), so a
180
+ * body running a replaced executable is rolled and the receipt says why.
181
+ *
182
+ * ### Per scope
183
+ *
184
+ * preflight (schema, glob parents, foreign-owner refusal, `workspace:*`
185
+ * declarations) → `bun install` → a SCOPED `bun update <enabled registry
186
+ * packages> @vibesdotdev/plugins`, never a fleet-wide `bun update` at a
187
+ * 232-member root → verification by spawning the SHIPPING path
188
+ * (`vibes plugins list --json --scope <kind>` with `VIBES_HOST_ROOT` bound to
189
+ * the scope root), where every row must read `loaded`.
190
+ *
191
+ * The verification deliberately re-enters the real executable rather than
192
+ * inspecting the manifest we just wrote. A manifest is a claim; a boot is a
193
+ * measurement, and it is the only thing that proves the scope this command
194
+ * just changed can still compose.
195
+ *
196
+ * ### Exit code
197
+ *
198
+ * A scope that did not verify, and a scope the command REFUSED to touch, both
199
+ * make `vibes update` exit non-zero — after the full report (including the
200
+ * `--json` envelope) has been printed. The draft printed `NOT VERIFIED` and
201
+ * exited 0, so a refused `$VIBES_HOME` and a scope that cannot compose read as
202
+ * success to CI, to a script and to the WU-07 harness. Benign skips (a
203
+ * workspace with no plugin list, a home delegated to a host workspace) are not
204
+ * failures. `--dry-run` never fails a VERIFICATION — it installs nothing, so
205
+ * it boots nothing — but a REFUSED scope still exits non-zero under it: the
206
+ * refusal describes the scope as it stands, not the install that did not run.
207
+ * The rule lives in `collectUpdateFailures`.
208
+ *
209
+ * ### Foreign / fossil manifests
210
+ *
211
+ * A `$VIBES_HOME/package.json` whose name is not `vibes-global` is somebody
212
+ * else's file (or the 24-April fossil). We refuse it by name and print
213
+ * `vibes update --repair --global`; `--repair` then backs it up as
214
+ * `*.fossil-<stamp>` and prints kept|dropped. Nothing is merged silently and
215
+ * nothing is deleted without a backup line.
216
+ */
217
+ export default class UpdateCLICommand extends PluginsCLICommand {
218
+ constructor(descriptor, context) {
219
+ super(descriptor, context);
220
+ }
221
+ async execute(args, options) {
222
+ const flags = { ...args, ...options };
223
+ const { dryRun, roll, latest, repair, ...input } = UpdateInputSchema.parse(flags);
224
+ const paths = getPathRoots(this.runtime);
225
+ const self = input.self ? await this.updateSelf(dryRun, input.mirror, input.allowDowngrade) : null;
226
+ const scopes = await resolveScopes(this.runtime, flags, { fallback: 'both' });
227
+ const scoped = [];
228
+ for (const scope of scopes) {
229
+ scoped.push(await this.updateScope(scope, { dryRun, latest, repair }));
230
+ }
231
+ const reports = scoped.map((outcome) => outcome.report);
232
+ const graphChanged = reports.some((report) => report.changed);
233
+ let outcomes = [];
234
+ let censusNote;
235
+ if (!roll) {
236
+ censusNote = '--no-roll: running bodies keep the previous graph until they restart.';
237
+ }
238
+ else {
239
+ // The census is taken BEFORE the trigger is decided, because one of the
240
+ // three reasons to roll is a fact about the bodies themselves.
241
+ const bodies = await markReplacedExecutables(await censusBodies({
242
+ runtime: this.runtime,
243
+ userHome: paths.userHome,
244
+ vibesHome: paths.vibesHome,
245
+ includePmDaemon: true
246
+ }), this.runtime);
247
+ // THE THIRD REASON. `--self` only reports a release this command
248
+ // downloaded, and the lock digest only reports the module graph; a local
249
+ // `bun run build:install` replaces the executable by atomic rename and
250
+ // moves neither. Gating on those two alone made `vibes update` print
251
+ // "nothing changed, so no body was rolled" while `vibes pm status`
252
+ // printed "runs a replaced vibes executable" about the same daemon.
253
+ // Same nerve, one decision.
254
+ const stale = bodiesRunningReplacedExecutable(bodies);
255
+ if (graphChanged || self?.replaced || stale.length > 0 || dryRun) {
256
+ const notes = [];
257
+ if (stale.length > 0 && !graphChanged && !self?.replaced) {
258
+ notes.push(`the module graph is unchanged, but ${stale.length} running ` +
259
+ `${stale.length === 1 ? 'body executes' : 'bodies execute'} a replaced vibes ` +
260
+ `executable — ${stale.map((body) => `${body.id}: ${body.replacedReason}`).join('; ')}`);
261
+ }
262
+ if (!this.runtime.hasKind('hq/os-service')) {
263
+ notes.push('hq/os-service is not composed into this host, so OS services were enumerated from ' +
264
+ 'the agent directory alone and cannot be restarted from here.');
265
+ }
266
+ if (notes.length)
267
+ censusNote = notes.join(' ');
268
+ outcomes = await rollBodies({ runtime: this.runtime, bodies, dryRun });
269
+ }
270
+ else {
271
+ censusNote =
272
+ 'nothing changed and no running body executes a replaced vibes executable, ' +
273
+ 'so no body was rolled.';
274
+ }
275
+ }
276
+ this.emit({ self, reports, outcomes, censusNote, dryRun });
277
+ // The report is printed FIRST (including the whole `--json` envelope, so
278
+ // a machine caller still gets every row) and only then does the command
279
+ // fail. Silence plus exit 0 is what this replaces.
280
+ const failures = collectUpdateFailures(reports, { dryRun });
281
+ if (failures.length > 0)
282
+ throw updateIncomplete(failures, scoped);
283
+ }
284
+ /** Replace the running executable from the declared `--mirror`, else the published release base. */
285
+ async updateSelf(dryRun, mirror, allowDowngrade) {
286
+ const host = this.runtime.localEnvironment;
287
+ const platform = host.getPlatform?.();
288
+ const arch = host.getArch?.();
289
+ const home = host.accountHome?.();
290
+ if (!platform || !arch || !home) {
291
+ throw new CapabilityError('Host platform, architecture and account home are not captured.', 'UNAVAILABLE', {
292
+ recoveryHint: 'Run through a host that captures platform, architecture and account home (the vibes CLI host).'
293
+ });
294
+ }
295
+ const target = selfInstallTarget(platform, { LOCALAPPDATA: this.runtime.getEnv('LOCALAPPDATA') }, home);
296
+ const manifestUrl = latestManifestUrl(mirror || (await publishedReleaseBase(this.runtime)));
297
+ const result = await runSelfUpdate({
298
+ runtime: this.runtime,
299
+ manifestUrl,
300
+ currentVersion: this.hostVersion(),
301
+ target,
302
+ platform,
303
+ arch,
304
+ dryRun,
305
+ allowDowngrade
306
+ });
307
+ const report = {
308
+ replaced: result.replaced,
309
+ fromVersion: result.fromVersion,
310
+ toVersion: result.toVersion,
311
+ target: result.target.label,
312
+ manifestUrl
313
+ };
314
+ // THE DIGEST IS THE POINT OF A DRY RUN. `runSelfUpdate` already
315
+ // resolved the asset and its sha256; dropping it here left
316
+ // `--self --dry-run` announcing a download whose verification the
317
+ // operator could not check against SHA256SUMS.txt before running it
318
+ // for real. Reported on both surfaces (WU-12).
319
+ if (result.sha256)
320
+ report.sha256 = result.sha256;
321
+ if (result.note)
322
+ report.note = result.note;
323
+ return report;
324
+ }
325
+ /** Version of the running host: the host's own version fact, the one `--version` reports. */
326
+ hostVersion() {
327
+ // The shim's channel version, else the version the executable host
328
+ // declared; the environment captured both once.
329
+ return this.runtime.localEnvironment.hostVersion?.()?.trim() || 'unknown';
330
+ }
331
+ async updateScope(scope, options) {
332
+ const filesystem = await resolveFileSystemAdapter(this.runtime, { binding: false });
333
+ const plugins = await getRuntimePackagePlugins(this.runtime);
334
+ const manifestPath = filesystem.join(scope.root, 'package.json');
335
+ const base = {
336
+ scope: scope.kind,
337
+ root: scope.root,
338
+ manifestPath,
339
+ lockBefore: await lockDigest(this.runtime, scope.root),
340
+ lockAfter: null,
341
+ changed: false,
342
+ packages: [],
343
+ commands: [],
344
+ verified: false,
345
+ plugins: []
346
+ };
347
+ if (scope.kind === 'global') {
348
+ const repairReport = await this.prepareGlobal(plugins, scope.root, options);
349
+ if (repairReport.refusal)
350
+ return { report: { ...base, skipped: repairReport.refusal, failed: true } };
351
+ if (repairReport.lines.length)
352
+ base.repair = repairReport.lines;
353
+ }
354
+ const selected = await this.selectionsFor(scope);
355
+ const selections = selected.plugins;
356
+ if (selected.surface)
357
+ base.surface = selected.surface;
358
+ if (selections.length === 0 && scope.kind === 'workspace') {
359
+ const skipped = selected.surface
360
+ ? `${selected.surface} selects no plugins`
361
+ : `no workspace plugin list to update (looked for vibes.config.{ts,js,json}#plugins, then package.json#vibes.plugins in ${scope.root})`;
362
+ return { report: { ...base, skipped } };
363
+ }
364
+ // Preflight the on-disk rules before running bun: a glob whose parent is
365
+ // gone or a specifier nothing declares makes `bun install` succeed and the
366
+ // next boot fail, which is the worst possible ordering.
367
+ const preflight = this.preflight(plugins, scope);
368
+ if (preflight.issues.length) {
369
+ const skipped = { ...base, skipped: preflight.issues.join('; ') };
370
+ if (!preflight.benign)
371
+ skipped.failed = true;
372
+ return { report: skipped };
373
+ }
374
+ // The scope's install inputs, read once: an unreadable manifest refuses the scope (non-zero exit).
375
+ const declarations = await this.readDeclarations(scope.root);
376
+ if ('refused' in declarations) {
377
+ const refused = declarations.refused;
378
+ return { report: { ...base, skipped: `${refused.message} (${refused.reason})`, failed: true }, failure: refused };
379
+ }
380
+ const declared = declarations.declared;
381
+ // A scope that declares NOTHING — no plugins, no dependencies, no
382
+ // `workspaces` globs — has no install to run, and running one is not
383
+ // harmless: `bun install` on an empty manifest prints "No packages!
384
+ // Deleted empty lockfile" and still MATERIALIZES an empty
385
+ // `node_modules/` (measured, bun 1.4.0). Under `$VIBES_HOME` that
386
+ // directory is the one thing plan §8 item 3 says a delegated home must
387
+ // not have — an empty `node_modules` is where a later stray install
388
+ // silently lands and becomes a second graph. This is the exact shape
389
+ // `vibes update --repair --global` leaves behind on the migration path
390
+ // (plan §9 step 1 drops every fossil dependency and glob before step 2
391
+ // records `hostWorkspace`), so without this skip the documented
392
+ // migration cannot reach the documented end state.
393
+ if (selections.length === 0 && !declaresAnything(declared)) {
394
+ return { report: { ...base, skipped: 'nothing declared — no plugins, dependencies or workspaces globs to install' } };
395
+ }
396
+ const members = plugins.expandWorkspaceMembers(scope.root);
397
+ const registryPackages = [...new Set(selections.map((selection) => plugins.getPackageName(selection)))]
398
+ .filter((name) => !members.has(name))
399
+ .sort();
400
+ // The host package is always kept current; a scope that never names it
401
+ // still resolves its plugins through it.
402
+ const updateTargets = [...new Set([...registryPackages, HOST_PACKAGE_NAME])].filter((name) => declared?.dependencies.includes(name) === true);
403
+ base.packages = updateTargets;
404
+ const install = { action: 'install', cwd: scope.root };
405
+ const update = {
406
+ action: 'update',
407
+ cwd: scope.root,
408
+ args: [...updateTargets, ...(options.latest ? ['--latest'] : [])]
409
+ };
410
+ base.commands = [
411
+ await planBun({ runtime: this.runtime, ...install }),
412
+ ...(updateTargets.length ? [await planBun({ runtime: this.runtime, ...update })] : [])
413
+ ];
414
+ if (options.dryRun)
415
+ return { report: base };
416
+ // REGISTRY AUTH PREFLIGHT — the last thing checked before bun runs.
417
+ //
418
+ // Everything above this line preflights the on-disk shape. This one
419
+ // preflights the CREDENTIAL, because the failure it prevents is not a
420
+ // crash: packages.vibes.dev 404-hides a rejected or unresolved
421
+ // credential rather than returning 401, so `bun install` emits
422
+ // `error: GET https://packages.vibes.dev/@vibesdotdev%2fplugins - 404`
423
+ // and the user reasonably concludes the package was never published.
424
+ // Measured on a real machine 2026-08-31 — days were spent on a
425
+ // publication bug that did not exist. A refusal that NAMES the remedy
426
+ // is strictly better than a bare 404 trace.
427
+ // `runBunOrThrow` owns it (`registry-sign-in.consumer.ts`), so every
428
+ // install path — `update`, `plugins add|link|unlink|remove` — gets it.
429
+ await runBunOrThrow({ runtime: this.runtime, ...install });
430
+ // NEVER a bare `bun update` here: at a 232-member developer root that
431
+ // walks every workspace member and rewrites the whole lock.
432
+ if (updateTargets.length) {
433
+ await runBunOrThrow({ runtime: this.runtime, ...update });
434
+ }
435
+ base.lockAfter = await lockDigest(this.runtime, scope.root);
436
+ base.changed = base.lockAfter !== base.lockBefore;
437
+ const verification = await this.verifyScope(plugins, scope);
438
+ base.verified = verification.ok;
439
+ base.plugins = verification.rows;
440
+ if (!verification.ok)
441
+ base.verifyDetail = verification.detail;
442
+ return { report: base, failure: verification.failure };
443
+ }
444
+ /** Seed an absent home, repair a fossil one, or refuse a foreign one by name. */
445
+ async prepareGlobal(plugins, root, options) {
446
+ const scope = plugins.readGlobalPluginScope(root);
447
+ if (scope.absent) {
448
+ const seeded = await seedGlobalScope(this.runtime, { home: root, dryRun: options.dryRun });
449
+ const lines = [`seeded ${seeded.manifestPath}`];
450
+ if (seeded.wroteBunfig)
451
+ lines.push(`seeded ${seeded.bunfigPath}`);
452
+ if (seeded.migratedBunfig)
453
+ lines.push(`repaired the @vibesdotdev scope in ${seeded.bunfigPath} (backup beside it)`);
454
+ return { lines };
455
+ }
456
+ if (options.repair) {
457
+ const result = await repairGlobalScope(this.runtime, {
458
+ home: root,
459
+ dryRun: options.dryRun,
460
+ // The kernel is dependency-free by contract, so the glob expander
461
+ // is injected here: without it a `workspace:*` dependency whose
462
+ // supplying glob is gone would be KEPT and bun would fail the
463
+ // install again — the shape `vibes plugins unlink` used to leave
464
+ // behind, and the one `--repair` could not repair.
465
+ workspaceMembers: (scopeRoot, globs) => plugins.expandWorkspaceMembers(scopeRoot, [...globs]).keys()
466
+ });
467
+ return { lines: formatRepairReceipt(result) };
468
+ }
469
+ if (!scope.owned) {
470
+ return {
471
+ lines: [],
472
+ refusal: `${scope.manifestPath} is named '${scope.manifestName ?? '(unnamed)'}', not ` +
473
+ `'${GLOBAL_MANIFEST_NAME}'. vibes will not rewrite a manifest it does not own. ` +
474
+ 'Run: vibes update --repair --global (it backs the file up first)'
475
+ };
476
+ }
477
+ return { lines: [] };
478
+ }
479
+ /**
480
+ * The specifiers a scope has ENABLED, read through the SAME resolution the
481
+ * bootstrap loader composes from.
482
+ *
483
+ * The workspace half used to call `resolveWorkspaceListTarget`, the WRITER's
484
+ * target resolver, which reports an empty list for a `vibes.config.ts`
485
+ * because it refuses to rewrite a module. `update --workspace` therefore saw
486
+ * nothing to update for plan §3's PRIMARY workspace surface — the file
487
+ * requirement #1 tells users to write — and skipped the scope with
488
+ * `no workspace plugin list to update`. It now reads Runtime's
489
+ * `readWorkspacePluginSurfaces` (`@vibesdotdev/kernel/bootstrap`, the
490
+ * owner of workspace composition formats) over the handed runtime's
491
+ * selected `runtime/loader`, the same reader the workspace composition and
492
+ * `plugins list` read: `vibes.config.{ts,js,json}#plugins` primary,
493
+ * `package.json#vibes.plugins` fallback.
494
+ *
495
+ * A SHADOWED fallback list is deliberately not included. `bun update` here
496
+ * targets the packages this scope composes; a specifier the effective surface
497
+ * overrides is not composed, and installing it would make `vibes update`
498
+ * disagree with the boot it uses to verify itself.
499
+ */
500
+ async selectionsFor(scope) {
501
+ if (scope.kind === 'global') {
502
+ return {
503
+ plugins: (await getRuntimePackagePlugins(this.runtime)).readGlobalPluginScope(scope.root).plugins,
504
+ surface: 'package.json#vibes.plugins'
505
+ };
506
+ }
507
+ const loader = await this.runtime
508
+ .query('runtime/loader')
509
+ .enabled()
510
+ .resolve();
511
+ const listing = await readWorkspacePluginSurfaces(scope.root, { loader });
512
+ // A declaration that failed to load leaves the list unknown: refuse
513
+ // rather than update the manifest's list or nothing.
514
+ if (listing.failures.length > 0)
515
+ throw unreadableSelectionsRefusal(listing);
516
+ return {
517
+ plugins: listing.effective?.plugins ?? [],
518
+ surface: listing.effective?.surface ?? null
519
+ };
520
+ }
521
+ /**
522
+ * The scope's install inputs. An unreadable or malformed manifest is the
523
+ * typed `plugin-scope-manifest-unreadable` refusal, carried into the report
524
+ * as a refused scope (SPEC: a refused scope exits non-zero) and kept whole
525
+ * as the command's cause; every other failure propagates.
526
+ */
527
+ async readDeclarations(root) {
528
+ try {
529
+ return { declared: await readScopeDeclarations(this.runtime, root) };
530
+ }
531
+ catch (error) {
532
+ const failure = CaughtFailureSchema.parse(error);
533
+ if (isCapabilityError(failure) && failure.reason === 'plugin-scope-manifest-unreadable')
534
+ return { refused: failure };
535
+ throw failure;
536
+ }
537
+ }
538
+ /**
539
+ * On-disk rules that decide whether bun may run at all.
540
+ *
541
+ * `benign` separates "there is nothing here to do" (a delegated home owns
542
+ * no packages — the update belongs to the host workspace) from "the request
543
+ * could not be carried out" (a link glob whose parent directory is gone).
544
+ * Only the second kind fails the command; conflating them would make
545
+ * `vibes update --global` on a delegated machine exit non-zero forever.
546
+ */
547
+ preflight(plugins, scope) {
548
+ if (scope.kind === 'global') {
549
+ const global = plugins.readGlobalPluginScope(scope.root);
550
+ if (global.hostWorkspace) {
551
+ return {
552
+ issues: [
553
+ `delegated to ${global.hostWorkspace} — update that workspace instead ` +
554
+ '(the global home owns no packages)'
555
+ ],
556
+ benign: true
557
+ };
558
+ }
559
+ return {
560
+ issues: plugins.validatePluginScopeOnDisk(global)
561
+ .filter((issue) => issue.code === 'workspace-parent-missing')
562
+ .map((issue) => `${issue.message}. Remedy: ${issue.remedy}`),
563
+ benign: false
564
+ };
565
+ }
566
+ return { issues: [], benign: true };
567
+ }
568
+ /**
569
+ * Boot the SHIPPING path against the scope root and require every row to be
570
+ * composed. A manifest that parses is not a scope that composes.
571
+ *
572
+ * Two details that are easy to get wrong and were measured, not guessed:
573
+ *
574
+ * - The child's cwd is the scope root only for the WORKSPACE scope. Setting
575
+ * it to `$VIBES_HOME` for the global scope made bootstrap read
576
+ * `$VIBES_HOME/package.json#vibes.plugins` as the *workspace* fallback
577
+ * list (there is no config there, so the cwd IS the workspace root), so
578
+ * the workspace batch claimed every global specifier and the global batch
579
+ * reported `deduped` for all of them.
580
+ * - `deduped` is therefore accepted alongside `loaded`: it means the very
581
+ * same specifier was already selected by an earlier scope in that process,
582
+ * so the plugin IS composed. `missing` and `failed` are the real failures,
583
+ * and `shadowed` is reported as a warning detail because another plugin
584
+ * owns the id.
585
+ */
586
+ async verifyScope(plugins, scope) {
587
+ let argv;
588
+ try {
589
+ argv = (await getRuntimeHostIdentity(this.runtime)).selfSpawnArgv('vibes update verification');
590
+ }
591
+ catch (error) {
592
+ const failure = CaughtFailureSchema.parse(error);
593
+ return { ok: false, rows: [], detail: reportLine(failure), failure };
594
+ }
595
+ const execution = await resolveRuntimeExecution(this.runtime, 'update verification boot');
596
+ const request = {
597
+ command: argv[0],
598
+ args: [...argv.slice(1), 'plugins', 'list', '--json', '--scope', scope.kind],
599
+ env: composeChildEnv(this.runtime, { env: { VIBES_HOST_ROOT: scope.root } }),
600
+ envMode: 'replace',
601
+ timeout: VERIFICATION_TIMEOUT_MS
602
+ };
603
+ if (scope.kind === 'workspace')
604
+ request.cwd = scope.root;
605
+ const result = await execution.execute(request);
606
+ if (result.exitCode !== 0 || result.timedOut) {
607
+ const summary = summarizeChildFailure({ stdout: result.stdout, stderr: result.stderr, max: 3 });
608
+ return {
609
+ ok: false,
610
+ rows: [],
611
+ detail: (summary && redactedExcerpt(summary, { maxChars: 1_000 })) ||
612
+ `verification exited ${result.timedOut ? 'by timeout' : String(result.exitCode)} with no output`
613
+ };
614
+ }
615
+ const start = result.stdout.indexOf('{');
616
+ if (start < 0)
617
+ return { ok: false, rows: [], detail: 'verification produced no JSON envelope' };
618
+ let json;
619
+ try {
620
+ json = JSON.parse(result.stdout.slice(start));
621
+ }
622
+ catch (error) {
623
+ const failure = CaughtFailureSchema.parse(error);
624
+ return { ok: false, rows: [], detail: `verification produced malformed JSON: ${reportLine(failure)}`, failure };
625
+ }
626
+ const envelope = VerificationEnvelopeSchema.safeParse(json);
627
+ if (!envelope.success)
628
+ return { ok: false, rows: [], detail: 'verification produced a JSON envelope of the wrong shape' };
629
+ const linkage = new Map((envelope.data.resolutions ?? []).map((row) => [row.specifier, row]));
630
+ const rows = (envelope.data.plugins ?? []).map((row) => ({
631
+ plugin: row.plugin,
632
+ status: row.status,
633
+ reason: row.reason,
634
+ linked: linkage.get(row.plugin)?.linked ?? null,
635
+ realpath: linkage.get(row.plugin)?.realpath ?? null
636
+ }));
637
+ const bad = rows.filter((row) => isUnverifiedPluginRow(row));
638
+ if (bad.length) {
639
+ return { ok: false, rows, detail: bad.map((row) => `${row.plugin}: ${row.status}`).join(', ') };
640
+ }
641
+ return { ok: true, rows };
642
+ }
643
+ emit(payload) {
644
+ if (this.format === 'json') {
645
+ const roll = {
646
+ denominator: payload.outcomes.length,
647
+ rolled: payload.outcomes.filter((outcome) => outcome.rolled).length,
648
+ bodies: payload.outcomes.map(rollBodyRow)
649
+ };
650
+ if (payload.censusNote)
651
+ roll.note = payload.censusNote;
652
+ this.ui.json({ dryRun: payload.dryRun, self: payload.self, scopes: payload.reports, roll });
653
+ return;
654
+ }
655
+ if (payload.self) {
656
+ const self = payload.self;
657
+ // The sha256 is printed on BOTH surfaces. A dry run whose whole purpose
658
+ // is "tell me what you would do" that withholds the digest it would
659
+ // verify leaves the operator unable to compare it with SHA256SUMS.txt
660
+ // before authorising the real run.
661
+ const digest = self.sha256 ? ` sha256 ${self.sha256}` : '';
662
+ this.ui.info(self.replaced
663
+ ? `self ${self.fromVersion} → ${self.toVersion} installed at ${self.target}${digest}`
664
+ : `self ${self.fromVersion}: ${self.note ?? 'unchanged'}${digest}`);
665
+ this.ui.info(` manifest ${self.manifestUrl}`);
666
+ }
667
+ for (const report of payload.reports) {
668
+ for (const line of report.repair ?? [])
669
+ this.ui.info(` ${line}`);
670
+ if (report.skipped !== undefined) {
671
+ const line = `${report.scope} ${report.root}: ${report.skipped}`;
672
+ if (report.failed === true)
673
+ this.ui.error(line);
674
+ else
675
+ this.ui.warn(line);
676
+ continue;
677
+ }
678
+ const lock = report.changed
679
+ ? `lock ${report.lockBefore ?? 'none'} → ${report.lockAfter ?? 'none'}`
680
+ : `lock ${report.lockAfter ?? report.lockBefore ?? 'none'} (unchanged)`;
681
+ const failed = !payload.dryRun && !report.verified;
682
+ const verdict = payload.dryRun
683
+ ? 'dry run'
684
+ : report.verified
685
+ ? 'verified'
686
+ : `NOT VERIFIED: ${report.verifyDetail ?? 'unknown'}`;
687
+ const line = `${report.scope} ${report.root} — ${lock} — ${verdict}`;
688
+ if (failed)
689
+ this.ui.error(line);
690
+ else
691
+ this.ui.info(line);
692
+ for (const command of payload.dryRun ? report.commands : []) {
693
+ this.ui.info(` would run: ${command.join(' ')}`);
694
+ }
695
+ for (const row of report.plugins) {
696
+ this.ui.info(` ${row.plugin} ${row.status} ${row.linked === null ? '' : row.linked ? 'linked' : 'installed'}`.trimEnd());
697
+ }
698
+ }
699
+ if (payload.censusNote)
700
+ this.ui.warn(payload.censusNote);
701
+ for (const outcome of payload.outcomes) {
702
+ if (outcome.rolled)
703
+ this.ui.success(outcome.line);
704
+ else
705
+ this.ui.warn(outcome.line);
706
+ }
707
+ if (payload.outcomes.length) {
708
+ this.ui.info(`${payload.outcomes.filter((outcome) => outcome.rolled).length} of ` +
709
+ `${payload.outcomes.length} known bodies rolled`);
710
+ }
711
+ }
712
+ }
713
+ function rollBodyRow(outcome) {
714
+ const row = {
715
+ kind: outcome.body.kind,
716
+ id: outcome.body.id,
717
+ source: outcome.body.source,
718
+ shape: outcome.body.shape,
719
+ hqRegistered: outcome.body.hqRegistered,
720
+ // The census reports WHY a body was in scope, not only what happened to
721
+ // it: `replacedExecutable` is the nerve `vibes pm status` prints as
722
+ // `⚠ STALE`, and `null` means unanswerable.
723
+ replacedExecutable: outcome.body.replacedExecutable ?? null,
724
+ rolled: outcome.rolled
725
+ };
726
+ if (outcome.body.pid !== undefined)
727
+ row.runningPid = outcome.body.pid;
728
+ if (outcome.body.replacedReason)
729
+ row.replacedReason = outcome.body.replacedReason;
730
+ if (outcome.previousPid !== undefined)
731
+ row.previousPid = outcome.previousPid;
732
+ if (outcome.pid !== undefined)
733
+ row.pid = outcome.pid;
734
+ if (outcome.reason)
735
+ row.reason = outcome.reason;
736
+ return row;
737
+ }
738
+ /**
739
+ * `vibes update` did not complete: one line per failing scope, and the failure
740
+ * raised behind each refused or unverified scope kept as the cause for `-v`.
741
+ */
742
+ function updateIncomplete(failures, scoped) {
743
+ const message = `vibes update did not complete (${failures.length} of ${scoped.length} scope(s)):\n` +
744
+ failures.map((failure) => ` ${failure.scope} ${failure.root}: ${failure.reason}`).join('\n');
745
+ const causes = scoped.flatMap((outcome) => (outcome.failure === undefined ? [] : [outcome.failure]));
746
+ if (causes.length === 0)
747
+ return new Error(message);
748
+ return new Error(message, { cause: new AggregateError(causes, 'The failure behind each refused or unverified scope') });
749
+ }
750
+ //# sourceMappingURL=update.cli-command.impl.consumer.js.map