@astrale-os/cli 1.0.0-beta.32 → 1.0.0-beta.43

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 (441) hide show
  1. package/CHANGELOG.md +928 -0
  2. package/README.md +53 -43
  3. package/THIRD-PARTY-NOTICES.md +37 -1
  4. package/dist/astrale.js +349631 -83669
  5. package/dist/public/connect-core.js +16 -47
  6. package/dist/public/keys/index.js +1 -1
  7. package/dist/types/admin/contract.d.ts +11 -0
  8. package/dist/types/admin/graph/index.d.ts +1 -0
  9. package/dist/types/admin/graph/nodes.d.ts +12 -0
  10. package/dist/types/admin/instance/client.d.ts +2 -0
  11. package/dist/types/connection/lifetime.d.ts +1 -1
  12. package/dist/types/errors.d.ts +3 -0
  13. package/dist/types/lib/proc.d.ts +1 -0
  14. package/dist/types/lib/update.d.ts +15 -7
  15. package/package.json +8 -6
  16. package/studio/client/dist/assets/index-5C1uT6ML.css +2 -0
  17. package/studio/client/dist/assets/index-C4kNlvwF.js +80 -0
  18. package/studio/client/dist/assets/schema-studio-BR9d0faB.js +8 -0
  19. package/studio/client/dist/index.html +13 -2
  20. package/studio/package.json +2 -1
  21. package/studio/server/agent/ask.test.ts +7 -1
  22. package/studio/server/agent/bridge/grant.ts +15 -3
  23. package/studio/server/agent/prompts/system.test.ts +5 -0
  24. package/studio/server/agent/prompts/system.ts +19 -4
  25. package/studio/server/agent/routes.test.ts +39 -1
  26. package/studio/server/agent/routes.ts +3 -0
  27. package/studio/server/agent/run/coordinator.test.ts +7 -1
  28. package/studio/server/agent/run/preparation.ts +1 -0
  29. package/studio/server/agent/run/transcript.test.ts +47 -2
  30. package/studio/server/agent/run/transcript.ts +32 -3
  31. package/studio/server/api/documents.test.ts +31 -0
  32. package/studio/server/api/documents.ts +35 -1
  33. package/studio/server/api-agent-loadout.test.ts +7 -1
  34. package/studio/server/api.test.ts +7 -1
  35. package/studio/server/cache.ts +4 -4
  36. package/studio/server/detect.ts +9 -12
  37. package/studio/server/domain.test.ts +83 -3
  38. package/studio/server/domain.ts +205 -27
  39. package/studio/server/handoff/service.ts +1 -1
  40. package/studio/server/index.ts +5 -8
  41. package/studio/server/introspect/extractor.ts +111 -17
  42. package/studio/server/introspect/overlay-tsmorph.test.ts +9 -9
  43. package/studio/server/introspect/overlay.ts +1 -1
  44. package/studio/server/introspect/runtime.ts +9 -1
  45. package/studio/server/introspect/source-overlay/spans.ts +2 -5
  46. package/studio/server/lifecycle.ts +1 -1
  47. package/studio/server/state/baseline.test.ts +14 -0
  48. package/studio/server/state/baseline.ts +16 -2
  49. package/studio/server/watch.test.ts +2 -1
  50. package/studio/server/watch.ts +5 -2
  51. package/studio/server/workspace/create.ts +9 -8
  52. package/studio/server/workspace-state.ts +4 -6
  53. package/studio/server/workspace-watch.ts +33 -22
  54. package/studio/shared/contracts/agent.ts +3 -0
  55. package/viewer/dist/main.js +12 -12
  56. package/src/__tests__/fixtures/publication.ts +0 -20
  57. package/src/admin/.spec/api.d.ts +0 -3
  58. package/src/admin/.spec/architecture.md +0 -22
  59. package/src/admin/.spec/icon.svg +0 -3
  60. package/src/admin/.spec/layout.ts +0 -6
  61. package/src/admin/__tests__/fixture.ts +0 -25
  62. package/src/admin/catalog/.spec/api.d.ts +0 -63
  63. package/src/admin/catalog/.spec/architecture.md +0 -7
  64. package/src/admin/catalog/.spec/icon.svg +0 -3
  65. package/src/admin/catalog/.spec/layout.ts +0 -6
  66. package/src/admin/catalog/__tests__/client.test.ts +0 -245
  67. package/src/admin/catalog/client.ts +0 -195
  68. package/src/admin/catalog/index.ts +0 -13
  69. package/src/admin/catalog/model.ts +0 -41
  70. package/src/admin/catalog/tsconfig.json +0 -8
  71. package/src/admin/contract.ts +0 -47
  72. package/src/admin/graph/.spec/api.d.ts +0 -22
  73. package/src/admin/graph/.spec/architecture.md +0 -5
  74. package/src/admin/graph/.spec/icon.svg +0 -3
  75. package/src/admin/graph/.spec/layout.ts +0 -3
  76. package/src/admin/graph/index.ts +0 -6
  77. package/src/admin/graph/nodes.ts +0 -56
  78. package/src/admin/index.ts +0 -2
  79. package/src/admin/instance/.spec/api.d.ts +0 -62
  80. package/src/admin/instance/.spec/architecture.md +0 -10
  81. package/src/admin/instance/.spec/icon.svg +0 -3
  82. package/src/admin/instance/.spec/layout.ts +0 -6
  83. package/src/admin/instance/__tests__/client.test.ts +0 -273
  84. package/src/admin/instance/client.ts +0 -191
  85. package/src/admin/instance/index.ts +0 -15
  86. package/src/admin/instance/model.ts +0 -59
  87. package/src/admin/instance/tsconfig.json +0 -8
  88. package/src/commands/__tests__/admin-instance.test.ts +0 -91
  89. package/src/commands/__tests__/auth-login.test.ts +0 -231
  90. package/src/commands/__tests__/auth-token.test.ts +0 -223
  91. package/src/commands/__tests__/call-describe.test.ts +0 -80
  92. package/src/commands/__tests__/call.test.ts +0 -63
  93. package/src/commands/__tests__/domain-install-operation.test.ts +0 -145
  94. package/src/commands/__tests__/domain-install-owned.test.ts +0 -191
  95. package/src/commands/__tests__/domain-list.test.ts +0 -133
  96. package/src/commands/__tests__/domain-uninstall.test.ts +0 -53
  97. package/src/commands/__tests__/install-direct.test.ts +0 -128
  98. package/src/commands/__tests__/install-identity-override.test.ts +0 -76
  99. package/src/commands/__tests__/instance-bookmark.test.ts +0 -166
  100. package/src/commands/__tests__/instance-list-admin.test.ts +0 -26
  101. package/src/commands/__tests__/instance-list-rows.test.ts +0 -64
  102. package/src/commands/__tests__/instance-status.test.ts +0 -139
  103. package/src/commands/__tests__/instance-use.test.ts +0 -67
  104. package/src/commands/__tests__/introspect-parse.test.ts +0 -100
  105. package/src/commands/__tests__/logs.test.ts +0 -345
  106. package/src/commands/__tests__/read-commands.test.ts +0 -377
  107. package/src/commands/__tests__/setup-plan.test.ts +0 -61
  108. package/src/commands/__tests__/studio-descriptor.test.ts +0 -22
  109. package/src/commands/__tests__/token-ttl.test.ts +0 -66
  110. package/src/commands/__tests__/update.test.ts +0 -95
  111. package/src/commands/__tests__/view-build.test.ts +0 -58
  112. package/src/commands/__tests__/view.test.ts +0 -197
  113. package/src/commands/admin/status.ts +0 -61
  114. package/src/commands/admin/use.ts +0 -100
  115. package/src/commands/auth/login.ts +0 -82
  116. package/src/commands/auth/logout.ts +0 -53
  117. package/src/commands/auth/status.ts +0 -86
  118. package/src/commands/auth/token.ts +0 -174
  119. package/src/commands/browser.ts +0 -236
  120. package/src/commands/call-describe.ts +0 -123
  121. package/src/commands/call.ts +0 -230
  122. package/src/commands/domain/install.ts +0 -575
  123. package/src/commands/domain/list.ts +0 -155
  124. package/src/commands/domain/publish.ts +0 -136
  125. package/src/commands/domain/uninstall.ts +0 -128
  126. package/src/commands/get.ts +0 -69
  127. package/src/commands/identity/__tests__/register.test.ts +0 -93
  128. package/src/commands/identity/create.ts +0 -35
  129. package/src/commands/identity/delete.ts +0 -24
  130. package/src/commands/identity/export.ts +0 -46
  131. package/src/commands/identity/import.ts +0 -61
  132. package/src/commands/identity/list.ts +0 -56
  133. package/src/commands/identity/register.ts +0 -195
  134. package/src/commands/identity/sync.ts +0 -38
  135. package/src/commands/identity/unsync.ts +0 -34
  136. package/src/commands/identity/use.ts +0 -24
  137. package/src/commands/identity/whoami.ts +0 -34
  138. package/src/commands/idp/add.ts +0 -150
  139. package/src/commands/idp/list.ts +0 -57
  140. package/src/commands/idp/refresh.ts +0 -38
  141. package/src/commands/idp/remove.ts +0 -36
  142. package/src/commands/idp/show.ts +0 -29
  143. package/src/commands/instance/active.ts +0 -73
  144. package/src/commands/instance/bookmark.ts +0 -101
  145. package/src/commands/instance/create.ts +0 -61
  146. package/src/commands/instance/delete.ts +0 -69
  147. package/src/commands/instance/forget.ts +0 -26
  148. package/src/commands/instance/list.ts +0 -197
  149. package/src/commands/instance/status.ts +0 -92
  150. package/src/commands/instance/use.ts +0 -263
  151. package/src/commands/introspect.ts +0 -129
  152. package/src/commands/logs.ts +0 -371
  153. package/src/commands/mutate.ts +0 -122
  154. package/src/commands/query.ts +0 -142
  155. package/src/commands/session/analyze.ts +0 -72
  156. package/src/commands/session/list.ts +0 -36
  157. package/src/commands/setup.ts +0 -54
  158. package/src/commands/status.ts +0 -60
  159. package/src/commands/studio.ts +0 -434
  160. package/src/commands/token.ts +0 -121
  161. package/src/commands/ui/__tests__/commands.test.ts +0 -152
  162. package/src/commands/ui/add.ts +0 -51
  163. package/src/commands/ui/doctor.ts +0 -21
  164. package/src/commands/ui/init.ts +0 -38
  165. package/src/commands/ui/list.ts +0 -26
  166. package/src/commands/ui/preset-apply.ts +0 -19
  167. package/src/commands/ui/preset-list.ts +0 -12
  168. package/src/commands/ui/shared.ts +0 -25
  169. package/src/commands/update.ts +0 -356
  170. package/src/commands/use.ts +0 -86
  171. package/src/commands/view-serve.ts +0 -26
  172. package/src/commands/view.ts +0 -658
  173. package/src/connect-core.test.ts +0 -38
  174. package/src/connect-core.ts +0 -42
  175. package/src/connection/.spec/api.d.ts +0 -110
  176. package/src/connection/.spec/architecture.md +0 -58
  177. package/src/connection/.spec/capabilities/connection.ts +0 -13
  178. package/src/connection/.spec/examples/call.ts +0 -7
  179. package/src/connection/.spec/flows/command.ts +0 -20
  180. package/src/connection/.spec/flows/session.ts +0 -62
  181. package/src/connection/.spec/laws/connection.ts +0 -213
  182. package/src/connection/.spec/layout.ts +0 -22
  183. package/src/connection/__tests__/auth.test.ts +0 -129
  184. package/src/connection/__tests__/ca-fetch.test.ts +0 -60
  185. package/src/connection/__tests__/call.test.ts +0 -17
  186. package/src/connection/__tests__/credential.test.ts +0 -254
  187. package/src/connection/__tests__/errors.test.ts +0 -577
  188. package/src/connection/__tests__/exchange.test.ts +0 -404
  189. package/src/connection/__tests__/failure-fixtures.ts +0 -39
  190. package/src/connection/__tests__/failure-safety.test.ts +0 -75
  191. package/src/connection/__tests__/fixtures/localhost-cert.pem +0 -19
  192. package/src/connection/__tests__/fixtures/localhost-key.base64 +0 -1
  193. package/src/connection/__tests__/managed.test.ts +0 -39
  194. package/src/connection/__tests__/reasons.test.ts +0 -78
  195. package/src/connection/__tests__/self.test.ts +0 -71
  196. package/src/connection/__tests__/session.test.ts +0 -144
  197. package/src/connection/__tests__/target.test.ts +0 -95
  198. package/src/connection/auth.ts +0 -265
  199. package/src/connection/call.ts +0 -7
  200. package/src/connection/command.ts +0 -67
  201. package/src/connection/credential.ts +0 -138
  202. package/src/connection/errors.ts +0 -1
  203. package/src/connection/exchange.ts +0 -394
  204. package/src/connection/failure/classify.ts +0 -110
  205. package/src/connection/failure/index.ts +0 -18
  206. package/src/connection/failure/model.ts +0 -28
  207. package/src/connection/failure/render.ts +0 -116
  208. package/src/connection/index.ts +0 -8
  209. package/src/connection/lifetime.ts +0 -25
  210. package/src/connection/reasons.ts +0 -209
  211. package/src/connection/self.ts +0 -138
  212. package/src/connection/session.ts +0 -203
  213. package/src/connection/target.ts +0 -112
  214. package/src/connection/tsconfig.json +0 -6
  215. package/src/errors.ts +0 -66
  216. package/src/graph/.spec/api.d.ts +0 -41
  217. package/src/graph/.spec/laws/documents.ts +0 -24
  218. package/src/graph/.spec/laws/projection.ts +0 -13
  219. package/src/graph/.spec/layout.ts +0 -14
  220. package/src/graph/__tests__/mutation.test.ts +0 -51
  221. package/src/graph/__tests__/projection.test.ts +0 -22
  222. package/src/graph/__tests__/query.test.ts +0 -180
  223. package/src/graph/class.ts +0 -30
  224. package/src/graph/index.ts +0 -4
  225. package/src/graph/mutation.ts +0 -46
  226. package/src/graph/projection.ts +0 -21
  227. package/src/graph/query.ts +0 -95
  228. package/src/graph/tsconfig.json +0 -6
  229. package/src/identity/.history/ard/bootstrap-root-transfer.md +0 -57
  230. package/src/identity/.history/ard/domain-mediated-registration.md +0 -36
  231. package/src/identity/.spec/api.d.ts +0 -124
  232. package/src/identity/.spec/architecture.md +0 -33
  233. package/src/identity/.spec/capabilities/identity.ts +0 -7
  234. package/src/identity/.spec/flows/bootstrap-root.ts +0 -79
  235. package/src/identity/.spec/flows/domain-mediated-registration.ts +0 -51
  236. package/src/identity/.spec/flows/import.ts +0 -29
  237. package/src/identity/.spec/laws/identity.ts +0 -56
  238. package/src/identity/.spec/layout.ts +0 -13
  239. package/src/identity/__tests__/fixtures/registry-journey.ts +0 -65
  240. package/src/identity/__tests__/registration.test.ts +0 -64
  241. package/src/identity/__tests__/registry.test.ts +0 -74
  242. package/src/identity/__tests__/transfer.test.ts +0 -198
  243. package/src/identity/index.ts +0 -23
  244. package/src/identity/registration.ts +0 -72
  245. package/src/identity/registry.ts +0 -181
  246. package/src/identity/transfer.ts +0 -187
  247. package/src/identity/tsconfig.json +0 -6
  248. package/src/index.ts +0 -1
  249. package/src/keys/.spec/api.d.ts +0 -74
  250. package/src/keys/.spec/architecture.md +0 -13
  251. package/src/keys/.spec/laws/keys.ts +0 -43
  252. package/src/keys/.spec/layout.ts +0 -6
  253. package/src/keys/__tests__/algorithm.test.ts +0 -39
  254. package/src/keys/__tests__/keys.test.ts +0 -172
  255. package/src/keys/algorithm.ts +0 -33
  256. package/src/keys/credential.ts +0 -113
  257. package/src/keys/index.ts +0 -14
  258. package/src/keys/pair.ts +0 -219
  259. package/src/keys/tsconfig.json +0 -6
  260. package/src/lib/__tests__/admin-target.test.ts +0 -184
  261. package/src/lib/__tests__/binary.test.ts +0 -70
  262. package/src/lib/__tests__/browser-retention.test.ts +0 -212
  263. package/src/lib/__tests__/command-dx.test.ts +0 -102
  264. package/src/lib/__tests__/concurrency.test.ts +0 -62
  265. package/src/lib/__tests__/config.test.ts +0 -81
  266. package/src/lib/__tests__/design.test.ts +0 -99
  267. package/src/lib/__tests__/domain-publication.test.ts +0 -138
  268. package/src/lib/__tests__/format.test.ts +0 -22
  269. package/src/lib/__tests__/idp-session.driver.ts +0 -60
  270. package/src/lib/__tests__/idp-session.test.ts +0 -404
  271. package/src/lib/__tests__/idp.test.ts +0 -415
  272. package/src/lib/__tests__/instance-candidates.test.ts +0 -146
  273. package/src/lib/__tests__/instance-target.test.ts +0 -213
  274. package/src/lib/__tests__/instance.test.ts +0 -192
  275. package/src/lib/__tests__/local-status.test.ts +0 -202
  276. package/src/lib/__tests__/log-errors.test.ts +0 -64
  277. package/src/lib/__tests__/log.test.ts +0 -31
  278. package/src/lib/__tests__/meta.test.ts +0 -55
  279. package/src/lib/__tests__/output.test.ts +0 -162
  280. package/src/lib/__tests__/panel.test.ts +0 -40
  281. package/src/lib/__tests__/port.test.ts +0 -44
  282. package/src/lib/__tests__/prompt.test.ts +0 -25
  283. package/src/lib/__tests__/sdk-deps.test.ts +0 -68
  284. package/src/lib/__tests__/self.test.ts +0 -50
  285. package/src/lib/__tests__/skills.test.ts +0 -834
  286. package/src/lib/__tests__/studio-server-deps.test.ts +0 -111
  287. package/src/lib/__tests__/table.test.ts +0 -53
  288. package/src/lib/__tests__/update.test.ts +0 -511
  289. package/src/lib/__tests__/use-target.test.ts +0 -56
  290. package/src/lib/__tests__/validation.test.ts +0 -34
  291. package/src/lib/__tests__/view-assets.test.ts +0 -171
  292. package/src/lib/__tests__/view-external-open-intent.test.ts +0 -150
  293. package/src/lib/__tests__/view-external-open-origins.test.ts +0 -27
  294. package/src/lib/__tests__/view-open-intent.test.ts +0 -280
  295. package/src/lib/__tests__/view-port-allocation.test.ts +0 -82
  296. package/src/lib/__tests__/view-server.test.ts +0 -91
  297. package/src/lib/__tests__/view-session.test.ts +0 -100
  298. package/src/lib/__tests__/view-snapshot.test.ts +0 -77
  299. package/src/lib/admin-domain.ts +0 -43
  300. package/src/lib/admin-instance.ts +0 -56
  301. package/src/lib/admin-target.ts +0 -290
  302. package/src/lib/binary.ts +0 -127
  303. package/src/lib/browser-retention.ts +0 -210
  304. package/src/lib/browser.ts +0 -150
  305. package/src/lib/ca-fetch.ts +0 -133
  306. package/src/lib/command-dx.ts +0 -191
  307. package/src/lib/concurrency.ts +0 -31
  308. package/src/lib/config.ts +0 -57
  309. package/src/lib/credential-lifetime.ts +0 -24
  310. package/src/lib/domain-publication.ts +0 -104
  311. package/src/lib/failure-debug.ts +0 -26
  312. package/src/lib/format.ts +0 -4
  313. package/src/lib/idp-session.ts +0 -156
  314. package/src/lib/idp.ts +0 -926
  315. package/src/lib/instance-candidates.ts +0 -49
  316. package/src/lib/instance-target.ts +0 -197
  317. package/src/lib/instance.ts +0 -468
  318. package/src/lib/invocation.ts +0 -11
  319. package/src/lib/local-status.ts +0 -152
  320. package/src/lib/log.ts +0 -143
  321. package/src/lib/login-flow.ts +0 -201
  322. package/src/lib/meta.ts +0 -90
  323. package/src/lib/output.ts +0 -238
  324. package/src/lib/panel.ts +0 -61
  325. package/src/lib/port.ts +0 -41
  326. package/src/lib/proc.ts +0 -87
  327. package/src/lib/prompt.ts +0 -136
  328. package/src/lib/provision-instance.ts +0 -139
  329. package/src/lib/sdk-deps.ts +0 -104
  330. package/src/lib/self.ts +0 -22
  331. package/src/lib/skills/lock.ts +0 -117
  332. package/src/lib/skills/sync.ts +0 -878
  333. package/src/lib/skills.ts +0 -120
  334. package/src/lib/table.ts +0 -62
  335. package/src/lib/update.ts +0 -568
  336. package/src/lib/use-target.ts +0 -24
  337. package/src/lib/validation.ts +0 -59
  338. package/src/lib/view/assets.ts +0 -87
  339. package/src/lib/view/external-open-intent.ts +0 -41
  340. package/src/lib/view/external-open-origins.ts +0 -37
  341. package/src/lib/view/host-capabilities.ts +0 -17
  342. package/src/lib/view/open-intent.ts +0 -67
  343. package/src/lib/view/port-allocation.ts +0 -19
  344. package/src/lib/view/resolve.ts +0 -111
  345. package/src/lib/view/server.ts +0 -259
  346. package/src/lib/view/session.ts +0 -154
  347. package/src/lib/view/snapshot.ts +0 -101
  348. package/src/paths/.spec/api.d.ts +0 -13
  349. package/src/paths/.spec/architecture.md +0 -5
  350. package/src/paths/.spec/layout.ts +0 -3
  351. package/src/paths/index.ts +0 -13
  352. package/src/program/.spec/api.d.ts +0 -43
  353. package/src/program/.spec/architecture.md +0 -18
  354. package/src/program/.spec/laws/program.ts +0 -29
  355. package/src/program/.spec/layout.ts +0 -14
  356. package/src/program/__tests__/program.test.ts +0 -556
  357. package/src/program/argv.ts +0 -14
  358. package/src/program/build.ts +0 -205
  359. package/src/program/command.ts +0 -43
  360. package/src/program/index.ts +0 -3
  361. package/src/program/options.ts +0 -41
  362. package/src/program/registry.ts +0 -64
  363. package/src/program/tsconfig.json +0 -6
  364. package/src/setup/__tests__/instance-step.test.ts +0 -256
  365. package/src/setup/__tests__/util.test.ts +0 -29
  366. package/src/setup/engine.ts +0 -83
  367. package/src/setup/render.ts +0 -107
  368. package/src/setup/steps/admin.ts +0 -93
  369. package/src/setup/steps/agent-browser.ts +0 -81
  370. package/src/setup/steps/auth.ts +0 -54
  371. package/src/setup/steps/domain.ts +0 -68
  372. package/src/setup/steps/index.ts +0 -18
  373. package/src/setup/steps/instance.ts +0 -206
  374. package/src/setup/steps/skills-bridge.ts +0 -70
  375. package/src/setup/steps/skills.ts +0 -61
  376. package/src/setup/types.ts +0 -61
  377. package/src/setup/util.ts +0 -34
  378. package/src/state/.spec/api.d.ts +0 -152
  379. package/src/state/.spec/architecture.md +0 -45
  380. package/src/state/.spec/capabilities/state.ts +0 -7
  381. package/src/state/.spec/flows/identity-update.ts +0 -22
  382. package/src/state/.spec/laws/state.ts +0 -73
  383. package/src/state/.spec/layout.ts +0 -15
  384. package/src/state/__tests__/exchange-credentials.test.ts +0 -341
  385. package/src/state/__tests__/files.test.ts +0 -136
  386. package/src/state/__tests__/fixtures/identity-transition.ts +0 -37
  387. package/src/state/__tests__/fixtures/session-route-process.ts +0 -93
  388. package/src/state/__tests__/identities.test.ts +0 -142
  389. package/src/state/__tests__/paths.test.ts +0 -28
  390. package/src/state/__tests__/session-routes.test.ts +0 -138
  391. package/src/state/exchange-credentials.ts +0 -250
  392. package/src/state/files.ts +0 -170
  393. package/src/state/identities.ts +0 -229
  394. package/src/state/index.ts +0 -31
  395. package/src/state/paths.ts +0 -64
  396. package/src/state/session-routes.ts +0 -34
  397. package/src/state/tsconfig.json +0 -6
  398. package/src/telemetry/__tests__/analyze-log.test.ts +0 -38
  399. package/src/telemetry/__tests__/gate.test.ts +0 -63
  400. package/src/telemetry/__tests__/recorder.test.ts +0 -111
  401. package/src/telemetry/__tests__/redact.test.ts +0 -79
  402. package/src/telemetry/__tests__/retention.test.ts +0 -267
  403. package/src/telemetry/__tests__/session.test.ts +0 -166
  404. package/src/telemetry/__tests__/settings.test.ts +0 -102
  405. package/src/telemetry/__tests__/store-scan.test.ts +0 -128
  406. package/src/telemetry/__tests__/trigger.test.ts +0 -78
  407. package/src/telemetry/adapters/__tests__/claude-code.test.ts +0 -89
  408. package/src/telemetry/adapters/__tests__/codex.test.ts +0 -138
  409. package/src/telemetry/adapters/__tests__/index.test.ts +0 -88
  410. package/src/telemetry/adapters/claude-code.ts +0 -90
  411. package/src/telemetry/adapters/codex.ts +0 -160
  412. package/src/telemetry/adapters/index.ts +0 -45
  413. package/src/telemetry/adapters/types.ts +0 -21
  414. package/src/telemetry/analyze.ts +0 -249
  415. package/src/telemetry/gate.ts +0 -79
  416. package/src/telemetry/recorder.ts +0 -68
  417. package/src/telemetry/redact.ts +0 -53
  418. package/src/telemetry/retention.ts +0 -176
  419. package/src/telemetry/session.ts +0 -94
  420. package/src/telemetry/settings.ts +0 -79
  421. package/src/telemetry/store.ts +0 -174
  422. package/src/telemetry/trigger.ts +0 -107
  423. package/src/telemetry/types.ts +0 -64
  424. package/src/test-utils.ts +0 -18
  425. package/src/ui/.spec/api.d.ts +0 -14
  426. package/src/ui/.spec/architecture.md +0 -14
  427. package/src/ui/.spec/laws.ts +0 -50
  428. package/src/ui/.spec/layout.ts +0 -16
  429. package/src/ui/__tests__/ui.test.ts +0 -1742
  430. package/src/ui/index.ts +0 -13
  431. package/src/ui/lock.ts +0 -89
  432. package/src/ui/model.ts +0 -83
  433. package/src/ui/operations.ts +0 -1044
  434. package/src/ui/project.ts +0 -333
  435. package/src/ui/release.ts +0 -398
  436. package/src/ui/runner.ts +0 -18
  437. package/studio/client/dist/assets/index-BFVFs_8x.js +0 -81
  438. package/studio/client/dist/assets/index-DuB9iUdu.css +0 -2
  439. package/studio/client/dist/assets/schema-studio-DH6oEWME.js +0 -8
  440. package/studio/tsconfig.json +0 -24
  441. package/viewer/tsconfig.json +0 -13
@@ -1,878 +0,0 @@
1
- import { createHash } from 'node:crypto'
2
- import {
3
- cp,
4
- lstat,
5
- mkdir,
6
- mkdtemp,
7
- readFile,
8
- readdir,
9
- readlink,
10
- rename,
11
- rm,
12
- symlink,
13
- writeFile,
14
- } from 'node:fs/promises'
15
- import { homedir, tmpdir } from 'node:os'
16
- import { dirname, join, relative, resolve } from 'node:path'
17
-
18
- import { AstraleError } from '../../errors'
19
- import { run, type RunResult } from '../proc'
20
- import { withFileLock } from './lock'
21
-
22
- /**
23
- * Astrale-owned agent skill reconciliation. The ecosystem installer owns agent links;
24
- * Astrale owns source freshness, local integrity, repair, rollback, and outcomes.
25
- */
26
-
27
- /** Published source whose top-level skill directories Astrale owns as one cohort. */
28
- export const ASTRALE_CLI_SKILL_SOURCE = 'astrale-os/cli'
29
- export const ASTRALE_SKILL_REPAIR_COMMAND = 'astrale update --yes --no-deps'
30
-
31
- /** Deliberately pinned: installer behavior is part of the verified update path. */
32
- export const SKILLS_INSTALLER_PACKAGE = 'skills@1.5.23'
33
-
34
- /** Human-facing recovery stays on the Astrale-owned, verified path. */
35
- export const SKILL_INSTALL_HINT = ASTRALE_SKILL_REPAIR_COMMAND
36
-
37
- export type SkillCheckStatus =
38
- | 'current'
39
- | 'update-available'
40
- | 'repair-needed'
41
- | 'unavailable'
42
- | 'skipped'
43
-
44
- export type SkillApplyStatus =
45
- | 'unchanged'
46
- | 'installed'
47
- | 'updated'
48
- | 'repaired'
49
- | 'failed'
50
- | 'skipped'
51
-
52
- export type SkillCheckResult = {
53
- status: SkillCheckStatus
54
- error?: string
55
- source?: {
56
- repository: typeof ASTRALE_CLI_SKILL_SOURCE
57
- revision: string
58
- skills: Array<{ name: string; tree: string; entrypoint: string }>
59
- }
60
- }
61
-
62
- export type SkillApplyResult = {
63
- status: SkillApplyStatus
64
- }
65
-
66
- type SourceSkill = { name: string; path: string; tree: string }
67
- export type AstraleSkillSourceSnapshot = { ref: 'main'; revision: string; skills: SourceSkill[] }
68
-
69
- type SkillLockEntry = {
70
- source?: string
71
- sourceType?: string
72
- sourceUrl?: string
73
- ref?: string
74
- skillPath?: string
75
- skillFolderHash?: string
76
- installedAt?: string
77
- updatedAt?: string
78
- astraleSourceRevision?: string
79
- astraleSourceTree?: string
80
- }
81
-
82
- type SkillLock = {
83
- version: number
84
- skills: Record<string, SkillLockEntry>
85
- lastSelectedAgents?: string[]
86
- dismissed?: Record<string, unknown>
87
- }
88
-
89
- type SkillState = 'absent' | 'current' | 'outdated' | 'unhealthy'
90
-
91
- type SkillInspection = {
92
- state: SkillState
93
- sourceCurrent: boolean
94
- managedNames: string[]
95
- managedFolders: string[]
96
- }
97
-
98
- export type SkillSyncDependencies = {
99
- home?: string
100
- lockPath?: string
101
- resolveSource?: () => Promise<AstraleSkillSourceSnapshot>
102
- run?: (file: string, args?: string[]) => Promise<RunResult>
103
- }
104
-
105
- const SOURCE_REPOSITORY_URL = 'https://github.com/astrale-os/cli.git'
106
- const SAFE_NAME = /^[a-z0-9][a-z0-9._-]*$/iu
107
-
108
- function resolvedDependencies(overrides: SkillSyncDependencies = {}) {
109
- const home = overrides.home ?? homedir()
110
- const xdgStateHome = process.env.XDG_STATE_HOME
111
- return {
112
- home,
113
- lockPath:
114
- overrides.lockPath ??
115
- (xdgStateHome
116
- ? join(xdgStateHome, 'skills', '.skill-lock.json')
117
- : join(home, '.agents', '.skill-lock.json')),
118
- run: overrides.run ?? run,
119
- resolveSource: overrides.resolveSource,
120
- }
121
- }
122
-
123
- function sourceOwned(entry: SkillLockEntry): boolean {
124
- return (
125
- entry.source === ASTRALE_CLI_SKILL_SOURCE ||
126
- entry.sourceUrl === SOURCE_REPOSITORY_URL ||
127
- entry.sourceUrl === `https://github.com/${ASTRALE_CLI_SKILL_SOURCE}`
128
- )
129
- }
130
-
131
- function folderName(entry: SkillLockEntry): string | null {
132
- const match = entry.skillPath?.match(/^skills\/([^/]+)\/SKILL\.md$/u)
133
- return match?.[1] ?? null
134
- }
135
-
136
- async function resolveAstraleSkillSource(
137
- execute: ReturnType<typeof resolvedDependencies>['run'],
138
- ): Promise<AstraleSkillSourceSnapshot> {
139
- const checkout = await mkdtemp(join(tmpdir(), 'astrale-skill-source-'))
140
- try {
141
- const cloned = await execute('git', [
142
- 'clone',
143
- '--quiet',
144
- '--depth',
145
- '1',
146
- '--filter=blob:none',
147
- '--no-checkout',
148
- '--single-branch',
149
- '--branch',
150
- 'main',
151
- SOURCE_REPOSITORY_URL,
152
- checkout,
153
- ])
154
- if (cloned.code !== 0) throw new Error(cloned.stderr || cloned.stdout || 'git clone failed')
155
- const revision = await execute('git', ['-C', checkout, 'rev-parse', 'HEAD'])
156
- if (revision.code !== 0) {
157
- throw new Error(revision.stderr || revision.stdout || 'git rev-parse failed')
158
- }
159
- const folders = await execute('git', ['-C', checkout, 'ls-tree', 'HEAD:skills'])
160
- const files = await execute('git', [
161
- '-C',
162
- checkout,
163
- 'ls-tree',
164
- '-r',
165
- '--name-only',
166
- 'HEAD:skills',
167
- ])
168
- if (folders.code !== 0 || files.code !== 0) {
169
- throw new Error(folders.stderr || files.stderr || 'git ls-tree failed')
170
- }
171
- const skillFiles = new Set(
172
- files.stdout
173
- .split('\n')
174
- .filter((path) => /^[^/]+\/SKILL\.md$/u.test(path))
175
- .map((path) => path.slice(0, -'/SKILL.md'.length)),
176
- )
177
- const skills = folders.stdout.split('\n').flatMap((line): SourceSkill[] => {
178
- const match = line.match(/^040000 tree ([0-9a-f]{40})\t([^/]+)$/u)
179
- if (!match || !SAFE_NAME.test(match[2]) || !skillFiles.has(match[2])) return []
180
- return [{ name: match[2], path: `skills/${match[2]}/SKILL.md`, tree: match[1] }]
181
- })
182
- skills.sort((a, b) => a.name.localeCompare(b.name))
183
- if (skills.length === 0) throw new Error('astrale-os/cli publishes no top-level skills')
184
- return { ref: 'main', revision: revision.stdout.trim(), skills }
185
- } finally {
186
- await rm(checkout, { recursive: true, force: true })
187
- }
188
- }
189
-
190
- async function readSkillLock(
191
- path: string,
192
- ): Promise<{ lock: SkillLock | null; raw: string | null }> {
193
- let raw: string
194
- try {
195
- raw = await readFile(path, 'utf8')
196
- } catch (error) {
197
- if ((error as { code?: string }).code === 'ENOENT') return { lock: null, raw: null }
198
- throw error
199
- }
200
- try {
201
- const parsed: unknown = JSON.parse(raw)
202
- if (
203
- parsed === null ||
204
- typeof parsed !== 'object' ||
205
- !('skills' in parsed) ||
206
- (parsed as { skills?: unknown }).skills === null ||
207
- typeof (parsed as { skills?: unknown }).skills !== 'object'
208
- ) {
209
- return { lock: null, raw }
210
- }
211
- return { lock: parsed as SkillLock, raw }
212
- } catch {
213
- return { lock: null, raw }
214
- }
215
- }
216
-
217
- async function writeSkillLock(path: string, lock: SkillLock): Promise<void> {
218
- await mkdir(dirname(path), { recursive: true })
219
- const next = `${path}.next`
220
- await writeFile(next, `${JSON.stringify(lock, null, 2)}\n`, { mode: 0o600 })
221
- await rename(next, path)
222
- }
223
-
224
- function gitObjectHash(type: 'blob' | 'tree', body: Buffer): Buffer {
225
- return createHash('sha1')
226
- .update(Buffer.from(`${type} ${body.length}\0`))
227
- .update(body)
228
- .digest()
229
- }
230
-
231
- /** Git tree identity of the bytes actually installed on disk. */
232
- export async function computeSkillTreeHash(root: string): Promise<string> {
233
- async function treeHash(directory: string): Promise<Buffer> {
234
- const entries = await Promise.all(
235
- (await readdir(directory)).map(async (name) => {
236
- const path = join(directory, name)
237
- const stat = await lstat(path)
238
- if (stat.isDirectory()) {
239
- return { name, sort: `${name}/`, mode: '40000', hash: await treeHash(path) }
240
- }
241
- if (stat.isSymbolicLink()) {
242
- return {
243
- name,
244
- sort: name,
245
- mode: '120000',
246
- hash: gitObjectHash('blob', Buffer.from(await readlink(path))),
247
- }
248
- }
249
- return {
250
- name,
251
- sort: name,
252
- mode: stat.mode & 0o111 ? '100755' : '100644',
253
- hash: gitObjectHash('blob', await readFile(path)),
254
- }
255
- }),
256
- )
257
- entries.sort((a, b) => Buffer.compare(Buffer.from(a.sort), Buffer.from(b.sort)))
258
- const body = Buffer.concat(
259
- entries.flatMap((entry) => [Buffer.from(`${entry.mode} ${entry.name}\0`), entry.hash]),
260
- )
261
- return gitObjectHash('tree', body)
262
- }
263
- return (await treeHash(root)).toString('hex')
264
- }
265
-
266
- async function computeInstallerFolderHash(root: string): Promise<string> {
267
- const files: Array<{ path: string; content: Buffer }> = []
268
- async function collect(directory: string): Promise<void> {
269
- await Promise.all(
270
- (await readdir(directory, { withFileTypes: true })).map(async (entry) => {
271
- if (entry.name === '.git' || entry.name === 'node_modules') return
272
- const path = join(directory, entry.name)
273
- if (entry.isDirectory()) await collect(path)
274
- else if (entry.isFile()) {
275
- files.push({
276
- path: relative(root, path).split('\\').join('/'),
277
- content: await readFile(path),
278
- })
279
- }
280
- }),
281
- )
282
- }
283
- await collect(root)
284
- files.sort((a, b) => a.path.localeCompare(b.path))
285
- const hash = createHash('sha256')
286
- for (const file of files) hash.update(file.path).update(file.content)
287
- return hash.digest('hex')
288
- }
289
-
290
- async function inspectAstraleSkills(
291
- snapshot: AstraleSkillSourceSnapshot,
292
- home: string,
293
- lockPath: string,
294
- ): Promise<SkillInspection> {
295
- const { lock } = await readSkillLock(lockPath)
296
- const managed = Object.entries(lock?.skills ?? {}).filter(([, entry]) => sourceOwned(entry))
297
- const expected = new Map(snapshot.skills.map((skill) => [skill.name, skill]))
298
- const expectedPresence = await Promise.all(
299
- snapshot.skills.map(async (skill) => {
300
- try {
301
- return (await lstat(join(home, '.agents', 'skills', skill.name))).isDirectory()
302
- } catch {
303
- return false
304
- }
305
- }),
306
- )
307
- if (managed.length === 0 && expectedPresence.every((present) => !present)) {
308
- return { state: 'absent', sourceCurrent: false, managedNames: [], managedFolders: [] }
309
- }
310
-
311
- const folders = managed.flatMap(([name, entry]) => {
312
- const folder = folderName(entry)
313
- return folder ? [{ key: name, folder, entry }] : []
314
- })
315
- const uniqueFolders = new Set(folders.map((entry) => entry.folder))
316
- let coherent = folders.length === managed.length && uniqueFolders.size === folders.length
317
- const actualHashes = new Map<string, string>()
318
- for (const item of folders) {
319
- try {
320
- const root = join(home, '.agents', 'skills', item.folder)
321
- const actual = await computeSkillTreeHash(root)
322
- actualHashes.set(item.folder, actual)
323
- const receiptHash = item.entry.skillFolderHash
324
- if (
325
- !receiptHash ||
326
- (receiptHash.length === 40
327
- ? actual !== receiptHash
328
- : receiptHash.length === 64
329
- ? (await computeInstallerFolderHash(root)) !== receiptHash
330
- : true)
331
- ) {
332
- coherent = false
333
- }
334
- if (item.entry.skillPath !== `skills/${item.folder}/SKILL.md`) coherent = false
335
- } catch {
336
- coherent = false
337
- }
338
- }
339
- for (const [index, skill] of snapshot.skills.entries()) {
340
- if (expectedPresence[index] && !uniqueFolders.has(skill.name)) coherent = false
341
- }
342
- if (await directoryExists(join(home, '.claude'))) {
343
- for (const skill of snapshot.skills) {
344
- const link = join(home, '.claude', 'skills', skill.name)
345
- try {
346
- if (!(await lstat(link)).isSymbolicLink()) coherent = false
347
- else if (
348
- resolve(dirname(link), await readlink(link)) !==
349
- join(home, '.agents', 'skills', skill.name)
350
- ) {
351
- coherent = false
352
- }
353
- } catch {
354
- coherent = false
355
- }
356
- }
357
- }
358
-
359
- const sourceCurrent =
360
- coherent &&
361
- managed.length === snapshot.skills.length &&
362
- folders.every(({ key, folder, entry }) => {
363
- const skill = expected.get(folder)
364
- return (
365
- key === folder &&
366
- skill !== undefined &&
367
- entry.ref === snapshot.ref &&
368
- actualHashes.get(folder) === skill.tree
369
- )
370
- })
371
- const exactCurrent =
372
- sourceCurrent &&
373
- folders.every(({ folder, entry }) => {
374
- const skill = expected.get(folder)
375
- return (
376
- skill !== undefined &&
377
- entry.astraleSourceRevision === snapshot.revision &&
378
- entry.astraleSourceTree === skill.tree
379
- )
380
- })
381
-
382
- return {
383
- state: exactCurrent ? 'current' : coherent ? 'outdated' : 'unhealthy',
384
- sourceCurrent,
385
- managedNames: managed.map(([name]) => name),
386
- managedFolders: [...uniqueFolders],
387
- }
388
- }
389
-
390
- async function directoryExists(path: string): Promise<boolean> {
391
- try {
392
- return (await lstat(path)).isDirectory()
393
- } catch {
394
- return false
395
- }
396
- }
397
-
398
- async function sourceSnapshot(dependencies: ReturnType<typeof resolvedDependencies>) {
399
- return dependencies.resolveSource
400
- ? await dependencies.resolveSource()
401
- : await resolveAstraleSkillSource(dependencies.run)
402
- }
403
-
404
- function installerArgs(...args: string[]): string[] {
405
- return ['--yes', SKILLS_INSTALLER_PACKAGE, ...args]
406
- }
407
-
408
- async function installSnapshot(
409
- snapshot: AstraleSkillSourceSnapshot,
410
- execute: ReturnType<typeof resolvedDependencies>['run'],
411
- selectedAgents: string[],
412
- ): Promise<RunResult> {
413
- return await execute(
414
- 'npx',
415
- installerArgs(
416
- 'add',
417
- `${ASTRALE_CLI_SKILL_SOURCE}#${snapshot.ref}`,
418
- '-g',
419
- '-y',
420
- '--skill',
421
- ...snapshot.skills.map((skill) => skill.name),
422
- ...(selectedAgents.length > 0 ? ['--agent', ...selectedAgents] : []),
423
- ),
424
- )
425
- }
426
-
427
- async function stampAstraleSource(
428
- snapshot: AstraleSkillSourceSnapshot,
429
- lockPath: string,
430
- ): Promise<void> {
431
- const { lock } = await readSkillLock(lockPath)
432
- if (!lock) throw new Error('skill installer receipt is unavailable after installation')
433
- for (const skill of snapshot.skills) {
434
- const entry = lock.skills[skill.name]
435
- if (
436
- !entry ||
437
- !sourceOwned(entry) ||
438
- entry.ref !== snapshot.ref ||
439
- entry.skillPath !== skill.path
440
- ) {
441
- throw new Error(`skill installer receipt is incomplete for ${skill.name}`)
442
- }
443
- entry.astraleSourceRevision = snapshot.revision
444
- entry.astraleSourceTree = skill.tree
445
- }
446
- await writeSkillLock(lockPath, lock)
447
- }
448
-
449
- async function selectedAgents(home: string, lockPath: string): Promise<string[]> {
450
- const { lock } = await readSkillLock(lockPath)
451
- const agents = new Set(
452
- (lock?.lastSelectedAgents ?? []).filter(
453
- (agent): agent is string => typeof agent === 'string' && SAFE_NAME.test(agent),
454
- ),
455
- )
456
- agents.add('codex')
457
- if (await directoryExists(join(home, '.claude'))) agents.add('claude-code')
458
- return [...agents]
459
- }
460
-
461
- async function cleanManagedState(names: string[], home: string, lockPath: string): Promise<void> {
462
- for (const name of names) {
463
- await rm(join(home, '.agents', 'skills', name), { recursive: true, force: true })
464
- }
465
- const { lock, raw } = await readSkillLock(lockPath)
466
- if (!lock) {
467
- if (raw !== null) await rm(lockPath, { force: true })
468
- return
469
- }
470
- for (const [name, entry] of Object.entries(lock.skills)) {
471
- if (sourceOwned(entry)) delete lock.skills[name]
472
- }
473
- await writeSkillLock(lockPath, lock)
474
- }
475
-
476
- async function removeKnownAgentLinks(names: string[], home: string): Promise<void> {
477
- const roots = await readdir(home, { withFileTypes: true }).catch(() => [])
478
- for (const root of roots) {
479
- if (!root.isDirectory() || !root.name.startsWith('.') || root.name === '.agents') continue
480
- for (const name of names) {
481
- const link = join(home, root.name, 'skills', name)
482
- try {
483
- const stat = await lstat(link)
484
- if (!stat.isSymbolicLink()) continue
485
- const target = await readlink(link)
486
- if (resolve(dirname(link), target) === join(home, '.agents', 'skills', name)) {
487
- await rm(link, { force: true })
488
- }
489
- } catch {
490
- // Missing or foreign links are not part of the Astrale-owned repair.
491
- }
492
- }
493
- }
494
- }
495
-
496
- async function pruneObsoleteEntries(
497
- snapshot: AstraleSkillSourceSnapshot,
498
- home: string,
499
- lockPath: string,
500
- execute: ReturnType<typeof resolvedDependencies>['run'],
501
- knownRetired: string[],
502
- ): Promise<void> {
503
- const expected = new Set(snapshot.skills.map((skill) => skill.name))
504
- const { lock } = await readSkillLock(lockPath)
505
- const retiredEntries = Object.entries(lock?.skills ?? {}).filter(([, entry]) => {
506
- const folder = folderName(entry)
507
- return sourceOwned(entry) && folder !== null && !expected.has(folder)
508
- })
509
- const retired = [
510
- ...new Set([
511
- ...knownRetired,
512
- ...retiredEntries.flatMap(([, entry]) => folderName(entry) ?? []),
513
- ]),
514
- ]
515
- if (retired.length > 0) {
516
- const result = await execute('npx', installerArgs('remove', ...retired, '-g', '-y'))
517
- if (result.code !== 0) throw new Error(result.stderr || result.stdout || 'skill removal failed')
518
- }
519
- const refreshed = await readSkillLock(lockPath)
520
- if (!refreshed.lock) return
521
- let changed = false
522
- for (const [name, entry] of Object.entries(refreshed.lock.skills)) {
523
- if (!sourceOwned(entry)) continue
524
- const folder = folderName(entry)
525
- if (folder === null || name !== folder || !expected.has(folder)) {
526
- delete refreshed.lock.skills[name]
527
- changed = true
528
- }
529
- }
530
- if (changed) await writeSkillLock(lockPath, refreshed.lock)
531
-
532
- await removeKnownAgentLinks(retired, home)
533
- for (const name of retired) {
534
- await rm(join(home, '.agents', 'skills', name), { recursive: true, force: true })
535
- }
536
- }
537
-
538
- type SkillBackup = {
539
- root: string
540
- names: string[]
541
- copied: string[]
542
- links: AgentLinkBackup[]
543
- lockRaw: string | null
544
- createdAt: string
545
- phase: 'prepared' | 'verified'
546
- }
547
-
548
- type AgentLinkBackup = { root: string; name: string; target: string }
549
-
550
- const BACKUP_PREFIX = '.astrale-skill-backup-'
551
- const BACKUP_MANIFEST = '.manifest.json'
552
-
553
- async function captureAgentLinks(home: string, names: string[]): Promise<AgentLinkBackup[]> {
554
- const links: AgentLinkBackup[] = []
555
- const roots = await readdir(home, { withFileTypes: true }).catch(() => [])
556
- for (const root of roots) {
557
- if (!root.isDirectory() || !root.name.startsWith('.') || root.name === '.agents') continue
558
- for (const name of names) {
559
- const path = join(home, root.name, 'skills', name)
560
- try {
561
- if ((await lstat(path)).isSymbolicLink()) {
562
- links.push({ root: root.name, name, target: await readlink(path) })
563
- }
564
- } catch {
565
- // Only existing symlinks need transactional restoration.
566
- }
567
- }
568
- }
569
- return links
570
- }
571
-
572
- async function writeBackupManifest(backup: SkillBackup): Promise<void> {
573
- const next = join(backup.root, `${BACKUP_MANIFEST}.next`)
574
- await writeFile(next, JSON.stringify(backup), { mode: 0o600 })
575
- await rename(next, join(backup.root, BACKUP_MANIFEST))
576
- }
577
-
578
- async function captureBackup(home: string, lockPath: string, names: string[]) {
579
- const agentsRoot = join(home, '.agents')
580
- await mkdir(agentsRoot, { recursive: true })
581
- const root = await mkdtemp(join(agentsRoot, BACKUP_PREFIX))
582
- const copied: string[] = []
583
- for (const name of names) {
584
- const source = join(agentsRoot, 'skills', name)
585
- try {
586
- await cp(source, join(root, name), { recursive: true, dereference: false })
587
- copied.push(name)
588
- } catch (error) {
589
- if ((error as { code?: string }).code !== 'ENOENT') throw error
590
- }
591
- }
592
- const lock = await readSkillLock(lockPath)
593
- const backup: SkillBackup = {
594
- root,
595
- names,
596
- copied,
597
- links: await captureAgentLinks(home, names),
598
- lockRaw: lock.raw,
599
- createdAt: new Date().toISOString(),
600
- phase: 'prepared',
601
- }
602
- await writeBackupManifest(backup)
603
- return backup
604
- }
605
-
606
- async function extendBackup(backup: SkillBackup, home: string, names: string[]): Promise<void> {
607
- const additions = names.filter((name) => !backup.names.includes(name))
608
- if (additions.length === 0) return
609
- for (const name of additions) {
610
- const source = join(home, '.agents', 'skills', name)
611
- try {
612
- await cp(source, join(backup.root, name), { recursive: true, dereference: false })
613
- backup.copied.push(name)
614
- } catch (error) {
615
- if ((error as { code?: string }).code !== 'ENOENT') throw error
616
- }
617
- }
618
- backup.names.push(...additions)
619
- backup.links.push(...(await captureAgentLinks(home, additions)))
620
- await writeBackupManifest(backup)
621
- }
622
-
623
- async function restoreAgentLinks(
624
- backup: SkillBackup,
625
- home: string,
626
- onlyForeign = false,
627
- ): Promise<void> {
628
- for (const link of backup.links) {
629
- const path = join(home, link.root, 'skills', link.name)
630
- const canonical = join(home, '.agents', 'skills', link.name)
631
- if (onlyForeign && resolve(dirname(path), link.target) === canonical) continue
632
- await mkdir(dirname(path), { recursive: true })
633
- await rm(path, { recursive: true, force: true })
634
- await symlink(link.target, path)
635
- }
636
- }
637
-
638
- async function restoreBackup(
639
- backup: Awaited<ReturnType<typeof captureBackup>>,
640
- home: string,
641
- lockPath: string,
642
- ): Promise<void> {
643
- for (const name of backup.names) {
644
- await rm(join(home, '.agents', 'skills', name), { recursive: true, force: true })
645
- }
646
- await mkdir(join(home, '.agents', 'skills'), { recursive: true })
647
- for (const name of backup.copied) {
648
- await cp(join(backup.root, name), join(home, '.agents', 'skills', name), {
649
- recursive: true,
650
- dereference: false,
651
- })
652
- }
653
- if (backup.lockRaw === null) await rm(lockPath, { force: true })
654
- else {
655
- await mkdir(dirname(lockPath), { recursive: true })
656
- const next = `${lockPath}.restore`
657
- await writeFile(next, backup.lockRaw, { mode: 0o600 })
658
- await rename(next, lockPath)
659
- }
660
- await removeKnownAgentLinks(backup.names, home)
661
- await restoreAgentLinks(backup, home)
662
- }
663
-
664
- async function recoverInterruptedBackup(home: string, lockPath: string): Promise<void> {
665
- const agentsRoot = join(home, '.agents')
666
- const candidates = (await readdir(agentsRoot, { withFileTypes: true }).catch(() => []))
667
- .filter((entry) => entry.isDirectory() && entry.name.startsWith(BACKUP_PREFIX))
668
- .map((entry) => join(agentsRoot, entry.name))
669
- const backups: SkillBackup[] = []
670
- for (const root of candidates) {
671
- try {
672
- const parsed = JSON.parse(await readFile(join(root, BACKUP_MANIFEST), 'utf8')) as SkillBackup
673
- if (
674
- Array.isArray(parsed.names) &&
675
- parsed.names.every((name) => typeof name === 'string' && SAFE_NAME.test(name)) &&
676
- Array.isArray(parsed.copied) &&
677
- parsed.copied.every((name) => typeof name === 'string' && parsed.names.includes(name)) &&
678
- Array.isArray(parsed.links) &&
679
- (typeof parsed.lockRaw === 'string' || parsed.lockRaw === null) &&
680
- typeof parsed.createdAt === 'string' &&
681
- (parsed.phase === 'prepared' || parsed.phase === 'verified') &&
682
- parsed.links.every(
683
- (link) =>
684
- link !== null &&
685
- typeof link === 'object' &&
686
- typeof link.root === 'string' &&
687
- /^\.[^/]+$/u.test(link.root) &&
688
- typeof link.name === 'string' &&
689
- parsed.names.includes(link.name) &&
690
- typeof link.target === 'string',
691
- )
692
- ) {
693
- const backup = { ...parsed, root }
694
- if (backup.phase === 'verified') await rm(root, { recursive: true, force: true })
695
- else backups.push(backup)
696
- } else {
697
- await rm(root, { recursive: true, force: true })
698
- }
699
- } catch {
700
- // The live cohort is untouched until the manifest is complete.
701
- await rm(root, { recursive: true, force: true })
702
- }
703
- }
704
- if (backups.length === 0) return
705
- backups.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
706
- await restoreBackup(backups[0], home, lockPath)
707
- await Promise.all(candidates.map((root) => rm(root, { recursive: true, force: true })))
708
- }
709
-
710
- async function markBackupVerified(backup: SkillBackup): Promise<void> {
711
- backup.phase = 'verified'
712
- await writeBackupManifest(backup)
713
- }
714
-
715
- function skillFailure(error: unknown): AstraleError {
716
- const detail = error instanceof Error && error.message.trim() ? `: ${error.message}` : ''
717
- return new AstraleError(
718
- 'SKILL_UPDATE_FAILED',
719
- `Astrale could not install and verify its agent skills${detail}`,
720
- `Retry with \`${ASTRALE_SKILL_REPAIR_COMMAND}\`.`,
721
- error instanceof Error ? { cause: error } : undefined,
722
- )
723
- }
724
-
725
- /** Read-only source, freshness, and local integrity check. */
726
- export async function checkAstraleSkills(
727
- overrides: SkillSyncDependencies = {},
728
- ): Promise<SkillCheckResult> {
729
- const dependencies = resolvedDependencies(overrides)
730
- try {
731
- const snapshot = await sourceSnapshot(dependencies)
732
- const inspection = await inspectAstraleSkills(
733
- snapshot,
734
- dependencies.home,
735
- dependencies.lockPath,
736
- )
737
- const status =
738
- inspection.state === 'current'
739
- ? 'current'
740
- : inspection.state === 'unhealthy'
741
- ? 'repair-needed'
742
- : 'update-available'
743
- return {
744
- status,
745
- ...(status === 'current'
746
- ? {
747
- source: {
748
- repository: ASTRALE_CLI_SKILL_SOURCE,
749
- revision: snapshot.revision,
750
- skills: snapshot.skills.map(({ name, tree, path }) => ({
751
- name,
752
- tree,
753
- entrypoint: path,
754
- })),
755
- },
756
- }
757
- : {}),
758
- }
759
- } catch (error) {
760
- return { status: 'unavailable', error: error instanceof Error ? error.message : String(error) }
761
- }
762
- }
763
-
764
- /**
765
- * Ensure the complete Astrale-owned cohort is current and internally consistent.
766
- * A clean second attempt follows any failed ordinary refresh; a failed operation
767
- * restores the exact prior cohort and never reports success.
768
- */
769
- export async function syncAstraleSkills(
770
- overrides: SkillSyncDependencies = {},
771
- ): Promise<SkillApplyResult> {
772
- const dependencies = resolvedDependencies(overrides)
773
- let snapshot: AstraleSkillSourceSnapshot
774
- try {
775
- snapshot = await sourceSnapshot(dependencies)
776
- } catch (error) {
777
- throw skillFailure(error)
778
- }
779
- const lockFile = join(
780
- process.env.ASTRALE_HOME ?? join(dependencies.home, '.astrale'),
781
- 'locks',
782
- 'skills-update.lock',
783
- )
784
-
785
- try {
786
- return await withFileLock(lockFile, async () => {
787
- await recoverInterruptedBackup(dependencies.home, dependencies.lockPath)
788
- const initial = await inspectAstraleSkills(snapshot, dependencies.home, dependencies.lockPath)
789
- if (initial.state === 'current') return { status: 'unchanged' }
790
-
791
- let expectedNames = snapshot.skills.map((skill) => skill.name)
792
- let managedFolders = [...new Set([...expectedNames, ...initial.managedFolders])]
793
- let knownRetired = initial.managedFolders.filter((name) => !expectedNames.includes(name))
794
- const agents = await selectedAgents(dependencies.home, dependencies.lockPath)
795
- const backup = await captureBackup(dependencies.home, dependencies.lockPath, managedFolders)
796
- let lastError: unknown
797
- try {
798
- for (let attempt = 0; attempt < 2; attempt++) {
799
- try {
800
- if (attempt === 1) {
801
- snapshot = await sourceSnapshot(dependencies)
802
- expectedNames = snapshot.skills.map((skill) => skill.name)
803
- managedFolders = [...new Set([...managedFolders, ...expectedNames])]
804
- await extendBackup(backup, dependencies.home, managedFolders)
805
- knownRetired = [
806
- ...new Set([
807
- ...knownRetired,
808
- ...managedFolders.filter((name) => !expectedNames.includes(name)),
809
- ]),
810
- ]
811
- }
812
- if (initial.state === 'unhealthy' || attempt === 1) {
813
- await cleanManagedState(managedFolders, dependencies.home, dependencies.lockPath)
814
- }
815
- const installed = await installSnapshot(snapshot, dependencies.run, agents)
816
- if (installed.code !== 0) {
817
- throw new Error(installed.stderr || installed.stdout || 'skill installer failed')
818
- }
819
- await pruneObsoleteEntries(
820
- snapshot,
821
- dependencies.home,
822
- dependencies.lockPath,
823
- dependencies.run,
824
- knownRetired,
825
- )
826
- const installedInspection = await inspectAstraleSkills(
827
- snapshot,
828
- dependencies.home,
829
- dependencies.lockPath,
830
- )
831
- if (!installedInspection.sourceCurrent) {
832
- throw new Error('installed Astrale skills do not match the resolved source')
833
- }
834
- await stampAstraleSource(snapshot, dependencies.lockPath)
835
- const verified = await inspectAstraleSkills(
836
- snapshot,
837
- dependencies.home,
838
- dependencies.lockPath,
839
- )
840
- if (verified.state !== 'current') {
841
- throw new Error('installed Astrale skills did not pass verification')
842
- }
843
- const latest = await sourceSnapshot(dependencies)
844
- if (latest.revision !== snapshot.revision) {
845
- throw new Error('Astrale skill source changed during installation')
846
- }
847
- await markBackupVerified(backup)
848
- await rm(backup.root, { recursive: true, force: true })
849
- return {
850
- status:
851
- initial.state === 'absent'
852
- ? 'installed'
853
- : initial.state === 'outdated'
854
- ? 'updated'
855
- : 'repaired',
856
- }
857
- } catch (error) {
858
- lastError = error
859
- }
860
- }
861
- throw lastError
862
- } catch (error) {
863
- if (initial.state === 'absent' || initial.state === 'unhealthy') {
864
- await cleanManagedState(managedFolders, dependencies.home, dependencies.lockPath)
865
- await removeKnownAgentLinks(managedFolders, dependencies.home)
866
- await restoreAgentLinks(backup, dependencies.home, true)
867
- } else {
868
- await restoreBackup(backup, dependencies.home, dependencies.lockPath)
869
- }
870
- await rm(backup.root, { recursive: true, force: true })
871
- throw skillFailure(error)
872
- }
873
- })
874
- } catch (error) {
875
- if (error instanceof AstraleError) throw error
876
- throw skillFailure(error)
877
- }
878
- }