@astrale-os/cli 1.0.0-beta.3 → 1.0.0-beta.30

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 (343) hide show
  1. package/README.md +80 -6
  2. package/dist/astrale.js +55471 -58674
  3. package/dist/public/connect-core.js +2158 -30706
  4. package/dist/public/keys/index.js +1016 -29541
  5. package/dist/public/paths/index.js +370 -28654
  6. package/dist/types/admin/contract.d.ts +26 -0
  7. package/dist/types/admin/instance/client.d.ts +20 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +6 -10
  10. package/dist/types/connection/auth.d.ts +3 -0
  11. package/dist/types/connection/call.d.ts +3 -0
  12. package/dist/types/connection/command.d.ts +27 -0
  13. package/dist/types/connection/credential.d.ts +14 -0
  14. package/dist/types/connection/errors.d.ts +1 -0
  15. package/dist/types/connection/exchange.d.ts +9 -0
  16. package/dist/types/connection/failure/classify.d.ts +2 -0
  17. package/dist/types/connection/failure/index.d.ts +5 -0
  18. package/dist/types/connection/failure/model.d.ts +25 -0
  19. package/dist/types/connection/failure/render.d.ts +3 -0
  20. package/dist/types/connection/index.d.ts +8 -0
  21. package/dist/types/connection/reasons.d.ts +37 -0
  22. package/dist/types/connection/self.d.ts +37 -0
  23. package/dist/types/connection/session.d.ts +31 -0
  24. package/dist/types/connection/target.d.ts +38 -0
  25. package/dist/types/errors.d.ts +1 -1
  26. package/dist/types/lib/admin-instance.d.ts +17 -0
  27. package/dist/types/lib/admin-target.d.ts +11 -36
  28. package/dist/types/lib/config.d.ts +13 -59
  29. package/dist/types/lib/failure-debug.d.ts +1 -0
  30. package/dist/types/lib/idp.d.ts +25 -173
  31. package/dist/types/lib/instance-target.d.ts +1 -1
  32. package/dist/types/lib/instance.d.ts +30 -88
  33. package/dist/types/lib/log.d.ts +5 -3
  34. package/dist/types/lib/output.d.ts +1 -1
  35. package/dist/types/lib/proc.d.ts +43 -0
  36. package/dist/types/lib/self.d.ts +9 -0
  37. package/dist/types/lib/update.d.ts +109 -0
  38. package/dist/types/lib/validation.d.ts +4 -1
  39. package/dist/types/state/exchange-credentials.d.ts +6 -2
  40. package/dist/types/state/files.d.ts +2 -0
  41. package/dist/types/state/index.d.ts +3 -2
  42. package/dist/types/state/paths.d.ts +2 -0
  43. package/dist/types/state/session-routes.d.ts +10 -0
  44. package/package.json +16 -19
  45. package/src/__tests__/fixtures/publication.ts +20 -0
  46. package/src/admin/.spec/architecture.md +12 -5
  47. package/src/admin/__tests__/fixture.ts +25 -0
  48. package/src/admin/catalog/.spec/api.d.ts +1 -3
  49. package/src/admin/catalog/.spec/architecture.md +5 -4
  50. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  51. package/src/admin/catalog/client.ts +40 -54
  52. package/src/admin/catalog/model.ts +3 -4
  53. package/src/admin/contract.ts +47 -0
  54. package/src/admin/graph/.spec/api.d.ts +4 -10
  55. package/src/admin/graph/nodes.ts +1 -1
  56. package/src/admin/instance/.spec/api.d.ts +5 -9
  57. package/src/admin/instance/.spec/architecture.md +7 -7
  58. package/src/admin/instance/__tests__/client.test.ts +157 -175
  59. package/src/admin/instance/client.ts +39 -128
  60. package/src/admin/instance/index.ts +1 -2
  61. package/src/admin/instance/model.ts +12 -18
  62. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  63. package/src/commands/__tests__/call-describe.test.ts +46 -33
  64. package/src/commands/__tests__/call.test.ts +34 -0
  65. package/src/commands/__tests__/domain-install-operation.test.ts +30 -22
  66. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  67. package/src/commands/__tests__/domain-list.test.ts +5 -25
  68. package/src/commands/__tests__/install-direct.test.ts +115 -8
  69. package/src/commands/__tests__/install-identity-override.test.ts +6 -17
  70. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  71. package/src/commands/__tests__/instance-status.test.ts +139 -0
  72. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  73. package/src/commands/__tests__/logs.test.ts +250 -2
  74. package/src/commands/__tests__/read-commands.test.ts +118 -10
  75. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  76. package/src/commands/__tests__/update.test.ts +25 -3
  77. package/src/commands/__tests__/view.test.ts +50 -2
  78. package/src/commands/auth/logout.ts +2 -1
  79. package/src/commands/browser.ts +29 -0
  80. package/src/commands/call-describe.ts +60 -68
  81. package/src/commands/call.ts +71 -59
  82. package/src/commands/domain/install.ts +73 -46
  83. package/src/commands/domain/list.ts +1 -1
  84. package/src/commands/domain/publish.ts +14 -11
  85. package/src/commands/domain/uninstall.ts +2 -2
  86. package/src/commands/get.ts +15 -16
  87. package/src/commands/identity/__tests__/register.test.ts +3 -3
  88. package/src/commands/identity/register.ts +8 -8
  89. package/src/commands/instance/create.ts +8 -16
  90. package/src/commands/instance/delete.ts +12 -9
  91. package/src/commands/instance/list.ts +2 -2
  92. package/src/commands/instance/status.ts +66 -20
  93. package/src/commands/instance/use.ts +16 -12
  94. package/src/commands/introspect.ts +53 -41
  95. package/src/commands/logs.ts +123 -36
  96. package/src/commands/mutate.ts +6 -5
  97. package/src/commands/query.ts +35 -32
  98. package/src/commands/session/analyze.ts +26 -4
  99. package/src/commands/token.ts +42 -27
  100. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  101. package/src/commands/ui/add.ts +51 -0
  102. package/src/commands/ui/doctor.ts +21 -0
  103. package/src/commands/ui/init.ts +38 -0
  104. package/src/commands/ui/list.ts +26 -0
  105. package/src/commands/ui/preset-apply.ts +19 -0
  106. package/src/commands/ui/preset-list.ts +12 -0
  107. package/src/commands/ui/shared.ts +25 -0
  108. package/src/commands/update.ts +89 -48
  109. package/src/commands/view.ts +82 -37
  110. package/src/connection/.spec/api.d.ts +7 -8
  111. package/src/connection/.spec/architecture.md +14 -5
  112. package/src/connection/.spec/flows/session.ts +2 -2
  113. package/src/connection/.spec/laws/connection.ts +6 -2
  114. package/src/connection/.spec/layout.ts +1 -0
  115. package/src/connection/__tests__/auth.test.ts +1 -0
  116. package/src/connection/__tests__/credential.test.ts +21 -1
  117. package/src/connection/__tests__/errors.test.ts +27 -20
  118. package/src/connection/__tests__/exchange.test.ts +64 -11
  119. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  120. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  121. package/src/connection/__tests__/self.test.ts +31 -11
  122. package/src/connection/__tests__/session.test.ts +8 -4
  123. package/src/connection/__tests__/target.test.ts +2 -0
  124. package/src/connection/auth.ts +13 -0
  125. package/src/connection/call.ts +1 -1
  126. package/src/connection/credential.ts +2 -2
  127. package/src/connection/errors.ts +1 -355
  128. package/src/connection/exchange.ts +110 -61
  129. package/src/connection/failure/classify.ts +110 -0
  130. package/src/connection/failure/index.ts +18 -0
  131. package/src/connection/failure/model.ts +28 -0
  132. package/src/connection/failure/render.ts +116 -0
  133. package/src/connection/reasons.ts +16 -0
  134. package/src/connection/self.ts +39 -21
  135. package/src/connection/session.ts +27 -9
  136. package/src/errors.ts +3 -2
  137. package/src/graph/.spec/api.d.ts +12 -5
  138. package/src/graph/.spec/layout.ts +9 -1
  139. package/src/graph/__tests__/mutation.test.ts +2 -2
  140. package/src/graph/__tests__/query.test.ts +25 -6
  141. package/src/graph/class.ts +30 -0
  142. package/src/graph/index.ts +1 -0
  143. package/src/graph/query.ts +21 -32
  144. package/src/identity/.spec/api.d.ts +2 -2
  145. package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
  146. package/src/identity/__tests__/registration.test.ts +1 -1
  147. package/src/identity/__tests__/registry.test.ts +4 -0
  148. package/src/identity/registration.ts +3 -3
  149. package/src/identity/registry.ts +2 -0
  150. package/src/keys/__tests__/keys.test.ts +4 -0
  151. package/src/keys/credential.ts +1 -0
  152. package/src/lib/__tests__/admin-target.test.ts +12 -0
  153. package/src/lib/__tests__/binary.test.ts +16 -1
  154. package/src/lib/__tests__/browser-retention.test.ts +212 -0
  155. package/src/lib/__tests__/command-dx.test.ts +1 -1
  156. package/src/lib/__tests__/config.test.ts +27 -0
  157. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  158. package/src/lib/__tests__/idp.test.ts +34 -4
  159. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  160. package/src/lib/__tests__/instance-target.test.ts +7 -39
  161. package/src/lib/__tests__/log-errors.test.ts +64 -0
  162. package/src/lib/__tests__/skills.test.ts +809 -0
  163. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  164. package/src/lib/__tests__/update.test.ts +264 -11
  165. package/src/lib/__tests__/view-assets.test.ts +25 -0
  166. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  167. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  168. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  169. package/src/lib/__tests__/view-server.test.ts +26 -3
  170. package/src/lib/__tests__/view-session.test.ts +2 -1
  171. package/src/lib/admin-instance.ts +14 -8
  172. package/src/lib/admin-target.ts +21 -2
  173. package/src/lib/binary.ts +22 -5
  174. package/src/lib/browser-retention.ts +210 -0
  175. package/src/lib/command-dx.ts +9 -9
  176. package/src/lib/config.ts +12 -1
  177. package/src/lib/domain-publication.ts +9 -7
  178. package/src/lib/failure-debug.ts +26 -0
  179. package/src/lib/idp.ts +34 -3
  180. package/src/lib/instance-candidates.ts +2 -2
  181. package/src/lib/instance-target.ts +3 -14
  182. package/src/lib/instance.ts +16 -11
  183. package/src/lib/log.ts +16 -9
  184. package/src/lib/output.ts +1 -1
  185. package/src/lib/proc.ts +7 -2
  186. package/src/lib/provision-instance.ts +34 -57
  187. package/src/lib/skills/lock.ts +117 -0
  188. package/src/lib/skills/sync.ts +814 -0
  189. package/src/lib/skills.ts +1 -52
  190. package/src/lib/update.ts +298 -45
  191. package/src/lib/validation.ts +2 -2
  192. package/src/lib/view/assets.ts +11 -2
  193. package/src/lib/view/external-open-intent.ts +41 -0
  194. package/src/lib/view/external-open-origins.ts +37 -0
  195. package/src/lib/view/host-capabilities.ts +17 -0
  196. package/src/lib/view/resolve.ts +1 -1
  197. package/src/lib/view/server.ts +18 -3
  198. package/src/lib/view/session.ts +2 -0
  199. package/src/program/.spec/api.d.ts +1 -0
  200. package/src/program/__tests__/program.test.ts +163 -6
  201. package/src/program/argv.ts +14 -0
  202. package/src/program/build.ts +26 -4
  203. package/src/program/command.ts +1 -0
  204. package/src/program/index.ts +1 -0
  205. package/src/program/registry.ts +2 -1
  206. package/src/setup/__tests__/instance-step.test.ts +27 -10
  207. package/src/setup/steps/instance.ts +22 -15
  208. package/src/setup/steps/skills.ts +40 -38
  209. package/src/state/.spec/api.d.ts +21 -2
  210. package/src/state/.spec/architecture.md +18 -4
  211. package/src/state/.spec/layout.ts +1 -0
  212. package/src/state/__tests__/exchange-credentials.test.ts +88 -42
  213. package/src/state/__tests__/files.test.ts +24 -1
  214. package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
  215. package/src/state/__tests__/paths.test.ts +1 -0
  216. package/src/state/__tests__/session-routes.test.ts +138 -0
  217. package/src/state/exchange-credentials.ts +22 -9
  218. package/src/state/files.ts +41 -0
  219. package/src/state/index.ts +3 -1
  220. package/src/state/paths.ts +3 -0
  221. package/src/state/session-routes.ts +34 -0
  222. package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
  223. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  224. package/src/telemetry/__tests__/retention.test.ts +267 -0
  225. package/src/telemetry/__tests__/settings.test.ts +102 -0
  226. package/src/telemetry/__tests__/store-scan.test.ts +128 -0
  227. package/src/telemetry/__tests__/trigger.test.ts +33 -4
  228. package/src/telemetry/analyze.ts +24 -2
  229. package/src/telemetry/recorder.ts +15 -4
  230. package/src/telemetry/retention.ts +176 -0
  231. package/src/telemetry/session.ts +18 -12
  232. package/src/telemetry/settings.ts +64 -11
  233. package/src/telemetry/store.ts +92 -9
  234. package/src/telemetry/trigger.ts +16 -28
  235. package/src/ui/.spec/api.d.ts +14 -0
  236. package/src/ui/.spec/architecture.md +14 -0
  237. package/src/ui/.spec/laws.ts +50 -0
  238. package/src/ui/.spec/layout.ts +16 -0
  239. package/src/ui/__tests__/ui.test.ts +1742 -0
  240. package/src/ui/index.ts +13 -0
  241. package/src/ui/lock.ts +89 -0
  242. package/src/ui/model.ts +83 -0
  243. package/src/ui/operations.ts +1044 -0
  244. package/src/ui/project.ts +333 -0
  245. package/src/ui/release.ts +398 -0
  246. package/src/ui/runner.ts +18 -0
  247. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  248. package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
  249. package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
  250. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  251. package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
  252. package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
  253. package/studio/client/dist/index.html +4 -3
  254. package/studio/package.json +8 -14
  255. package/studio/server/agent/ask.test.ts +1 -1
  256. package/studio/server/agent/bridge/grant.test.ts +1 -1
  257. package/studio/server/agent/bridge/routes.test.ts +1 -1
  258. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  259. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  260. package/studio/server/agent/harness/gateway/token.ts +3 -3
  261. package/studio/server/agent/prompts/anchors.test.ts +41 -32
  262. package/studio/server/agent/prompts/anchors.ts +23 -51
  263. package/studio/server/agent/prompts/system.test.ts +4 -4
  264. package/studio/server/agent/prompts/system.ts +5 -5
  265. package/studio/server/agent/routes.test.ts +1 -1
  266. package/studio/server/agent/run/coordinator.test.ts +1 -1
  267. package/studio/server/agent/run/preparation.ts +1 -1
  268. package/studio/server/api/canvas.ts +0 -1
  269. package/studio/server/api/context.ts +1 -1
  270. package/studio/server/api/deployment.ts +1 -1
  271. package/studio/server/api/views.ts +2 -2
  272. package/studio/server/api/workspace.ts +1 -1
  273. package/studio/server/api-agent-loadout.test.ts +1 -1
  274. package/studio/server/api.test.ts +1 -1
  275. package/studio/server/cache.test.ts +24 -18
  276. package/studio/server/cache.ts +10 -54
  277. package/studio/server/cli-consumers.test.ts +31 -3
  278. package/studio/server/domain.test.ts +50 -68
  279. package/studio/server/domain.ts +82 -63
  280. package/studio/server/handoff/copy.ts +2 -2
  281. package/studio/server/index.ts +1 -1
  282. package/studio/server/instances/probe.test.ts +1 -1
  283. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  284. package/studio/server/introspect/anatomy/views.ts +10 -16
  285. package/studio/server/introspect/anatomy-extras.test.ts +29 -56
  286. package/studio/server/introspect/anatomy.test.ts +22 -14
  287. package/studio/server/introspect/anatomy.ts +6 -9
  288. package/studio/server/introspect/bundle.ts +0 -3
  289. package/studio/server/introspect/canonical-schema.test.ts +155 -424
  290. package/studio/server/introspect/canonical-schema.ts +248 -601
  291. package/studio/server/introspect/core.ts +14 -22
  292. package/studio/server/introspect/diff.test.ts +21 -9
  293. package/studio/server/introspect/diff.ts +19 -64
  294. package/studio/server/introspect/extractor.ts +23 -122
  295. package/studio/server/introspect/overlay-tsmorph.test.ts +116 -241
  296. package/studio/server/introspect/overlay-tsmorph.ts +0 -1
  297. package/studio/server/introspect/overlay.ts +3 -51
  298. package/studio/server/introspect/revision.test.ts +24 -28
  299. package/studio/server/introspect/revision.ts +11 -22
  300. package/studio/server/introspect/runtime.test.ts +77 -195
  301. package/studio/server/introspect/runtime.ts +5 -67
  302. package/studio/server/introspect/schema-ir-json.test.ts +81 -0
  303. package/studio/server/introspect/schema-ir-json.ts +39 -69
  304. package/studio/server/introspect/schema-refs.test.ts +43 -88
  305. package/studio/server/introspect/schema-refs.ts +14 -49
  306. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  307. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  308. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  309. package/studio/server/lifecycle.ts +4 -1
  310. package/studio/server/state/baseline.test.ts +14 -9
  311. package/studio/server/state/baseline.ts +3 -5
  312. package/studio/server/state/documents.test.ts +69 -0
  313. package/studio/server/state/documents.ts +57 -10
  314. package/studio/server/state/visibility.ts +1 -5
  315. package/studio/server/views/model.ts +3 -3
  316. package/studio/server/views/target.test.ts +18 -76
  317. package/studio/server/views/target.ts +24 -50
  318. package/studio/server/watch.test.ts +11 -11
  319. package/studio/server/watch.ts +8 -4
  320. package/studio/server/workspace/updates.ts +23 -1
  321. package/studio/server/workspace-watch.ts +2 -2
  322. package/studio/shared/contracts/runtime.ts +4 -0
  323. package/studio/shared/contracts/schema.ts +44 -131
  324. package/studio/shared/contracts/surface.test.ts +15 -17
  325. package/studio/shared/contracts/workspace.ts +5 -11
  326. package/studio/shared/schema/identity.test.ts +20 -44
  327. package/studio/shared/schema/identity.ts +9 -24
  328. package/studio/tsconfig.json +2 -1
  329. package/tsconfig.json +1 -1
  330. package/viewer/dist/index.html +21 -0
  331. package/viewer/dist/main.js +41 -62
  332. package/.check-workspace.cjs +0 -40
  333. package/src/admin/binding.ts +0 -168
  334. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  335. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  336. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  337. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  338. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  339. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  340. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
  341. package/studio/server/introspect/core-extractor.ts +0 -139
  342. package/studio/server/introspect/overlay.test.ts +0 -72
  343. package/studio/server/introspect/source-overlay/annotations.ts +0 -14
@@ -0,0 +1,152 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
+
3
+ import addCommand from '../add'
4
+ import listCommand from '../list'
5
+
6
+ class ExitError extends Error {}
7
+
8
+ const commit = 'a'.repeat(40)
9
+ const item = {
10
+ name: 'pattern-chart-line-basic',
11
+ type: 'registry:block',
12
+ title: 'Line chart',
13
+ description: 'A controlled chart.',
14
+ dependencies: ['@astrale-os/ui@^0.3.0-beta.0'],
15
+ files: [
16
+ {
17
+ path: 'registry/patterns/chart/line-basic.tsx',
18
+ type: 'registry:component',
19
+ target: 'components/astrale/pattern/chart/line-basic.tsx',
20
+ },
21
+ ],
22
+ meta: { canonicalAddress: 'pattern/chart/line/basic' },
23
+ }
24
+
25
+ let stdout = ''
26
+ let stderr = ''
27
+ let originalArgv: string[]
28
+ let originalExit: typeof process.exit
29
+ let originalFetch: typeof globalThis.fetch
30
+ let originalStdout: typeof process.stdout.write
31
+ let originalStderr: typeof process.stderr.write
32
+
33
+ beforeEach(() => {
34
+ stdout = ''
35
+ stderr = ''
36
+ originalArgv = process.argv
37
+ originalExit = process.exit
38
+ originalFetch = globalThis.fetch
39
+ originalStdout = process.stdout.write
40
+ originalStderr = process.stderr.write
41
+ process.stdout.write = ((chunk: string | Uint8Array) => {
42
+ stdout += String(chunk)
43
+ return true
44
+ }) as typeof process.stdout.write
45
+ process.stderr.write = ((chunk: string | Uint8Array) => {
46
+ stderr += String(chunk)
47
+ return true
48
+ }) as typeof process.stderr.write
49
+ process.exit = (() => {
50
+ throw new ExitError()
51
+ }) as typeof process.exit
52
+ globalThis.fetch = mockFetch()
53
+ })
54
+
55
+ afterEach(() => {
56
+ process.argv = originalArgv
57
+ process.exit = originalExit
58
+ globalThis.fetch = originalFetch
59
+ process.stdout.write = originalStdout
60
+ process.stderr.write = originalStderr
61
+ })
62
+
63
+ describe('UI command machine contracts', () => {
64
+ test('list emits exactly one parseable JSON value', async () => {
65
+ const action = listCommand.action as (
66
+ query: string | undefined,
67
+ options: { json?: boolean; limit?: string },
68
+ ) => Promise<void>
69
+ await action('line-basic', { json: true, limit: '100' })
70
+
71
+ expect(stderr).toBe('')
72
+ expect(JSON.parse(stdout)).toEqual([item])
73
+ })
74
+
75
+ test('list forwards an explicit release without consulting the beta channel', async () => {
76
+ const seen: string[] = []
77
+ globalThis.fetch = mockFetch(seen)
78
+ const action = listCommand.action as (
79
+ query: string | undefined,
80
+ options: { json?: boolean; limit?: string; version?: string },
81
+ ) => Promise<void>
82
+
83
+ await action('line-basic', { json: true, limit: '100', version: '0.3.0-beta.1' })
84
+
85
+ expect(JSON.parse(stdout)).toEqual([item])
86
+ expect(seen.some((url) => url.endsWith('/@astrale-os/ui/beta'))).toBe(false)
87
+ expect(seen.some((url) => url.includes('/git/ref/tags/v0.3.0-beta.1'))).toBe(true)
88
+ })
89
+
90
+ test('add rejects missing items without prompting in machine mode', async () => {
91
+ process.argv = ['node', 'astrale', '--no-prompt', 'ui', 'add']
92
+ const add = addCommand.action as (items: string[], options: { json?: boolean }) => Promise<void>
93
+ await expect(add([], { json: true })).rejects.toBeInstanceOf(ExitError)
94
+ expect(JSON.parse(stderr)).toMatchObject({ error: 'UI_ITEM_NOT_FOUND' })
95
+ expect(stdout).toBe('')
96
+ })
97
+
98
+ test('registry failures retain a stable code without leaking transport secrets', async () => {
99
+ globalThis.fetch = (async () => {
100
+ throw new Error('Authorization: Bearer npm_super_secret_value_that_must_not_escape')
101
+ }) as unknown as typeof fetch
102
+ const action = listCommand.action as (
103
+ query: string | undefined,
104
+ options: { json?: boolean; limit?: string },
105
+ ) => Promise<void>
106
+ await expect(action('line-basic', { json: true, limit: '100' })).rejects.toBeInstanceOf(
107
+ ExitError,
108
+ )
109
+ expect(JSON.parse(stderr)).toEqual({
110
+ error: 'UI_REGISTRY_UNAVAILABLE',
111
+ message: 'Unable to reach npm UI release.',
112
+ })
113
+ expect(stderr).not.toContain('super_secret')
114
+ })
115
+ })
116
+
117
+ function mockFetch(seen: string[] = []): typeof fetch {
118
+ return (async (input: string | URL | Request) => {
119
+ const url = String(input)
120
+ seen.push(url)
121
+ if (url.endsWith('/@astrale-os/ui/beta')) return Response.json({ version: '0.3.0-beta.1' })
122
+ if (url.includes('/git/ref/tags/')) {
123
+ return Response.json({ object: { type: 'commit', sha: commit, url: '' } })
124
+ }
125
+ if (url.endsWith('/tooling/compatibility.json')) {
126
+ return Response.json({
127
+ version: 1,
128
+ shadcn: '4.18.0',
129
+ base: 'base',
130
+ style: 'nova',
131
+ baseUi: '1.7.0',
132
+ react: '^18.3.1 || ^19.0.0',
133
+ tailwind: '^4.3.3',
134
+ presets: ['astrale', 'compact', 'expressive'],
135
+ })
136
+ }
137
+ if (url.endsWith('/registry/patterns/chart/registry.json')) {
138
+ return Response.json({
139
+ items: [
140
+ {
141
+ ...item,
142
+ files: item.files.map((file) => ({ ...file, path: 'line-basic.tsx' })),
143
+ },
144
+ ],
145
+ })
146
+ }
147
+ if (url.endsWith('/registry.json')) {
148
+ return Response.json({ include: ['registry/patterns/chart/registry.json'] })
149
+ }
150
+ return new Response('not found', { status: 404 })
151
+ }) as typeof fetch
152
+ }
@@ -0,0 +1,51 @@
1
+ import type { CommandDefinition } from '../../program'
2
+
3
+ import { promptMultiSelect } from '../../lib/prompt'
4
+ import { addUi, listLockedUi, UiError } from '../../ui'
5
+ import { UI_JSON_OPTION, UI_PROJECT_OPTION, runUiCommand, type UiCommandOptions } from './shared'
6
+
7
+ type Options = UiCommandOptions & { dryRun?: boolean; overwrite?: boolean; yes?: boolean }
8
+
9
+ export default {
10
+ name: 'add',
11
+ description: 'Install consumer-owned Astrale pattern, block, or theme source',
12
+ arguments: [
13
+ {
14
+ name: 'items',
15
+ description: 'Canonical registry addresses or a local exported theme CSS file',
16
+ required: false,
17
+ variadic: true,
18
+ },
19
+ ],
20
+ options: [
21
+ UI_PROJECT_OPTION,
22
+ { flags: '--dry-run', description: 'Show exact files and dependencies without writing' },
23
+ { flags: '--overwrite', description: 'Allow replacing locally changed installed source' },
24
+ { flags: '--yes', description: 'Confirm the planned operation non-interactively' },
25
+ UI_JSON_OPTION,
26
+ ],
27
+ afterHelpText:
28
+ '\nInstalled source belongs to the application. Use theme/<name> for a released theme or ./theme.css for a playground export.\nOrdinary add never overwrites local edits. Run astrale ui doctor before intentionally replacing local source with --overwrite --yes.\n',
29
+ action: async (items: string[], options: Options) =>
30
+ runUiCommand(options, async () => {
31
+ let selected = items
32
+ if (selected.length === 0) {
33
+ if (process.argv.includes('--ci') || process.argv.includes('--no-prompt')) {
34
+ throw new UiError('UI_ITEM_NOT_FOUND', 'No UI item was provided in non-interactive mode.')
35
+ }
36
+ const available = await listLockedUi(options.project)
37
+ selected =
38
+ (await promptMultiSelect(
39
+ 'Choose Astrale UI source to install',
40
+ available.map((item) => ({
41
+ name: item.title ?? item.meta.canonicalAddress,
42
+ value: item.meta.canonicalAddress,
43
+ description: item.description,
44
+ })),
45
+ )) ?? []
46
+ if (selected.length === 0)
47
+ throw new UiError('UI_ITEM_NOT_FOUND', 'No UI item was selected.')
48
+ }
49
+ return addUi(selected, options)
50
+ }),
51
+ } satisfies CommandDefinition
@@ -0,0 +1,21 @@
1
+ import type { CommandDefinition } from '../../program'
2
+
3
+ import { doctorUi, UiError } from '../../ui'
4
+ import { UI_JSON_OPTION, UI_PROJECT_OPTION, runUiCommand, type UiCommandOptions } from './shared'
5
+
6
+ export default {
7
+ name: 'doctor',
8
+ description: 'Verify Astrale UI package, CSS, config, lock, and installed source integrity',
9
+ arguments: [{ name: 'path', description: 'Application directory', required: false }],
10
+ options: [UI_PROJECT_OPTION, UI_JSON_OPTION],
11
+ action: async (path: string | undefined, options: UiCommandOptions) =>
12
+ runUiCommand(options, () => {
13
+ if (path && options.project) {
14
+ throw new UiError(
15
+ 'UI_ITEM_CONFLICT',
16
+ 'Choose either the positional application path or --project, not both.',
17
+ )
18
+ }
19
+ return doctorUi(options.project ?? path)
20
+ }),
21
+ } satisfies CommandDefinition
@@ -0,0 +1,38 @@
1
+ import type { CommandDefinition } from '../../program'
2
+
3
+ import { initUi, type UiPreset } from '../../ui'
4
+ import { UI_JSON_OPTION, runUiCommand, type UiCommandOptions } from './shared'
5
+
6
+ type Options = UiCommandOptions & {
7
+ preset?: UiPreset
8
+ version?: string
9
+ dryRun?: boolean
10
+ force?: boolean
11
+ install?: boolean
12
+ }
13
+
14
+ export default {
15
+ name: 'init',
16
+ description: 'Initialize Astrale UI in an existing React and Tailwind v4 application',
17
+ arguments: [{ name: 'path', description: 'Application directory', required: false }],
18
+ options: [
19
+ {
20
+ flags: '--preset <name>',
21
+ description: 'Visual preset',
22
+ choices: ['astrale', 'compact', 'expressive'],
23
+ default: 'astrale',
24
+ },
25
+ { flags: '--version <version>', description: 'Exact @astrale-os/ui version or release tag' },
26
+ { flags: '--dry-run', description: 'Print the complete plan without writing or installing' },
27
+ { flags: '--force', description: 'Replace an existing Astrale UI lock intentionally' },
28
+ {
29
+ flags: '--no-install',
30
+ description: 'Write configuration without running the package manager',
31
+ },
32
+ UI_JSON_OPTION,
33
+ ],
34
+ afterHelpText:
35
+ '\nExamples:\n $ astrale ui init --preset astrale\n $ astrale ui init ./app --dry-run --json\n',
36
+ action: async (path: string | undefined, options: Options) =>
37
+ runUiCommand(options, () => initUi({ path, ...options })),
38
+ } satisfies CommandDefinition
@@ -0,0 +1,26 @@
1
+ import type { CommandDefinition } from '../../program'
2
+
3
+ import { listUi } from '../../ui'
4
+ import { UI_JSON_OPTION, runUiCommand, type UiCommandOptions } from './shared'
5
+
6
+ type Options = UiCommandOptions & { type?: string; limit?: string; version?: string }
7
+
8
+ export default {
9
+ name: 'list',
10
+ description: 'List or search Astrale patterns, blocks, and themes',
11
+ arguments: [{ name: 'query', description: 'Optional search text', required: false }],
12
+ options: [
13
+ {
14
+ flags: '--type <type>',
15
+ description: 'Restrict to pattern, block, or theme',
16
+ choices: ['pattern', 'block', 'theme'],
17
+ },
18
+ { flags: '--limit <n>', description: 'Maximum results', default: '100' },
19
+ { flags: '--version <version>', description: 'Exact release to inspect' },
20
+ UI_JSON_OPTION,
21
+ ],
22
+ action: async (query: string | undefined, options: Options) =>
23
+ runUiCommand(options, () =>
24
+ listUi(query, { type: options.type, limit: Number(options.limit), version: options.version }),
25
+ ),
26
+ } satisfies CommandDefinition
@@ -0,0 +1,19 @@
1
+ import type { CommandDefinition } from '../../program'
2
+
3
+ import { applyPreset, type UiPreset } from '../../ui'
4
+ import { UI_JSON_OPTION, UI_PROJECT_OPTION, runUiCommand, type UiCommandOptions } from './shared'
5
+
6
+ type Options = UiCommandOptions & { dryRun?: boolean }
7
+
8
+ export default {
9
+ name: 'apply',
10
+ description: 'Change the preset CSS import without rewriting component source',
11
+ arguments: [{ name: 'name', description: 'Preset name' }],
12
+ options: [
13
+ UI_PROJECT_OPTION,
14
+ { flags: '--dry-run', description: 'Show the CSS and lock change without writing' },
15
+ UI_JSON_OPTION,
16
+ ],
17
+ action: async (name: UiPreset, options: Options) =>
18
+ runUiCommand(options, () => applyPreset(name, options)),
19
+ } satisfies CommandDefinition
@@ -0,0 +1,12 @@
1
+ import type { CommandDefinition } from '../../program'
2
+
3
+ import { UI_PRESETS } from '../../ui'
4
+ import { UI_JSON_OPTION, runUiCommand, type UiCommandOptions } from './shared'
5
+
6
+ export default {
7
+ name: 'list',
8
+ description: 'List qualified Astrale UI visual presets',
9
+ options: [UI_JSON_OPTION],
10
+ action: async (options: UiCommandOptions) =>
11
+ runUiCommand(options, async () => UI_PRESETS.map((name) => ({ name }))),
12
+ } satisfies CommandDefinition
@@ -0,0 +1,25 @@
1
+ import { fatal } from '../../lib/log'
2
+ import { output } from '../../lib/output'
3
+
4
+ export type UiCommandOptions = { json?: boolean; project?: string }
5
+
6
+ export async function runUiCommand(
7
+ options: UiCommandOptions,
8
+ operation: () => Promise<unknown>,
9
+ ): Promise<void> {
10
+ try {
11
+ output(await operation(), { json: options.json })
12
+ } catch (error) {
13
+ fatal(error, { json: options.json })
14
+ }
15
+ }
16
+
17
+ export const UI_PROJECT_OPTION = {
18
+ flags: '--project <path>',
19
+ description: 'Existing application root (defaults to the current project)',
20
+ } as const
21
+
22
+ export const UI_JSON_OPTION = {
23
+ flags: '--json',
24
+ description: 'Emit one machine-readable JSON value',
25
+ } as const
@@ -11,8 +11,13 @@ import {
11
11
  inDomainProject,
12
12
  type SdkOutdated,
13
13
  } from '../lib/sdk-deps'
14
- import { ASTRALE_CLI_SKILL, detectSkill, installSkills, SKILL_INSTALL_HINT } from '../lib/skills'
15
- import { DEFAULT_UPDATE_CHANNEL, updateAstrale } from '../lib/update'
14
+ import {
15
+ checkAstraleSkills,
16
+ type SkillApplyResult,
17
+ type SkillCheckResult,
18
+ syncAstraleSkills,
19
+ } from '../lib/skills'
20
+ import { DEFAULT_UPDATE_CHANNEL, packageManagedUpdateError, updateAstrale } from '../lib/update'
16
21
 
17
22
  type UpdateOpts = RawOutputOpts & {
18
23
  check?: boolean
@@ -23,26 +28,26 @@ type UpdateOpts = RawOutputOpts & {
23
28
  yes?: boolean
24
29
  }
25
30
 
26
- /**
27
- * Refresh the astrale agent skills alongside the binary. Delegates to the same
28
- * `npx skills add` installer `astrale setup` uses; re-running it updates an
29
- * existing install to the latest published SKILL.md.
30
- *
31
- * Only refreshes skills the user already has — `astrale update` keeps an existing
32
- * install current, it does not foist skills on a non-agent user; fresh installs
33
- * go through `astrale setup`. Best-effort: a failure here never fails the update
34
- * (the binary is already swapped), so we warn and move on.
35
- */
36
- async function refreshSkills(): Promise<void> {
37
- if (!detectSkill(ASTRALE_CLI_SKILL).installed) {
38
- log.dim(` Agent skills not installed — get them with: ${SKILL_INSTALL_HINT}`)
39
- return
40
- }
41
- log.step(`Refreshing the astrale agent skills — ${SKILL_INSTALL_HINT}`)
42
- if (await installSkills()) {
43
- log.success('astrale agent skills up to date')
44
- } else {
45
- log.warn(`Skill refresh did not complete run it later: ${SKILL_INSTALL_HINT}`)
31
+ async function refreshSkills(): Promise<SkillApplyResult> {
32
+ log.step('Ensuring Astrale agent skills are current and healthy')
33
+ const result = await syncAstraleSkills()
34
+ if (result.status === 'unchanged') log.success('Astrale skills already up to date')
35
+ else if (result.status === 'installed') log.success('Astrale skills installed')
36
+ else if (result.status === 'updated') log.success('Astrale skills updated')
37
+ else if (result.status === 'repaired') log.success('Astrale skills repaired and updated')
38
+ return result
39
+ }
40
+
41
+ function skillCheckStale(skills: SkillCheckResult): boolean {
42
+ return skills.status === 'update-available' || skills.status === 'repair-needed'
43
+ }
44
+
45
+ function printSkillCheck(skills: SkillCheckResult): void {
46
+ if (skills.status === 'current') log.success('Astrale skills are up to date')
47
+ else if (skills.status === 'update-available') log.info('Astrale skills update available')
48
+ else if (skills.status === 'repair-needed') log.warn('Astrale skills need repair')
49
+ else if (skills.status === 'unavailable') {
50
+ log.warn(`Could not verify Astrale skills${skills.error ? `: ${skills.error}` : ''}`)
46
51
  }
47
52
  }
48
53
 
@@ -104,14 +109,22 @@ async function refreshSdkDeps(check: boolean, assumeYes = false): Promise<boolea
104
109
  /**
105
110
  * Read-only staleness report for tooling — `astrale update --check --json`.
106
111
  * domain-studio polls this on load to drive its "update available" badge. It is
107
- * unified and NON-THROWING: the CLI axis catches the package-managed / no-metadata
108
- * case (reported as `managed`, never stale), and the SDK axis is already
109
- * best-effort. Skills are intentionally absent they ride along with `astrale
110
- * update`, so a stale CLI or SDK is the only signal worth surfacing.
112
+ * unified and NON-THROWING: an explicit package-managed result uses npm release
113
+ * identity, while script-install failures remain script failures in `error`
114
+ * instead of being recategorized. The SDK axis is already best-effort. Skills
115
+ * report meaningful health/freshness states without exposing installer metadata.
111
116
  */
112
117
  export type StaleReport = {
113
118
  stale: boolean
114
- cli: { stale: boolean; managed: boolean; current?: string; latest?: string; channel?: string }
119
+ cli: {
120
+ stale: boolean
121
+ managed: boolean
122
+ current?: string
123
+ latest?: string
124
+ channel?: string
125
+ error?: string
126
+ }
127
+ skills: SkillCheckResult
115
128
  sdk: { stale: boolean; inProject: boolean; outdated: SdkOutdated[] }
116
129
  }
117
130
 
@@ -138,6 +151,15 @@ export async function cliStale(
138
151
  version: target.version,
139
152
  currentVersion: running,
140
153
  })
154
+ if (r.status === 'managed') {
155
+ const latest = await dependencies.fetchPackageVersion(target).catch(() => undefined)
156
+ return {
157
+ stale: latest !== undefined && latest !== running,
158
+ managed: true,
159
+ current: running,
160
+ ...(latest === undefined ? {} : { latest, channel: 'npm' }),
161
+ }
162
+ }
141
163
  if (r.status === 'updated') {
142
164
  return {
143
165
  stale: false,
@@ -154,13 +176,12 @@ export async function cliStale(
154
176
  latest: r.latestVersion,
155
177
  channel: r.channel,
156
178
  }
157
- } catch {
158
- const latest = await dependencies.fetchPackageVersion(target).catch(() => undefined)
179
+ } catch (error) {
159
180
  return {
160
- stale: latest !== undefined && latest !== running,
161
- managed: true,
181
+ stale: false,
182
+ managed: false,
162
183
  current: running,
163
- ...(latest === undefined ? {} : { latest, channel: 'npm' }),
184
+ error: error instanceof Error ? error.message : String(error),
164
185
  }
165
186
  }
166
187
  }
@@ -202,7 +223,7 @@ export default {
202
223
  default: DEFAULT_UPDATE_CHANNEL,
203
224
  },
204
225
  { flags: '--version <version>', description: 'Update to an exact version tag' },
205
- { flags: '--no-skills', description: 'Skip refreshing the astrale agent skills' },
226
+ { flags: '--no-skills', description: 'Skip ensuring the Astrale agent skills' },
206
227
  { flags: '--no-deps', description: 'Skip checking @astrale-os SDK dependency versions' },
207
228
  {
208
229
  flags: '--yes',
@@ -215,23 +236,22 @@ Behavior:
215
236
  Keeps three things current, in order. (1) The CLI binary: updates official
216
237
  script installs only — if Astrale was installed by another package manager this
217
238
  command refuses so that manager stays in charge; downloads are checksum-verified
218
- before the binary is replaced. (2) The agent skills: if the astrale skills (cli +
219
- domain) are already installed, refreshes them to the latest by delegating to
220
- "npx skills add astrale-os/cli -g" the same installer "astrale setup" uses;
221
- fresh installs go through "astrale setup". (3) SDK deps: inside a pnpm domain
239
+ before the binary is replaced. (2) The Astrale agent skills: installs every
240
+ top-level skill published from astrale-os/cli main, updates healthy older
241
+ installs, repairs inconsistent installs, and verifies the result before
242
+ reporting success. (3) SDK deps: inside a pnpm domain
222
243
  project, proposes any @astrale-os/* dependency with a newer release and, on
223
244
  confirm, runs "pnpm update --latest --lockfile-only" (updates package.json AND
224
245
  the lockfile, honoring your registry + supply-chain age policy; run "pnpm
225
246
  install" to materialize).
226
247
 
227
248
  The default release channel is beta; --channel overrides it for one run.
228
- --check is a dry run (binary + SDK deps; exit 10 if anything is available) and
249
+ --check is a dry run (binary + skills + SDK deps; exit 10 if anything is available) and
229
250
  never writes. With --json it emits a unified staleness report
230
- ({ stale, cli, sdk }) for tooling non-throwing, skills omitted (they ride
231
- along with an update). --yes applies all three non-interactively (no prompts) and
251
+ ({ stale, cli, skills, sdk }) for tooling. --yes applies all three non-interactively and
232
252
  is resilient — a binary that can't self-update (package-managed) warns but never
233
- blocks the skills/deps steps; this is what domain-studio's "Update now" runs.
234
- --no-skills / --no-deps skip those steps in a real run.
253
+ blocks the skills/deps steps; a skill failure fails the command rather than
254
+ claiming a partial success. --no-skills / --no-deps explicitly skip those axes.
235
255
 
236
256
  Examples:
237
257
  $ astrale update
@@ -246,11 +266,18 @@ Examples:
246
266
  try {
247
267
  // Tooling path: a machine-readable `--check` (e.g. domain-studio's update
248
268
  // badge polling `astrale update --check --json`) gets a unified,
249
- // non-throwing staleness report — CLI + SDK only — and exits.
269
+ // non-throwing staleness report and exits.
250
270
  if (opts.check && isMachine(opts)) {
251
271
  const cli = await cliStale(opts)
272
+ const skills: SkillCheckResult =
273
+ opts.skills === false ? { status: 'skipped' } : await checkAstraleSkills()
252
274
  const sdk = await sdkStale()
253
- const report: StaleReport = { stale: cli.stale || sdk.stale, cli, sdk }
275
+ const report: StaleReport = {
276
+ stale: cli.stale || skillCheckStale(skills) || sdk.stale,
277
+ cli,
278
+ skills,
279
+ sdk,
280
+ }
254
281
  output(report, opts)
255
282
  if (report.stale) process.exitCode = 10
256
283
  return
@@ -294,11 +321,25 @@ Examples:
294
321
  log.success(`Updated astrale ${result.previousVersion} -> ${result.currentVersion}`)
295
322
  log.dim(` channel: ${result.channel}`)
296
323
  log.dim(` binary: ${result.bin}`)
324
+ } else if (result?.status === 'managed') {
325
+ const error = packageManagedUpdateError(result.executable)
326
+ if (!opts.yes) throw error
327
+ log.warn(`CLI self-update skipped: ${error.message}`)
297
328
  }
298
329
 
299
- // Axis B — agent skills. Keep an existing install current with the CLI.
300
- // Skip on --check (a dry run) and when the user opted out with --no-skills.
301
- if (!opts.check && opts.skills !== false) await refreshSkills()
330
+ // Axis B — agent skills. A successful real update guarantees a verified
331
+ // latest cohort; --check remains read-only and reports its status.
332
+ if (opts.skills !== false) {
333
+ if (opts.check) {
334
+ const skills = await checkAstraleSkills()
335
+ printSkillCheck(skills)
336
+ if (skillCheckStale(skills)) anyAvailable = true
337
+ } else {
338
+ await refreshSkills()
339
+ }
340
+ } else if (!opts.check) {
341
+ log.dim(' Astrale skills skipped (--no-skills)')
342
+ }
302
343
 
303
344
  // Axis C — first-party @astrale-os/* deps in the current domain project.
304
345
  // Runs on --check too (reports availability); --yes applies without a prompt.
@@ -308,7 +349,7 @@ Examples:
308
349
 
309
350
  if (opts.check && anyAvailable) process.exitCode = 10
310
351
  } catch (e) {
311
- fatal(e)
352
+ fatal(e, opts)
312
353
  }
313
354
  },
314
355
  } satisfies CommandDefinition