@astrale-os/cli 1.0.0-beta.2 → 1.0.0-beta.20

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 (282) hide show
  1. package/README.md +16 -1
  2. package/dist/astrale.js +51105 -58057
  3. package/dist/public/connect-core.js +2085 -30702
  4. package/dist/public/keys/index.js +884 -29476
  5. package/dist/public/paths/index.js +13101 -41452
  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 +7 -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/package.json +15 -20
  40. package/src/__tests__/fixtures/publication.ts +20 -0
  41. package/src/admin/.spec/architecture.md +12 -5
  42. package/src/admin/__tests__/fixture.ts +25 -0
  43. package/src/admin/catalog/.spec/api.d.ts +1 -3
  44. package/src/admin/catalog/.spec/architecture.md +5 -4
  45. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  46. package/src/admin/catalog/client.ts +40 -54
  47. package/src/admin/catalog/model.ts +3 -4
  48. package/src/admin/contract.ts +46 -0
  49. package/src/admin/graph/.spec/api.d.ts +4 -10
  50. package/src/admin/graph/nodes.ts +1 -1
  51. package/src/admin/instance/.spec/api.d.ts +5 -9
  52. package/src/admin/instance/.spec/architecture.md +7 -7
  53. package/src/admin/instance/__tests__/client.test.ts +157 -175
  54. package/src/admin/instance/client.ts +39 -128
  55. package/src/admin/instance/index.ts +1 -2
  56. package/src/admin/instance/model.ts +12 -18
  57. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  58. package/src/commands/__tests__/call-describe.test.ts +46 -33
  59. package/src/commands/__tests__/call.test.ts +34 -0
  60. package/src/commands/__tests__/domain-install-operation.test.ts +7 -22
  61. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  62. package/src/commands/__tests__/domain-list.test.ts +5 -25
  63. package/src/commands/__tests__/install-direct.test.ts +115 -8
  64. package/src/commands/__tests__/install-identity-override.test.ts +3 -14
  65. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  66. package/src/commands/__tests__/instance-status.test.ts +139 -0
  67. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  68. package/src/commands/__tests__/logs.test.ts +250 -2
  69. package/src/commands/__tests__/read-commands.test.ts +62 -10
  70. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  71. package/src/commands/__tests__/update.test.ts +25 -3
  72. package/src/commands/__tests__/view.test.ts +49 -0
  73. package/src/commands/call-describe.ts +60 -68
  74. package/src/commands/call.ts +71 -59
  75. package/src/commands/domain/install.ts +54 -35
  76. package/src/commands/domain/list.ts +1 -1
  77. package/src/commands/domain/publish.ts +14 -11
  78. package/src/commands/domain/uninstall.ts +2 -2
  79. package/src/commands/get.ts +14 -15
  80. package/src/commands/identity/__tests__/register.test.ts +3 -3
  81. package/src/commands/identity/register.ts +5 -4
  82. package/src/commands/instance/create.ts +8 -16
  83. package/src/commands/instance/delete.ts +12 -9
  84. package/src/commands/instance/list.ts +2 -2
  85. package/src/commands/instance/status.ts +66 -20
  86. package/src/commands/instance/use.ts +16 -12
  87. package/src/commands/introspect.ts +48 -35
  88. package/src/commands/logs.ts +123 -33
  89. package/src/commands/mutate.ts +6 -5
  90. package/src/commands/query.ts +35 -32
  91. package/src/commands/token.ts +42 -27
  92. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  93. package/src/commands/ui/add.ts +51 -0
  94. package/src/commands/ui/doctor.ts +13 -0
  95. package/src/commands/ui/init.ts +38 -0
  96. package/src/commands/ui/list.ts +26 -0
  97. package/src/commands/ui/preset-apply.ts +19 -0
  98. package/src/commands/ui/preset-list.ts +12 -0
  99. package/src/commands/ui/shared.ts +25 -0
  100. package/src/commands/update.ts +31 -11
  101. package/src/commands/view.ts +82 -37
  102. package/src/connection/.spec/api.d.ts +7 -8
  103. package/src/connection/.spec/architecture.md +5 -4
  104. package/src/connection/.spec/flows/session.ts +2 -2
  105. package/src/connection/.spec/laws/connection.ts +6 -2
  106. package/src/connection/.spec/layout.ts +1 -0
  107. package/src/connection/__tests__/credential.test.ts +20 -1
  108. package/src/connection/__tests__/errors.test.ts +27 -20
  109. package/src/connection/__tests__/exchange.test.ts +30 -1
  110. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  111. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  112. package/src/connection/__tests__/self.test.ts +31 -11
  113. package/src/connection/__tests__/session.test.ts +3 -3
  114. package/src/connection/__tests__/target.test.ts +1 -0
  115. package/src/connection/auth.ts +13 -0
  116. package/src/connection/call.ts +1 -1
  117. package/src/connection/credential.ts +2 -2
  118. package/src/connection/errors.ts +1 -355
  119. package/src/connection/exchange.ts +47 -23
  120. package/src/connection/failure/classify.ts +110 -0
  121. package/src/connection/failure/index.ts +18 -0
  122. package/src/connection/failure/model.ts +28 -0
  123. package/src/connection/failure/render.ts +116 -0
  124. package/src/connection/reasons.ts +16 -0
  125. package/src/connection/self.ts +39 -21
  126. package/src/connection/session.ts +19 -8
  127. package/src/errors.ts +3 -2
  128. package/src/graph/.spec/api.d.ts +12 -5
  129. package/src/graph/.spec/layout.ts +9 -1
  130. package/src/graph/__tests__/mutation.test.ts +2 -2
  131. package/src/graph/__tests__/query.test.ts +25 -6
  132. package/src/graph/class.ts +30 -0
  133. package/src/graph/index.ts +1 -0
  134. package/src/graph/query.ts +21 -32
  135. package/src/identity/.spec/api.d.ts +2 -2
  136. package/src/identity/__tests__/registration.test.ts +1 -1
  137. package/src/identity/registration.ts +3 -3
  138. package/src/keys/__tests__/keys.test.ts +4 -0
  139. package/src/keys/credential.ts +1 -0
  140. package/src/lib/__tests__/admin-target.test.ts +12 -0
  141. package/src/lib/__tests__/binary.test.ts +16 -1
  142. package/src/lib/__tests__/command-dx.test.ts +1 -1
  143. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  144. package/src/lib/__tests__/idp.test.ts +34 -4
  145. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  146. package/src/lib/__tests__/instance-target.test.ts +7 -39
  147. package/src/lib/__tests__/log-errors.test.ts +64 -0
  148. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  149. package/src/lib/__tests__/update.test.ts +264 -11
  150. package/src/lib/__tests__/view-assets.test.ts +14 -0
  151. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  152. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  153. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  154. package/src/lib/__tests__/view-server.test.ts +26 -3
  155. package/src/lib/__tests__/view-session.test.ts +2 -1
  156. package/src/lib/admin-instance.ts +14 -8
  157. package/src/lib/admin-target.ts +21 -2
  158. package/src/lib/binary.ts +22 -5
  159. package/src/lib/command-dx.ts +9 -9
  160. package/src/lib/domain-publication.ts +9 -7
  161. package/src/lib/failure-debug.ts +26 -0
  162. package/src/lib/idp.ts +34 -3
  163. package/src/lib/instance-candidates.ts +2 -2
  164. package/src/lib/instance-target.ts +3 -14
  165. package/src/lib/instance.ts +16 -11
  166. package/src/lib/log.ts +16 -9
  167. package/src/lib/output.ts +1 -1
  168. package/src/lib/proc.ts +7 -2
  169. package/src/lib/provision-instance.ts +34 -57
  170. package/src/lib/update.ts +298 -45
  171. package/src/lib/validation.ts +2 -2
  172. package/src/lib/view/assets.ts +11 -2
  173. package/src/lib/view/external-open-intent.ts +41 -0
  174. package/src/lib/view/external-open-origins.ts +37 -0
  175. package/src/lib/view/host-capabilities.ts +17 -0
  176. package/src/lib/view/resolve.ts +1 -1
  177. package/src/lib/view/server.ts +18 -3
  178. package/src/lib/view/session.ts +2 -0
  179. package/src/program/.spec/api.d.ts +1 -0
  180. package/src/program/__tests__/program.test.ts +163 -6
  181. package/src/program/argv.ts +14 -0
  182. package/src/program/build.ts +26 -4
  183. package/src/program/command.ts +1 -0
  184. package/src/program/index.ts +1 -0
  185. package/src/program/registry.ts +2 -1
  186. package/src/setup/__tests__/instance-step.test.ts +27 -10
  187. package/src/setup/steps/instance.ts +22 -15
  188. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  189. package/src/telemetry/recorder.ts +9 -1
  190. package/src/ui/.spec/api.d.ts +14 -0
  191. package/src/ui/.spec/architecture.md +10 -0
  192. package/src/ui/.spec/laws.ts +36 -0
  193. package/src/ui/.spec/layout.ts +16 -0
  194. package/src/ui/__tests__/ui.test.ts +981 -0
  195. package/src/ui/index.ts +13 -0
  196. package/src/ui/lock.ts +87 -0
  197. package/src/ui/model.ts +83 -0
  198. package/src/ui/operations.ts +710 -0
  199. package/src/ui/project.ts +219 -0
  200. package/src/ui/release.ts +321 -0
  201. package/src/ui/runner.ts +18 -0
  202. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  203. package/studio/client/dist/assets/index-7YHdeCxp.js +81 -0
  204. package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
  205. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  206. package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
  207. package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
  208. package/studio/client/dist/index.html +3 -2
  209. package/studio/package.json +7 -13
  210. package/studio/server/agent/ask.test.ts +1 -1
  211. package/studio/server/agent/bridge/grant.test.ts +1 -1
  212. package/studio/server/agent/bridge/routes.test.ts +1 -1
  213. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  214. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  215. package/studio/server/agent/harness/gateway/token.ts +3 -3
  216. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  217. package/studio/server/agent/prompts/anchors.ts +20 -49
  218. package/studio/server/agent/prompts/system.test.ts +3 -2
  219. package/studio/server/agent/prompts/system.ts +3 -3
  220. package/studio/server/agent/routes.test.ts +1 -1
  221. package/studio/server/agent/run/coordinator.test.ts +1 -1
  222. package/studio/server/api/canvas.ts +0 -1
  223. package/studio/server/api-agent-loadout.test.ts +1 -1
  224. package/studio/server/api.test.ts +1 -1
  225. package/studio/server/cache.test.ts +23 -12
  226. package/studio/server/cache.ts +12 -15
  227. package/studio/server/domain.test.ts +50 -68
  228. package/studio/server/domain.ts +82 -63
  229. package/studio/server/handoff/copy.ts +1 -1
  230. package/studio/server/index.ts +1 -1
  231. package/studio/server/instances/probe.test.ts +1 -1
  232. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  233. package/studio/server/introspect/anatomy/views.ts +9 -14
  234. package/studio/server/introspect/anatomy-extras.test.ts +26 -42
  235. package/studio/server/introspect/anatomy.test.ts +22 -14
  236. package/studio/server/introspect/anatomy.ts +6 -9
  237. package/studio/server/introspect/bundle.ts +0 -3
  238. package/studio/server/introspect/canonical-schema.test.ts +94 -389
  239. package/studio/server/introspect/canonical-schema.ts +227 -457
  240. package/studio/server/introspect/core-extractor.ts +18 -127
  241. package/studio/server/introspect/core.ts +1 -2
  242. package/studio/server/introspect/diff.test.ts +11 -7
  243. package/studio/server/introspect/diff.ts +18 -55
  244. package/studio/server/introspect/extractor.ts +22 -117
  245. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  246. package/studio/server/introspect/overlay.test.ts +26 -54
  247. package/studio/server/introspect/overlay.ts +10 -37
  248. package/studio/server/introspect/revision.test.ts +1 -1
  249. package/studio/server/introspect/revision.ts +1 -1
  250. package/studio/server/introspect/runtime.test.ts +65 -183
  251. package/studio/server/introspect/runtime.ts +8 -25
  252. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  253. package/studio/server/introspect/schema-ir-json.ts +38 -68
  254. package/studio/server/introspect/schema-refs.test.ts +43 -88
  255. package/studio/server/introspect/schema-refs.ts +14 -49
  256. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  257. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  258. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  259. package/studio/server/state/baseline.test.ts +14 -9
  260. package/studio/server/state/baseline.ts +3 -5
  261. package/studio/server/state/visibility.ts +1 -5
  262. package/studio/server/views/model.ts +3 -3
  263. package/studio/server/views/target.test.ts +17 -74
  264. package/studio/server/views/target.ts +24 -50
  265. package/studio/server/watch.test.ts +11 -11
  266. package/studio/server/watch.ts +8 -4
  267. package/studio/server/workspace-watch.ts +2 -2
  268. package/studio/shared/contracts/schema.ts +38 -107
  269. package/studio/shared/contracts/surface.test.ts +13 -15
  270. package/studio/shared/contracts/workspace.ts +2 -11
  271. package/studio/shared/schema/identity.test.ts +20 -44
  272. package/studio/shared/schema/identity.ts +9 -23
  273. package/viewer/dist/main.js +30 -51
  274. package/.check-workspace.cjs +0 -40
  275. package/src/admin/binding.ts +0 -168
  276. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  277. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  278. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  279. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  280. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  281. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  282. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
@@ -1,10 +1,12 @@
1
1
  import type { KernelCommandOpts } from '../../connection'
2
2
  import type { CommandDefinition } from '../../program/index'
3
3
 
4
+ import { AdminInstanceNotFoundError } from '../../admin/instance'
5
+ import { formatKernelError } from '../../connection/errors'
4
6
  import { deleteOwnedInstance } from '../../lib/admin-instance'
5
7
  import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
6
8
  import { clearActive, readInstances, removeInstance, resolveInstanceKey } from '../../lib/instance'
7
- import { fatal, log, withSpinner } from '../../lib/log'
9
+ import { log, withSpinner } from '../../lib/log'
8
10
  import { isMachine, output } from '../../lib/output'
9
11
 
10
12
  type DeleteOpts = KernelCommandOpts &
@@ -18,10 +20,9 @@ export default {
18
20
  afterHelpText: `
19
21
  Behavior:
20
22
  Calls Instance.delete on the configured admin kernel: best-effort decommission
21
- of the instance's routing-registry entry + WorkOS org (frees the slug), then
22
- removes the Instance record. The host kernel itself is NOT deregistered (the
23
- deprovisioning saga is a follow-up). A same-name local bookmark is removed
24
- after the delete succeeds unless --keep-bookmark is passed.
23
+ of the instance's route and tenant reservation, then removes the Instance
24
+ projection. A same-name local bookmark is removed after the delete succeeds
25
+ unless --keep-bookmark is passed.
25
26
  `,
26
27
  arguments: [{ name: 'id', description: 'Instance slug', required: true }],
27
28
  options: [
@@ -53,14 +54,16 @@ Behavior:
53
54
  } catch (e) {
54
55
  // Bookmark-only id → no admin Instance → kernel NOT_FOUND. Point to
55
56
  // `forget` (local drop) instead of surfacing the raw path error.
56
- const notManaged =
57
- e instanceof Error && (e.name === 'NotFoundError' || e.message.startsWith('Path not found'))
58
- if (notManaged && resolveInstanceKey(await readInstances(), id)) {
57
+ if (
58
+ e instanceof AdminInstanceNotFoundError &&
59
+ resolveInstanceKey(await readInstances(), id)
60
+ ) {
59
61
  log.error(`No admin-managed instance "${id}".`)
60
62
  log.dim(` hint: it's a local bookmark — drop it with: astrale instance forget ${id}`)
61
63
  process.exit(1)
62
64
  }
63
- fatal(e, opts)
65
+ await formatKernelError(e, isMachine(opts), undefined, opts.debug)
66
+ process.exit(1)
64
67
  }
65
68
  },
66
69
  } satisfies CommandDefinition
@@ -7,7 +7,7 @@ import type { CommandDefinition } from '../../program/index'
7
7
  import { AstraleError } from '../../errors'
8
8
  import { listOwnedInstances } from '../../lib/admin-instance'
9
9
  import {
10
- formatInstanceLocation,
10
+ formatInstanceState,
11
11
  type InstanceInfo,
12
12
  type OwnedInstanceInfo,
13
13
  } from '../../lib/admin-instance'
@@ -136,7 +136,7 @@ export function buildInstanceRows(
136
136
  name: twin?.active ? `${item.slug} ${chalk.green('*')}` : item.slug,
137
137
  kind: 'managed',
138
138
  url: item.url ?? '',
139
- extra: [formatInstanceLocation(item), twin ? formatBookmarkConnection(twin) : '']
139
+ extra: [formatInstanceState(item), twin ? formatBookmarkConnection(twin) : '']
140
140
  .filter(Boolean)
141
141
  .join(' · '),
142
142
  })
@@ -3,44 +3,90 @@ import chalk from 'chalk'
3
3
  import type { KernelCommandOpts } from '../../connection'
4
4
  import type { CommandDefinition } from '../../program/index'
5
5
 
6
+ import { formatKernelError } from '../../connection/errors'
6
7
  import { AstraleError } from '../../errors'
7
- import { statusOwnedInstance } from '../../lib/admin-instance'
8
+ import { statusOwnedInstance, type InstanceInfo } from '../../lib/admin-instance'
8
9
  import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
9
- import { fatal, log, withSpinner } from '../../lib/log'
10
+ import { resolveInstance } from '../../lib/instance'
11
+ import { log, withSpinner } from '../../lib/log'
10
12
  import { isMachine, output, type RawOutputOpts } from '../../lib/output'
13
+ import { probeBookmark } from './use'
11
14
 
12
- type StatusOpts = KernelCommandOpts & AdminTargetCommandOpts & RawOutputOpts
15
+ type StatusOpts = KernelCommandOpts &
16
+ AdminTargetCommandOpts &
17
+ RawOutputOpts & { bookmarked?: boolean }
18
+
19
+ interface BookmarkStatus {
20
+ readonly slug: string
21
+ readonly url: string
22
+ readonly issuer: string
23
+ readonly kind: 'bookmark'
24
+ readonly state: 'ready'
25
+ }
13
26
 
14
27
  export default {
15
28
  name: 'status',
16
- description: 'Show admin instance status',
29
+ description: 'Show managed instance status or probe a bookmark',
17
30
  arguments: [{ name: 'id', description: 'Instance slug', required: true }],
18
- options: [...ADMIN_TARGET_OPTIONS],
31
+ options: [
32
+ ...ADMIN_TARGET_OPTIONS,
33
+ { flags: '--bookmarked', description: 'Probe a locally bookmarked kernel connection' },
34
+ ],
19
35
  action: async (id: string, opts: StatusOpts) => {
20
36
  try {
21
37
  const result = await withSpinner(`Fetching instance ${id}`, !isMachine(opts), () =>
22
- statusOwnedInstance(opts, id).catch((error) => {
23
- if (!(error instanceof Error) || error.name !== 'NotFoundError') throw error
24
- throw new AstraleError(
25
- 'INSTANCE_NOT_FOUND',
26
- `No owned instance matches "${id}".`,
27
- 'Run `astrale instance list` to see your instances.',
28
- )
29
- }),
38
+ resolveStatus(id, opts),
30
39
  )
31
40
  if (isMachine(opts)) {
32
41
  output(result, opts)
33
42
  return
34
43
  }
35
44
  console.log(`${chalk.bold(result.slug)} ${chalk.dim(result.url)}`)
36
- log.dim(` state: ${result.state}${result.phase ? ` (${result.phase})` : ''}`)
37
- if (result.error) log.dim(` error: ${result.error}`)
38
- if (result.region) log.dim(` region: ${result.region}`)
39
- if (result.hostId) log.dim(` host: ${result.hostId}`)
40
- if (result.organizationId) log.dim(` organization: ${result.organizationId}`)
41
- if (result.createdAt) log.dim(` created: ${result.createdAt}`)
45
+ const phase = 'phase' in result ? result.phase : undefined
46
+ log.dim(` state: ${result.state}${phase ? ` (${phase})` : ''}`)
47
+ if ('error' in result && result.error) log.dim(` error: ${result.error}`)
48
+ if ('organizationId' in result && result.organizationId)
49
+ log.dim(` organization: ${result.organizationId}`)
50
+ if ('createdAt' in result && result.createdAt) log.dim(` created: ${result.createdAt}`)
42
51
  } catch (e) {
43
- fatal(e, opts)
52
+ await formatKernelError(e, isMachine(opts), undefined, opts.debug)
53
+ process.exit(1)
44
54
  }
45
55
  },
46
56
  } satisfies CommandDefinition
57
+
58
+ export interface StatusDependencies {
59
+ readonly statusOwnedInstance: typeof statusOwnedInstance
60
+ readonly resolveInstance: typeof resolveInstance
61
+ readonly probeBookmark: typeof probeBookmark
62
+ }
63
+
64
+ const defaultStatusDependencies: StatusDependencies = Object.freeze({
65
+ statusOwnedInstance,
66
+ resolveInstance,
67
+ probeBookmark,
68
+ })
69
+
70
+ export async function resolveStatus(
71
+ id: string,
72
+ opts: StatusOpts,
73
+ dependencies: StatusDependencies = defaultStatusDependencies,
74
+ ): Promise<InstanceInfo | BookmarkStatus> {
75
+ if (!opts.bookmarked) return dependencies.statusOwnedInstance(opts, id)
76
+ if (opts.admin !== undefined || opts.adminUrl !== undefined || opts.domainIssuer !== undefined) {
77
+ throw new AstraleError(
78
+ 'INVALID_FLAG',
79
+ '--bookmarked cannot be combined with Admin target options.',
80
+ 'Remove --bookmarked to inspect managed lifecycle, or remove the Admin options to probe a local bookmark.',
81
+ )
82
+ }
83
+ const bookmark = await dependencies.resolveInstance(id, undefined, { persist: false })
84
+ await dependencies.probeBookmark(bookmark)
85
+ return Object.freeze({
86
+ slug: bookmark.name,
87
+ url: bookmark.url,
88
+ issuer: bookmark.issuer ?? bookmark.url,
89
+ kind: 'bookmark',
90
+ state: 'ready',
91
+ })
92
+ }
@@ -3,7 +3,7 @@ import type { CommandDefinition } from '../../program/index'
3
3
 
4
4
  import { AstraleError } from '../../errors'
5
5
  import { getDefault, setDefault } from '../../identity/index'
6
- import { listOwnedInstances } from '../../lib/admin-instance'
6
+ import { listOwnedInstancesWithIdentity } from '../../lib/admin-instance'
7
7
  import { ADMIN_TARGET_OPTIONS } from '../../lib/admin-target'
8
8
  import { fetchWithCaFile } from '../../lib/ca-fetch'
9
9
  import {
@@ -143,7 +143,7 @@ const defaultBookmarkProbeDependencies: BookmarkProbeDependencies = Object.freez
143
143
 
144
144
  async function resolveUseTarget(name: string, opts: UseOpts): Promise<ResolvedInstance> {
145
145
  const [store, managed] = await Promise.all([readInstances(), fetchManagedInstances(name, opts)])
146
- const candidates = collectInstanceCandidates(name, store, managed)
146
+ const candidates = collectInstanceCandidates(name, store, managed.instances)
147
147
 
148
148
  if (candidates.length === 0) {
149
149
  throw new AstraleError(
@@ -168,12 +168,13 @@ async function resolveUseTarget(name: string, opts: UseOpts): Promise<ResolvedIn
168
168
  }
169
169
 
170
170
  assertManagedReady(chosen.info)
171
- const { repointedFrom } = await upsertManagedBookmark(
172
- chosen.key,
173
- chosen.info.slug,
174
- chosen.url,
175
- chosen.info.organizationId,
176
- )
171
+ const { repointedFrom } = await upsertManagedBookmark({
172
+ key: chosen.key,
173
+ slug: chosen.info.slug,
174
+ url: chosen.url,
175
+ ...(chosen.info.organizationId ? { organizationId: chosen.info.organizationId } : {}),
176
+ ...(managed.identity ? { defaultIdentity: managed.identity } : {}),
177
+ })
177
178
  if (repointedFrom) {
178
179
  log.warn(`Bookmark "${chosen.key}" repointed: ${repointedFrom} → ${chosen.url}`)
179
180
  }
@@ -185,16 +186,19 @@ async function resolveUseTarget(name: string, opts: UseOpts): Promise<ResolvedIn
185
186
  * Best-effort: an unreachable or unauthenticated admin kernel degrades to
186
187
  * bookmark-only resolution.
187
188
  */
188
- async function fetchManagedInstances(name: string, opts: UseOpts): Promise<OwnedInstanceInfo[]> {
189
+ async function fetchManagedInstances(
190
+ name: string,
191
+ opts: UseOpts,
192
+ ): Promise<{ readonly instances: OwnedInstanceInfo[]; readonly identity?: string }> {
189
193
  try {
190
194
  validateSlug(name)
191
195
  } catch {
192
- return []
196
+ return { instances: [] }
193
197
  }
194
198
  try {
195
- return await listOwnedInstances(opts)
199
+ return await listOwnedInstancesWithIdentity(opts)
196
200
  } catch {
197
- return []
201
+ return { instances: [] }
198
202
  }
199
203
  }
200
204
 
@@ -1,3 +1,5 @@
1
+ import type { DomainBundle, DomainInfo, SchemaApi } from '@astrale-os/sdk/client/schema'
2
+
1
3
  import { Path } from '@astrale-os/sdk/graph/path'
2
4
 
3
5
  import type { KernelCommandOpts } from '../connection'
@@ -5,46 +7,41 @@ import type { CommandDefinition } from '../program/index'
5
7
 
6
8
  import { runKernelCommand } from '../connection'
7
9
  import { AstraleError } from '../errors'
8
- import { failClosed } from '../lib/log'
10
+ import { failInput } from '../lib/log'
9
11
  import { output } from '../lib/output'
10
- import { describeCallableFromSchema, missingCallableDescription } from './call-describe'
12
+ import { describeCallableFromBundle, missingCallableDescription } from './call-describe'
11
13
 
12
14
  type IntrospectOpts = KernelCommandOpts & { bundle?: boolean }
13
15
 
14
- export async function introspectCommand(target: string, opts: IntrospectOpts): Promise<void> {
16
+ interface IntrospectDependencies {
17
+ readonly runKernelCommand: typeof runKernelCommand
18
+ }
19
+
20
+ const defaultIntrospectDependencies = Object.freeze({ runKernelCommand })
21
+
22
+ export async function introspectCommand(
23
+ target: string,
24
+ opts: IntrospectOpts,
25
+ dependencies: Partial<IntrospectDependencies> = {},
26
+ ): Promise<void> {
27
+ const introspect = { ...defaultIntrospectDependencies, ...dependencies }
15
28
  let origin: string
16
29
  let path: Path
17
30
  try {
18
31
  ;({ origin, path } = parseIntrospectTarget(target))
19
32
  } catch (error) {
20
- failClosed(error, opts)
33
+ failInput(error, opts)
21
34
  }
22
35
  const wantsCallable = isCallablePath(path)
23
36
 
24
- await runKernelCommand({
37
+ await introspect.runKernelCommand({
25
38
  opts,
26
39
  label: `Introspect ${origin}`,
27
40
  fn: async ({ session }) => {
28
41
  const includeBundle = wantsCallable || opts.bundle === true
29
- const result = await session.schema.introspect({
30
- from: { kind: 'installation', origin },
31
- select: {
32
- state: true,
33
- target: true,
34
- source: true,
35
- readiness: true,
36
- capabilities: true,
37
- ...(includeBundle ? { bundle: true as const } : {}),
38
- },
39
- })
40
- if (result === null) {
41
- throw new AstraleError(
42
- 'DOMAIN_NOT_INSTALLED',
43
- `Domain ${origin} is not installed on this Kernel.`,
44
- )
45
- }
42
+ const result = await readInstalledDomain(session.schema, origin, includeBundle)
46
43
  if (wantsCallable) {
47
- const described = describeCallableFromSchema(path, bundleRoot(result.bundle))
44
+ const described = describeCallableFromBundle(path, (result as DomainBundle).bundle)
48
45
  if (described === undefined) throw missingCallableDescription(path.raw)
49
46
  return described
50
47
  }
@@ -54,12 +51,35 @@ export async function introspectCommand(target: string, opts: IntrospectOpts): P
54
51
  })
55
52
  }
56
53
 
54
+ export function readInstalledDomain(
55
+ schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
56
+ origin: string,
57
+ includeBundle: true,
58
+ ): Promise<DomainBundle>
59
+ export function readInstalledDomain(
60
+ schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
61
+ origin: string,
62
+ includeBundle: false,
63
+ ): Promise<DomainInfo>
64
+ export function readInstalledDomain(
65
+ schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
66
+ origin: string,
67
+ includeBundle: boolean,
68
+ ): Promise<DomainInfo | DomainBundle>
69
+ export function readInstalledDomain(
70
+ schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
71
+ origin: string,
72
+ includeBundle: boolean,
73
+ ): Promise<DomainInfo | DomainBundle> {
74
+ return includeBundle ? schema.bundle(origin) : schema.inspect(origin)
75
+ }
76
+
57
77
  export function parseIntrospectTarget(target: string): { origin: string; path: Path } {
58
78
  if (target.startsWith('@')) {
59
79
  throw new AstraleError(
60
80
  'NOT_A_DOMAIN',
61
81
  'introspect requires a Domain origin or Path, not an @id.',
62
- 'Example: astrale introspect host.astrale.ai or /:host.astrale.ai:class.Manager:createInstance',
82
+ 'Example: astrale introspect kernel.astrale.ai or /:kernel.astrale.ai:class.Identity:whois',
63
83
  )
64
84
  }
65
85
  const raw = target.startsWith('/') ? target : `/:${target}`
@@ -85,28 +105,21 @@ function isCallablePath(path: Path): boolean {
85
105
  return last.kind === 'projection' && last.projection.kind === 'function'
86
106
  }
87
107
 
88
- function bundleRoot(bundle: unknown): unknown {
89
- if (bundle !== null && typeof bundle === 'object' && 'root' in bundle) {
90
- return (bundle as { root: unknown }).root
91
- }
92
- return bundle
93
- }
94
-
95
108
  export default {
96
109
  name: 'introspect',
97
110
  description: 'Read installed Domain schema from the Kernel Schema syscall',
98
111
  afterHelpText: `
99
112
  Behavior:
100
113
  Calls the public Kernel introspect syscall for one installed Domain.
101
- A bare origin (host.astrale.ai or /:host.astrale.ai) prints installation
114
+ A bare origin (kernel.astrale.ai or /:kernel.astrale.ai) prints installation
102
115
  state, target, source, readiness, and capabilities. --bundle includes the
103
116
  schema bundle. A method or Function Path projects that callable's
104
117
  input/output from the installed bundle.
105
118
 
106
119
  Examples:
107
- $ astrale introspect host.astrale.ai
108
- $ astrale introspect /:host.astrale.ai --bundle
109
- $ astrale introspect /:host.astrale.ai:class.Manager:createInstance
120
+ $ astrale introspect kernel.astrale.ai
121
+ $ astrale introspect /:kernel.astrale.ai --bundle
122
+ $ astrale introspect /:kernel.astrale.ai:class.Identity:whois
110
123
  $ astrale introspect /:kernel.astrale.ai:function.journal
111
124
  `,
112
125
  arguments: [{ name: 'target', description: 'Domain origin or canonical Path' }],
@@ -1,21 +1,16 @@
1
1
  import { Path } from '@astrale-os/sdk/graph/path'
2
- import { syscalls } from '@astrale-os/sdk/schema/kernel'
2
+ import { K } from '@astrale-os/sdk/schema'
3
3
  import chalk from 'chalk'
4
4
 
5
5
  import type { ConnectionContext, KernelCommandOpts } from '../connection'
6
6
  import type { Column, ListProjection } from '../lib/output'
7
7
  import type { CommandDefinition } from '../program/index'
8
8
 
9
- import {
10
- createPathCall,
11
- expandSelfInPath,
12
- runKernelCommand,
13
- withClientSession,
14
- } from '../connection'
15
- import { failClosed } from '../lib/log'
9
+ import { createPathCall, expandSelfInPath, runKernelCommand } from '../connection'
10
+ import { failInput } from '../lib/log'
16
11
  import { isMachine, output, presentList } from '../lib/output'
17
12
 
18
- const JOURNAL_PATH = Path.project(syscalls.journal.ref).raw
13
+ const JOURNAL_PATH = Path.project(K.functions.journal.ref).raw
19
14
  const DEFAULT_LIMIT = 200
20
15
  const FOLLOW_INTERVAL_MS = 2_000
21
16
 
@@ -38,10 +33,21 @@ export interface JournalRecord {
38
33
  readonly occurredAt?: string
39
34
  readonly committedAt?: string
40
35
  readonly principal?: string
36
+ readonly correlation?: JournalCorrelation
41
37
  readonly correlationId?: string
42
38
  readonly causationId?: string
43
39
  }
44
40
 
41
+ export interface JournalCorrelation {
42
+ readonly operationId?: string
43
+ readonly parentOperationId?: string
44
+ readonly invocationId?: string
45
+ readonly rootInvocationId?: string
46
+ readonly parentInvocationId?: string
47
+ readonly traceId?: string
48
+ readonly spanId?: string
49
+ }
50
+
45
51
  export interface JournalPage {
46
52
  readonly records: readonly JournalRecord[]
47
53
  readonly cursor?: string
@@ -127,7 +133,7 @@ async function runOnce(opts: LogsOpts): Promise<void> {
127
133
  await runKernelCommand({
128
134
  opts,
129
135
  label: 'Kernel journal',
130
- fn: (context) => fetchPage(context, opts),
136
+ fn: async (context) => fetchPage(context, await resolveLogsOpts(opts, context)),
131
137
  format: (page, format) => {
132
138
  if (isMachine(format) || format.format !== undefined) output(page, format)
133
139
  else {
@@ -138,15 +144,33 @@ async function runOnce(opts: LogsOpts): Promise<void> {
138
144
  })
139
145
  }
140
146
 
141
- async function follow(opts: LogsOpts): Promise<void> {
142
- await withClientSession(opts, async (context) => {
143
- let cursor = opts.cursor
144
- for (;;) {
145
- const page = await fetchPage(context, { ...opts, cursor })
146
- for (const record of page.records) printRecord(record)
147
- cursor = page.cursor ?? cursor
148
- await new Promise((resolve) => setTimeout(resolve, FOLLOW_INTERVAL_MS))
149
- }
147
+ type FollowDependencies = {
148
+ readonly run: typeof runKernelCommand
149
+ readonly pause: (milliseconds: number) => Promise<void>
150
+ }
151
+
152
+ /** Follow one admitted journal stream. Dependencies are explicit so routing is proven at the command boundary. */
153
+ export async function followLogs(
154
+ opts: LogsOpts,
155
+ dependencies: FollowDependencies = {
156
+ run: runKernelCommand,
157
+ pause: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
158
+ },
159
+ ): Promise<void> {
160
+ validateLogsOpts(opts)
161
+ await dependencies.run({
162
+ opts,
163
+ label: 'Kernel journal',
164
+ fn: async (context): Promise<never> => {
165
+ const resolved = await resolveLogsOpts(opts, context)
166
+ let cursor = resolved.cursor
167
+ for (;;) {
168
+ const page = await fetchPage(context, { ...resolved, cursor })
169
+ for (const record of page.records) printRecord(record, opts)
170
+ cursor = page.cursor ?? cursor
171
+ await dependencies.pause(FOLLOW_INTERVAL_MS)
172
+ }
173
+ },
150
174
  })
151
175
  }
152
176
 
@@ -169,12 +193,28 @@ function journalProjection(records: JournalRecord[]): ListProjection {
169
193
  }
170
194
  }
171
195
 
172
- function printRecord(record: JournalRecord): void {
196
+ function printRecord(record: JournalRecord, opts: LogsOpts): void {
197
+ if (isMachine(opts) || opts.format === 'json') {
198
+ process.stdout.write(formatFollowRecord(record))
199
+ return
200
+ }
173
201
  process.stdout.write(
174
202
  `${chalk.dim(String(record.sequence).padStart(6))} ${chalk.dim(record.timestamp)} ${chalk.cyan(record.topic)} ${chalk.dim(record.principal ?? '')}\n`,
175
203
  )
176
204
  }
177
205
 
206
+ /** Machine follow is an NDJSON stream: one complete admitted record per line. */
207
+ export function formatFollowRecord(record: JournalRecord): string {
208
+ return `${JSON.stringify(record)}\n`
209
+ }
210
+
211
+ export function validateLogsOpts(opts: LogsOpts): void {
212
+ buildJournalInput(opts)
213
+ if (opts.follow && opts.format === 'yaml' && !opts.json && !opts.raw) {
214
+ throw new TypeError('--follow does not support YAML; use --json for an NDJSON stream')
215
+ }
216
+ }
217
+
178
218
  function acceptRecord(input: unknown, index: number): JournalRecord {
179
219
  if (
180
220
  !isRecord(input) ||
@@ -188,10 +228,17 @@ function acceptRecord(input: unknown, index: number): JournalRecord {
188
228
  if (timestamp === undefined) {
189
229
  throw new TypeError(`Kernel journal record ${index} is missing occurredAt/timestamp`)
190
230
  }
191
- const correlation = isRecord(input.correlation) ? input.correlation : undefined
192
- const correlationId =
193
- optionalText(input.correlationId, index, 'correlationId') ??
194
- optionalText(correlation?.invocationId, index, 'correlation.invocationId')
231
+ const correlation = acceptCorrelation(input.correlation, index)
232
+ const legacyCorrelationId = optionalIdentifier(input.correlationId, index, 'correlationId')
233
+ const structuredCorrelationId = correlation?.invocationId
234
+ if (
235
+ legacyCorrelationId !== undefined &&
236
+ structuredCorrelationId !== undefined &&
237
+ legacyCorrelationId !== structuredCorrelationId
238
+ ) {
239
+ throw new TypeError(`Kernel journal record ${index} has conflicting correlation identifiers`)
240
+ }
241
+ const correlationId = structuredCorrelationId ?? legacyCorrelationId
195
242
  const principal = optionalText(input.principal, index, 'principal')
196
243
  return Object.freeze({
197
244
  sequence: input.sequence as number,
@@ -203,13 +250,56 @@ function acceptRecord(input: unknown, index: number): JournalRecord {
203
250
  ? {}
204
251
  : { committedAt: input.committedAt as string }),
205
252
  ...(principal === undefined ? {} : { principal }),
253
+ ...(correlation === undefined ? {} : { correlation }),
206
254
  ...(correlationId === undefined ? {} : { correlationId }),
207
- ...(optionalText(input.causationId, index, 'causationId') === undefined
255
+ ...(optionalIdentifier(input.causationId, index, 'causationId') === undefined
208
256
  ? {}
209
257
  : { causationId: input.causationId as string }),
210
258
  })
211
259
  }
212
260
 
261
+ const correlationFields = [
262
+ 'operationId',
263
+ 'parentOperationId',
264
+ 'invocationId',
265
+ 'rootInvocationId',
266
+ 'parentInvocationId',
267
+ 'traceId',
268
+ 'spanId',
269
+ ] as const
270
+
271
+ function acceptCorrelation(input: unknown, index: number): JournalCorrelation | undefined {
272
+ if (input === undefined) return undefined
273
+ if (!isRecord(input)) {
274
+ throw new TypeError(`Kernel journal record ${index}.correlation must be an object`)
275
+ }
276
+ const unknown = Object.keys(input).find(
277
+ (field) => !correlationFields.includes(field as (typeof correlationFields)[number]),
278
+ )
279
+ if (unknown !== undefined) {
280
+ throw new TypeError(`Kernel journal record ${index}.correlation.${unknown} is unsupported`)
281
+ }
282
+ return Object.freeze(
283
+ Object.fromEntries(
284
+ correlationFields.flatMap((field) => {
285
+ const value = optionalIdentifier(input[field], index, `correlation.${field}`)
286
+ return value === undefined ? [] : [[field, value]]
287
+ }),
288
+ ),
289
+ )
290
+ }
291
+
292
+ function optionalIdentifier(input: unknown, index: number, field: string): string | undefined {
293
+ const value = optionalText(input, index, field)
294
+ if (value === undefined) return undefined
295
+ if (value.trim() === '' || new TextEncoder().encode(value).byteLength > 256) {
296
+ throw new TypeError(
297
+ `Kernel journal record ${index}.${field} must be non-empty and at most 256 UTF-8 bytes`,
298
+ )
299
+ }
300
+ return value
301
+ }
302
+
213
303
  function optionalText(input: unknown, index: number, field: string): string | undefined {
214
304
  if (input === undefined) return undefined
215
305
  if (typeof input !== 'string') {
@@ -227,11 +317,10 @@ function positiveInteger(flag: string, raw: string): number {
227
317
  return value
228
318
  }
229
319
 
230
- async function prepareLogsOpts(opts: LogsOpts): Promise<LogsOpts> {
231
- buildJournalInput(opts)
320
+ async function resolveLogsOpts(opts: LogsOpts, context: ConnectionContext): Promise<LogsOpts> {
232
321
  const principal = nonEmpty(opts.principal)
233
322
  if (principal !== '@self') return opts
234
- const { path } = await expandSelfInPath('@self', opts)
323
+ const { path } = await expandSelfInPath('@self', context)
235
324
  return { ...opts, principal: path.startsWith('@') ? path.slice(1) : path }
236
325
  }
237
326
 
@@ -252,7 +341,8 @@ Behavior:
252
341
  Calls the public Kernel journal syscall and emits its { records, cursor }
253
342
  page. Topic selection is exact or prefix-based; cursors and timestamps are
254
343
  opaque strings owned by the journal backend. --follow reuses one Client Session
255
- and advances only with the returned cursor.
344
+ and advances only with the returned cursor. Machine follow output is NDJSON:
345
+ one complete admitted record per line; YAML follow is unsupported.
256
346
 
257
347
  Historical event-glob lowering and the application-specific services-domain
258
348
  log buffer are not part of the Kernel V2 journal contract.
@@ -274,11 +364,11 @@ Examples:
274
364
  ],
275
365
  action: async (opts: LogsOpts) => {
276
366
  try {
277
- const prepared = await prepareLogsOpts(opts)
278
- if (prepared.follow) await follow(prepared)
279
- else await runOnce(prepared)
367
+ validateLogsOpts(opts)
280
368
  } catch (error) {
281
- failClosed(error, opts)
369
+ failInput(error, opts)
282
370
  }
371
+ if (opts.follow) await followLogs(opts)
372
+ else await runOnce(opts)
283
373
  },
284
374
  } satisfies CommandDefinition
@@ -7,8 +7,9 @@ import type { KernelCommandOpts } from '../connection'
7
7
  import type { CommandDefinition } from '../program/index'
8
8
 
9
9
  import { runKernelCommand } from '../connection'
10
+ import { AstraleError } from '../errors'
10
11
  import { prepareMutation } from '../graph/index'
11
- import { failClosed } from '../lib/log'
12
+ import { failInput } from '../lib/log'
12
13
  import { output } from '../lib/output'
13
14
  import { renderTable } from '../lib/table'
14
15
 
@@ -23,7 +24,7 @@ export async function mutateCommand(opts: MutateOpts): Promise<void> {
23
24
  try {
24
25
  mutation = prepareMutation(await readDocument(opts))
25
26
  } catch (error) {
26
- failClosed(error, opts)
27
+ failInput(error, opts)
27
28
  }
28
29
 
29
30
  if (opts.dry) {
@@ -51,9 +52,9 @@ async function readDocument(opts: MutateOpts): Promise<unknown> {
51
52
  try {
52
53
  raw = await readFile(opts.file, 'utf8')
53
54
  } catch (error) {
54
- throw new Error(
55
- `Cannot read --file ${opts.file}: ${error instanceof Error ? error.message : error}`,
56
- )
55
+ throw new AstraleError('FILE_READ_FAILED', `Cannot read --file ${opts.file}.`, undefined, {
56
+ cause: error,
57
+ })
57
58
  }
58
59
  return parseJson(raw, opts.file)
59
60
  }