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

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 (345) hide show
  1. package/README.md +84 -6
  2. package/dist/astrale.js +57586 -60753
  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 +119 -11
  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 +39 -36
  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 +90 -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 +13 -6
  138. package/src/graph/.spec/laws/documents.ts +7 -7
  139. package/src/graph/.spec/layout.ts +9 -1
  140. package/src/graph/__tests__/mutation.test.ts +2 -2
  141. package/src/graph/__tests__/query.test.ts +35 -9
  142. package/src/graph/class.ts +30 -0
  143. package/src/graph/index.ts +1 -0
  144. package/src/graph/query.ts +27 -40
  145. package/src/identity/.spec/api.d.ts +2 -2
  146. package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
  147. package/src/identity/__tests__/registration.test.ts +1 -1
  148. package/src/identity/__tests__/registry.test.ts +4 -0
  149. package/src/identity/registration.ts +3 -3
  150. package/src/identity/registry.ts +2 -0
  151. package/src/keys/__tests__/keys.test.ts +4 -0
  152. package/src/keys/credential.ts +1 -0
  153. package/src/lib/__tests__/admin-target.test.ts +12 -0
  154. package/src/lib/__tests__/binary.test.ts +16 -1
  155. package/src/lib/__tests__/browser-retention.test.ts +212 -0
  156. package/src/lib/__tests__/command-dx.test.ts +1 -1
  157. package/src/lib/__tests__/config.test.ts +27 -0
  158. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  159. package/src/lib/__tests__/idp.test.ts +34 -4
  160. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  161. package/src/lib/__tests__/instance-target.test.ts +7 -39
  162. package/src/lib/__tests__/log-errors.test.ts +64 -0
  163. package/src/lib/__tests__/skills.test.ts +834 -0
  164. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  165. package/src/lib/__tests__/update.test.ts +264 -11
  166. package/src/lib/__tests__/view-assets.test.ts +25 -0
  167. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  168. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  169. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  170. package/src/lib/__tests__/view-server.test.ts +26 -3
  171. package/src/lib/__tests__/view-session.test.ts +2 -1
  172. package/src/lib/admin-instance.ts +14 -8
  173. package/src/lib/admin-target.ts +21 -2
  174. package/src/lib/binary.ts +22 -5
  175. package/src/lib/browser-retention.ts +210 -0
  176. package/src/lib/command-dx.ts +9 -9
  177. package/src/lib/config.ts +12 -1
  178. package/src/lib/domain-publication.ts +9 -7
  179. package/src/lib/failure-debug.ts +26 -0
  180. package/src/lib/idp.ts +34 -3
  181. package/src/lib/instance-candidates.ts +2 -2
  182. package/src/lib/instance-target.ts +3 -14
  183. package/src/lib/instance.ts +16 -11
  184. package/src/lib/log.ts +16 -9
  185. package/src/lib/output.ts +1 -1
  186. package/src/lib/proc.ts +7 -2
  187. package/src/lib/provision-instance.ts +34 -57
  188. package/src/lib/skills/lock.ts +117 -0
  189. package/src/lib/skills/sync.ts +878 -0
  190. package/src/lib/skills.ts +1 -52
  191. package/src/lib/update.ts +298 -45
  192. package/src/lib/validation.ts +2 -2
  193. package/src/lib/view/assets.ts +11 -2
  194. package/src/lib/view/external-open-intent.ts +41 -0
  195. package/src/lib/view/external-open-origins.ts +37 -0
  196. package/src/lib/view/host-capabilities.ts +17 -0
  197. package/src/lib/view/resolve.ts +1 -1
  198. package/src/lib/view/server.ts +18 -3
  199. package/src/lib/view/session.ts +2 -0
  200. package/src/program/.spec/api.d.ts +1 -0
  201. package/src/program/__tests__/program.test.ts +201 -7
  202. package/src/program/argv.ts +14 -0
  203. package/src/program/build.ts +26 -4
  204. package/src/program/command.ts +1 -0
  205. package/src/program/index.ts +1 -0
  206. package/src/program/registry.ts +2 -1
  207. package/src/setup/__tests__/instance-step.test.ts +27 -10
  208. package/src/setup/steps/instance.ts +22 -15
  209. package/src/setup/steps/skills.ts +40 -38
  210. package/src/state/.spec/api.d.ts +21 -2
  211. package/src/state/.spec/architecture.md +18 -4
  212. package/src/state/.spec/layout.ts +1 -0
  213. package/src/state/__tests__/exchange-credentials.test.ts +88 -42
  214. package/src/state/__tests__/files.test.ts +24 -1
  215. package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
  216. package/src/state/__tests__/paths.test.ts +1 -0
  217. package/src/state/__tests__/session-routes.test.ts +138 -0
  218. package/src/state/exchange-credentials.ts +22 -9
  219. package/src/state/files.ts +41 -0
  220. package/src/state/index.ts +3 -1
  221. package/src/state/paths.ts +3 -0
  222. package/src/state/session-routes.ts +34 -0
  223. package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
  224. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  225. package/src/telemetry/__tests__/retention.test.ts +267 -0
  226. package/src/telemetry/__tests__/settings.test.ts +102 -0
  227. package/src/telemetry/__tests__/store-scan.test.ts +128 -0
  228. package/src/telemetry/__tests__/trigger.test.ts +33 -4
  229. package/src/telemetry/analyze.ts +24 -2
  230. package/src/telemetry/recorder.ts +15 -4
  231. package/src/telemetry/retention.ts +176 -0
  232. package/src/telemetry/session.ts +18 -12
  233. package/src/telemetry/settings.ts +64 -11
  234. package/src/telemetry/store.ts +92 -9
  235. package/src/telemetry/trigger.ts +16 -28
  236. package/src/ui/.spec/api.d.ts +14 -0
  237. package/src/ui/.spec/architecture.md +14 -0
  238. package/src/ui/.spec/laws.ts +50 -0
  239. package/src/ui/.spec/layout.ts +16 -0
  240. package/src/ui/__tests__/ui.test.ts +1742 -0
  241. package/src/ui/index.ts +13 -0
  242. package/src/ui/lock.ts +89 -0
  243. package/src/ui/model.ts +83 -0
  244. package/src/ui/operations.ts +1044 -0
  245. package/src/ui/project.ts +333 -0
  246. package/src/ui/release.ts +398 -0
  247. package/src/ui/runner.ts +18 -0
  248. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  249. package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
  250. package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
  251. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  252. package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
  253. package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
  254. package/studio/client/dist/index.html +4 -3
  255. package/studio/package.json +8 -14
  256. package/studio/server/agent/ask.test.ts +1 -1
  257. package/studio/server/agent/bridge/grant.test.ts +1 -1
  258. package/studio/server/agent/bridge/routes.test.ts +1 -1
  259. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  260. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  261. package/studio/server/agent/harness/gateway/token.ts +3 -3
  262. package/studio/server/agent/prompts/anchors.test.ts +41 -32
  263. package/studio/server/agent/prompts/anchors.ts +23 -51
  264. package/studio/server/agent/prompts/system.test.ts +4 -4
  265. package/studio/server/agent/prompts/system.ts +5 -5
  266. package/studio/server/agent/routes.test.ts +1 -1
  267. package/studio/server/agent/run/coordinator.test.ts +1 -1
  268. package/studio/server/agent/run/preparation.ts +1 -1
  269. package/studio/server/api/canvas.ts +0 -1
  270. package/studio/server/api/context.ts +1 -1
  271. package/studio/server/api/deployment.ts +1 -1
  272. package/studio/server/api/views.ts +2 -2
  273. package/studio/server/api/workspace.ts +1 -1
  274. package/studio/server/api-agent-loadout.test.ts +1 -1
  275. package/studio/server/api.test.ts +1 -1
  276. package/studio/server/cache.test.ts +24 -18
  277. package/studio/server/cache.ts +10 -54
  278. package/studio/server/cli-consumers.test.ts +32 -4
  279. package/studio/server/cli.test.ts +2 -2
  280. package/studio/server/domain.test.ts +50 -68
  281. package/studio/server/domain.ts +82 -63
  282. package/studio/server/handoff/copy.ts +2 -2
  283. package/studio/server/index.ts +1 -1
  284. package/studio/server/instances/probe.test.ts +1 -1
  285. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  286. package/studio/server/introspect/anatomy/views.ts +10 -16
  287. package/studio/server/introspect/anatomy-extras.test.ts +29 -56
  288. package/studio/server/introspect/anatomy.test.ts +22 -14
  289. package/studio/server/introspect/anatomy.ts +6 -9
  290. package/studio/server/introspect/bundle.ts +0 -3
  291. package/studio/server/introspect/canonical-schema.test.ts +155 -424
  292. package/studio/server/introspect/canonical-schema.ts +248 -601
  293. package/studio/server/introspect/core.ts +14 -22
  294. package/studio/server/introspect/diff.test.ts +21 -9
  295. package/studio/server/introspect/diff.ts +19 -64
  296. package/studio/server/introspect/extractor.ts +23 -122
  297. package/studio/server/introspect/overlay-tsmorph.test.ts +116 -241
  298. package/studio/server/introspect/overlay-tsmorph.ts +0 -1
  299. package/studio/server/introspect/overlay.ts +3 -51
  300. package/studio/server/introspect/revision.test.ts +24 -28
  301. package/studio/server/introspect/revision.ts +11 -22
  302. package/studio/server/introspect/runtime.test.ts +77 -195
  303. package/studio/server/introspect/runtime.ts +5 -67
  304. package/studio/server/introspect/schema-ir-json.test.ts +81 -0
  305. package/studio/server/introspect/schema-ir-json.ts +39 -69
  306. package/studio/server/introspect/schema-refs.test.ts +43 -88
  307. package/studio/server/introspect/schema-refs.ts +14 -49
  308. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  309. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  310. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  311. package/studio/server/lifecycle.ts +4 -1
  312. package/studio/server/state/baseline.test.ts +14 -9
  313. package/studio/server/state/baseline.ts +3 -5
  314. package/studio/server/state/documents.test.ts +69 -0
  315. package/studio/server/state/documents.ts +57 -10
  316. package/studio/server/state/visibility.ts +1 -5
  317. package/studio/server/views/model.ts +3 -3
  318. package/studio/server/views/target.test.ts +18 -76
  319. package/studio/server/views/target.ts +25 -51
  320. package/studio/server/watch.test.ts +11 -11
  321. package/studio/server/watch.ts +8 -4
  322. package/studio/server/workspace/updates.ts +23 -1
  323. package/studio/server/workspace-watch.ts +2 -2
  324. package/studio/shared/contracts/runtime.ts +4 -0
  325. package/studio/shared/contracts/schema.ts +44 -131
  326. package/studio/shared/contracts/surface.test.ts +15 -17
  327. package/studio/shared/contracts/workspace.ts +5 -11
  328. package/studio/shared/schema/identity.test.ts +20 -44
  329. package/studio/shared/schema/identity.ts +9 -24
  330. package/studio/tsconfig.json +2 -1
  331. package/tsconfig.json +1 -1
  332. package/viewer/dist/index.html +21 -0
  333. package/viewer/dist/main.js +41 -62
  334. package/.check-workspace.cjs +0 -40
  335. package/src/admin/binding.ts +0 -168
  336. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  337. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  338. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  339. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  340. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  341. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  342. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
  343. package/studio/server/introspect/core-extractor.ts +0 -139
  344. package/studio/server/introspect/overlay.test.ts +0 -72
  345. 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,23 @@ 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. A current cohort also exposes its exact
116
+ * source revision, skill trees, and entrypoints without leaking installer-local paths.
111
117
  */
112
118
  export type StaleReport = {
113
119
  stale: boolean
114
- cli: { stale: boolean; managed: boolean; current?: string; latest?: string; channel?: string }
120
+ cli: {
121
+ stale: boolean
122
+ managed: boolean
123
+ current?: string
124
+ latest?: string
125
+ channel?: string
126
+ error?: string
127
+ }
128
+ skills: SkillCheckResult
115
129
  sdk: { stale: boolean; inProject: boolean; outdated: SdkOutdated[] }
116
130
  }
117
131
 
@@ -138,6 +152,15 @@ export async function cliStale(
138
152
  version: target.version,
139
153
  currentVersion: running,
140
154
  })
155
+ if (r.status === 'managed') {
156
+ const latest = await dependencies.fetchPackageVersion(target).catch(() => undefined)
157
+ return {
158
+ stale: latest !== undefined && latest !== running,
159
+ managed: true,
160
+ current: running,
161
+ ...(latest === undefined ? {} : { latest, channel: 'npm' }),
162
+ }
163
+ }
141
164
  if (r.status === 'updated') {
142
165
  return {
143
166
  stale: false,
@@ -154,13 +177,12 @@ export async function cliStale(
154
177
  latest: r.latestVersion,
155
178
  channel: r.channel,
156
179
  }
157
- } catch {
158
- const latest = await dependencies.fetchPackageVersion(target).catch(() => undefined)
180
+ } catch (error) {
159
181
  return {
160
- stale: latest !== undefined && latest !== running,
161
- managed: true,
182
+ stale: false,
183
+ managed: false,
162
184
  current: running,
163
- ...(latest === undefined ? {} : { latest, channel: 'npm' }),
185
+ error: error instanceof Error ? error.message : String(error),
164
186
  }
165
187
  }
166
188
  }
@@ -202,7 +224,7 @@ export default {
202
224
  default: DEFAULT_UPDATE_CHANNEL,
203
225
  },
204
226
  { flags: '--version <version>', description: 'Update to an exact version tag' },
205
- { flags: '--no-skills', description: 'Skip refreshing the astrale agent skills' },
227
+ { flags: '--no-skills', description: 'Skip ensuring the Astrale agent skills' },
206
228
  { flags: '--no-deps', description: 'Skip checking @astrale-os SDK dependency versions' },
207
229
  {
208
230
  flags: '--yes',
@@ -215,23 +237,22 @@ Behavior:
215
237
  Keeps three things current, in order. (1) The CLI binary: updates official
216
238
  script installs only — if Astrale was installed by another package manager this
217
239
  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
240
+ before the binary is replaced. (2) The Astrale agent skills: installs every
241
+ top-level skill published from one resolved astrale-os/cli main commit, updates healthy older
242
+ installs, repairs inconsistent installs, and verifies the result before
243
+ reporting success. (3) SDK deps: inside a pnpm domain
222
244
  project, proposes any @astrale-os/* dependency with a newer release and, on
223
245
  confirm, runs "pnpm update --latest --lockfile-only" (updates package.json AND
224
246
  the lockfile, honoring your registry + supply-chain age policy; run "pnpm
225
247
  install" to materialize).
226
248
 
227
249
  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
250
+ --check is a dry run (binary + skills + SDK deps; exit 10 if anything is available) and
229
251
  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
252
+ ({ stale, cli, skills, sdk }) for tooling. --yes applies all three non-interactively and
232
253
  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.
254
+ blocks the skills/deps steps; a skill failure fails the command rather than
255
+ claiming a partial success. --no-skills / --no-deps explicitly skip those axes.
235
256
 
236
257
  Examples:
237
258
  $ astrale update
@@ -246,11 +267,18 @@ Examples:
246
267
  try {
247
268
  // Tooling path: a machine-readable `--check` (e.g. domain-studio's update
248
269
  // badge polling `astrale update --check --json`) gets a unified,
249
- // non-throwing staleness report — CLI + SDK only — and exits.
270
+ // non-throwing staleness report and exits.
250
271
  if (opts.check && isMachine(opts)) {
251
272
  const cli = await cliStale(opts)
273
+ const skills: SkillCheckResult =
274
+ opts.skills === false ? { status: 'skipped' } : await checkAstraleSkills()
252
275
  const sdk = await sdkStale()
253
- const report: StaleReport = { stale: cli.stale || sdk.stale, cli, sdk }
276
+ const report: StaleReport = {
277
+ stale: cli.stale || skillCheckStale(skills) || sdk.stale,
278
+ cli,
279
+ skills,
280
+ sdk,
281
+ }
254
282
  output(report, opts)
255
283
  if (report.stale) process.exitCode = 10
256
284
  return
@@ -294,11 +322,25 @@ Examples:
294
322
  log.success(`Updated astrale ${result.previousVersion} -> ${result.currentVersion}`)
295
323
  log.dim(` channel: ${result.channel}`)
296
324
  log.dim(` binary: ${result.bin}`)
325
+ } else if (result?.status === 'managed') {
326
+ const error = packageManagedUpdateError(result.executable)
327
+ if (!opts.yes) throw error
328
+ log.warn(`CLI self-update skipped: ${error.message}`)
297
329
  }
298
330
 
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()
331
+ // Axis B — agent skills. A successful real update guarantees a verified
332
+ // latest cohort; --check remains read-only and reports its status.
333
+ if (opts.skills !== false) {
334
+ if (opts.check) {
335
+ const skills = await checkAstraleSkills()
336
+ printSkillCheck(skills)
337
+ if (skillCheckStale(skills)) anyAvailable = true
338
+ } else {
339
+ await refreshSkills()
340
+ }
341
+ } else if (!opts.check) {
342
+ log.dim(' Astrale skills skipped (--no-skills)')
343
+ }
302
344
 
303
345
  // Axis C — first-party @astrale-os/* deps in the current domain project.
304
346
  // Runs on --check too (reports availability); --yes applies without a prompt.
@@ -308,7 +350,7 @@ Examples:
308
350
 
309
351
  if (opts.check && anyAvailable) process.exitCode = 10
310
352
  } catch (e) {
311
- fatal(e)
353
+ fatal(e, opts)
312
354
  }
313
355
  },
314
356
  } satisfies CommandDefinition