@astrale-os/cli 0.8.1-alpha.0 → 0.8.1-alpha.3

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 (314) hide show
  1. package/dist/astrale.js +63133 -41267
  2. package/dist/public/connect-core.js +55030 -0
  3. package/dist/public/keys/index.js +45631 -0
  4. package/dist/public/paths/index.js +43990 -0
  5. package/dist/types/admin/instance/model.d.ts +40 -0
  6. package/dist/types/connect-core.d.ts +22 -0
  7. package/dist/types/connection/auth.d.ts +33 -0
  8. package/dist/types/errors.d.ts +23 -0
  9. package/dist/types/identity/registry.d.ts +25 -0
  10. package/dist/types/keys/algorithm.d.ts +6 -0
  11. package/dist/types/keys/credential.d.ts +21 -0
  12. package/dist/types/keys/index.d.ts +4 -0
  13. package/dist/types/keys/pair.d.ts +34 -0
  14. package/dist/types/lib/admin-target.d.ts +74 -0
  15. package/dist/types/lib/ca-fetch.d.ts +2 -0
  16. package/dist/types/lib/config.d.ts +71 -0
  17. package/dist/types/lib/format.d.ts +1 -0
  18. package/dist/types/lib/idp-session.d.ts +33 -0
  19. package/dist/types/lib/idp.d.ts +397 -0
  20. package/dist/types/lib/instance-target.d.ts +36 -0
  21. package/dist/types/lib/instance.d.ts +186 -0
  22. package/dist/types/lib/log.d.ts +30 -0
  23. package/dist/types/lib/login-flow.d.ts +49 -0
  24. package/dist/types/lib/meta.d.ts +36 -0
  25. package/dist/types/lib/output.d.ts +83 -0
  26. package/dist/types/lib/table.d.ts +18 -0
  27. package/dist/types/lib/validation.d.ts +11 -0
  28. package/dist/types/paths/index.d.ts +2 -0
  29. package/dist/types/state/exchange-credentials.d.ts +25 -0
  30. package/dist/types/state/files.d.ts +9 -0
  31. package/dist/types/state/identities.d.ts +42 -0
  32. package/dist/types/state/index.d.ts +8 -0
  33. package/dist/types/state/paths.d.ts +32 -0
  34. package/package.json +22 -7
  35. package/src/admin/.spec/api.d.ts +3 -0
  36. package/src/admin/.spec/architecture.md +15 -0
  37. package/src/admin/.spec/icon.svg +3 -0
  38. package/src/admin/.spec/layout.ts +6 -0
  39. package/src/admin/catalog/.spec/api.d.ts +65 -0
  40. package/src/admin/catalog/.spec/architecture.md +6 -0
  41. package/src/admin/catalog/.spec/icon.svg +3 -0
  42. package/src/admin/catalog/.spec/layout.ts +6 -0
  43. package/src/admin/catalog/__tests__/client.test.ts +171 -0
  44. package/src/admin/catalog/client.ts +214 -0
  45. package/src/admin/catalog/index.ts +13 -0
  46. package/src/admin/catalog/model.ts +42 -0
  47. package/src/admin/catalog/tsconfig.json +8 -0
  48. package/src/admin/graph/.spec/api.d.ts +28 -0
  49. package/src/admin/graph/.spec/architecture.md +5 -0
  50. package/src/admin/graph/.spec/icon.svg +3 -0
  51. package/src/admin/graph/.spec/layout.ts +3 -0
  52. package/src/admin/graph/index.ts +6 -0
  53. package/src/admin/graph/nodes.ts +56 -0
  54. package/src/admin/index.ts +2 -0
  55. package/src/admin/instance/.spec/api.d.ts +66 -0
  56. package/src/admin/instance/.spec/architecture.md +10 -0
  57. package/src/admin/instance/.spec/icon.svg +3 -0
  58. package/src/admin/instance/.spec/layout.ts +6 -0
  59. package/src/admin/instance/__tests__/client.test.ts +289 -0
  60. package/src/admin/instance/client.ts +298 -0
  61. package/src/admin/instance/index.ts +16 -0
  62. package/src/admin/instance/model.ts +65 -0
  63. package/src/admin/instance/tsconfig.json +8 -0
  64. package/src/commands/__tests__/admin-instance.test.ts +3 -36
  65. package/src/commands/__tests__/call-describe.test.ts +67 -0
  66. package/src/commands/__tests__/call.test.ts +21 -68
  67. package/src/commands/__tests__/domain-install-owned.test.ts +26 -27
  68. package/src/commands/__tests__/domain-list.test.ts +2 -6
  69. package/src/commands/__tests__/install-direct.test.ts +20 -0
  70. package/src/commands/__tests__/instance-list-admin.test.ts +26 -0
  71. package/src/commands/__tests__/logs.test.ts +71 -96
  72. package/src/commands/__tests__/ls.test.ts +21 -17
  73. package/src/commands/__tests__/read-commands.test.ts +171 -101
  74. package/src/commands/__tests__/view.test.ts +100 -51
  75. package/src/commands/admin/status.ts +3 -3
  76. package/src/commands/admin/use.ts +29 -6
  77. package/src/commands/auth/login.ts +1 -1
  78. package/src/commands/auth/logout.ts +4 -2
  79. package/src/commands/auth/status.ts +2 -2
  80. package/src/commands/auth/token.ts +2 -2
  81. package/src/commands/browser.ts +1 -1
  82. package/src/commands/call-describe.ts +131 -0
  83. package/src/commands/call.ts +69 -109
  84. package/src/commands/describe.ts +52 -147
  85. package/src/commands/domain/install.ts +106 -49
  86. package/src/commands/domain/list.ts +4 -8
  87. package/src/commands/domain/publish.ts +9 -31
  88. package/src/commands/get.ts +33 -66
  89. package/src/commands/identity/__tests__/register.test.ts +93 -0
  90. package/src/commands/identity/create.ts +2 -2
  91. package/src/commands/identity/delete.ts +2 -2
  92. package/src/commands/identity/export.ts +7 -33
  93. package/src/commands/identity/import.ts +21 -70
  94. package/src/commands/identity/list.ts +2 -2
  95. package/src/commands/identity/register.ts +148 -117
  96. package/src/commands/identity/sync.ts +2 -2
  97. package/src/commands/identity/unsync.ts +2 -2
  98. package/src/commands/identity/use.ts +2 -2
  99. package/src/commands/identity/whoami.ts +2 -2
  100. package/src/commands/idp/add.ts +1 -1
  101. package/src/commands/idp/list.ts +1 -1
  102. package/src/commands/idp/refresh.ts +1 -1
  103. package/src/commands/idp/remove.ts +2 -2
  104. package/src/commands/idp/show.ts +1 -1
  105. package/src/commands/instance/active.ts +2 -2
  106. package/src/commands/instance/bookmark.ts +8 -2
  107. package/src/commands/instance/create.ts +1 -1
  108. package/src/commands/instance/delete.ts +4 -10
  109. package/src/commands/instance/forget.ts +1 -1
  110. package/src/commands/instance/list.ts +39 -6
  111. package/src/commands/instance/status.ts +5 -7
  112. package/src/commands/instance/use.ts +3 -3
  113. package/src/commands/logs.ts +171 -278
  114. package/src/commands/ls.ts +78 -204
  115. package/src/commands/mutate.ts +70 -139
  116. package/src/commands/query.ts +100 -278
  117. package/src/commands/session/analyze.ts +1 -1
  118. package/src/commands/session/list.ts +1 -1
  119. package/src/commands/setup.ts +1 -1
  120. package/src/commands/status.ts +1 -1
  121. package/src/commands/studio.ts +1 -1
  122. package/src/commands/token.ts +26 -21
  123. package/src/commands/update.ts +1 -1
  124. package/src/commands/use.ts +2 -3
  125. package/src/commands/view-serve.ts +1 -1
  126. package/src/commands/view.ts +67 -109
  127. package/src/connect-core.test.ts +38 -0
  128. package/src/connect-core.ts +42 -0
  129. package/src/connection/.spec/api.d.ts +111 -0
  130. package/src/connection/.spec/architecture.md +41 -0
  131. package/src/connection/.spec/capabilities/connection.ts +13 -0
  132. package/src/connection/.spec/examples/call.ts +7 -0
  133. package/src/connection/.spec/flows/command.ts +20 -0
  134. package/src/connection/.spec/flows/session.ts +61 -0
  135. package/src/connection/.spec/laws/connection.ts +181 -0
  136. package/src/connection/.spec/layout.ts +19 -0
  137. package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
  138. package/src/connection/__tests__/ca-fetch.test.ts +53 -0
  139. package/src/connection/__tests__/call.test.ts +17 -0
  140. package/src/connection/__tests__/credential.test.ts +109 -0
  141. package/src/connection/__tests__/errors.test.ts +96 -0
  142. package/src/connection/__tests__/exchange.test.ts +224 -0
  143. package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
  144. package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
  145. package/src/connection/__tests__/managed.test.ts +39 -0
  146. package/src/connection/__tests__/self.test.ts +51 -0
  147. package/src/connection/__tests__/session.test.ts +140 -0
  148. package/src/connection/__tests__/target.test.ts +93 -0
  149. package/src/{kernel → connection}/auth.ts +8 -8
  150. package/src/connection/call.ts +7 -0
  151. package/src/{kernel/run.ts → connection/command.ts} +22 -13
  152. package/src/connection/credential.ts +114 -0
  153. package/src/{kernel → connection}/errors.ts +32 -9
  154. package/src/connection/exchange.ts +274 -0
  155. package/src/connection/index.ts +8 -0
  156. package/src/connection/self.ts +120 -0
  157. package/src/connection/session.ts +176 -0
  158. package/src/connection/target.ts +111 -0
  159. package/src/connection/tsconfig.json +6 -0
  160. package/src/graph/.spec/api.d.ts +34 -0
  161. package/src/graph/.spec/laws/documents.ts +24 -0
  162. package/src/graph/.spec/laws/projection.ts +13 -0
  163. package/src/graph/.spec/layout.ts +6 -0
  164. package/src/graph/__tests__/mutation.test.ts +45 -0
  165. package/src/graph/__tests__/projection.test.ts +22 -0
  166. package/src/graph/__tests__/query.test.ts +154 -0
  167. package/src/graph/index.ts +3 -0
  168. package/src/graph/mutation.ts +33 -0
  169. package/src/graph/projection.ts +21 -0
  170. package/src/graph/query.ts +108 -0
  171. package/src/graph/tsconfig.json +6 -0
  172. package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
  173. package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
  174. package/src/identity/.spec/api.d.ts +124 -0
  175. package/src/identity/.spec/architecture.md +33 -0
  176. package/src/identity/.spec/capabilities/identity.ts +7 -0
  177. package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
  178. package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
  179. package/src/identity/.spec/flows/import.ts +29 -0
  180. package/src/identity/.spec/laws/identity.ts +56 -0
  181. package/src/identity/.spec/layout.ts +13 -0
  182. package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
  183. package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
  184. package/src/identity/__tests__/registration.test.ts +64 -0
  185. package/src/identity/__tests__/registry.test.ts +70 -0
  186. package/src/identity/__tests__/transfer.test.ts +198 -0
  187. package/src/identity/index.ts +23 -0
  188. package/src/identity/registration.ts +72 -0
  189. package/src/identity/registry.ts +172 -0
  190. package/src/identity/transfer.ts +187 -0
  191. package/src/identity/tsconfig.json +6 -0
  192. package/src/index.ts +1 -0
  193. package/src/keys/.spec/api.d.ts +74 -0
  194. package/src/keys/.spec/architecture.md +13 -0
  195. package/src/keys/.spec/laws/keys.ts +43 -0
  196. package/src/keys/.spec/layout.ts +6 -0
  197. package/src/keys/__tests__/algorithm.test.ts +39 -0
  198. package/src/keys/__tests__/keys.test.ts +168 -0
  199. package/src/keys/algorithm.ts +33 -0
  200. package/src/keys/credential.ts +112 -0
  201. package/src/keys/index.ts +14 -0
  202. package/src/keys/pair.ts +219 -0
  203. package/src/keys/tsconfig.json +6 -0
  204. package/src/lib/__tests__/admin-target.test.ts +67 -7
  205. package/src/lib/__tests__/binary.test.ts +2 -3
  206. package/src/lib/__tests__/command-dx.test.ts +2 -2
  207. package/src/lib/__tests__/config.test.ts +3 -2
  208. package/src/lib/__tests__/idp.test.ts +4 -6
  209. package/src/lib/__tests__/instance-target.test.ts +7 -4
  210. package/src/lib/__tests__/local-status.test.ts +1 -1
  211. package/src/lib/__tests__/log.test.ts +31 -0
  212. package/src/lib/__tests__/meta.test.ts +55 -0
  213. package/src/lib/__tests__/sdk-deps.test.ts +1 -1
  214. package/src/lib/__tests__/self.test.ts +8 -230
  215. package/src/lib/__tests__/use-target.test.ts +1 -1
  216. package/src/lib/__tests__/view-open-intent.test.ts +78 -104
  217. package/src/lib/__tests__/view-session.test.ts +99 -0
  218. package/src/lib/admin-domain.ts +39 -25
  219. package/src/lib/admin-instance.ts +38 -43
  220. package/src/lib/admin-target.ts +67 -13
  221. package/src/lib/binary.ts +13 -34
  222. package/src/lib/browser.ts +1 -1
  223. package/src/{kernel → lib}/ca-fetch.ts +35 -27
  224. package/src/lib/config.ts +1 -1
  225. package/src/lib/idp-session.ts +1 -1
  226. package/src/lib/idp.ts +3 -8
  227. package/src/lib/instance-target.ts +12 -8
  228. package/src/lib/instance.ts +34 -2
  229. package/src/lib/local-status.ts +2 -2
  230. package/src/lib/log.ts +3 -1
  231. package/src/lib/login-flow.ts +32 -5
  232. package/src/lib/meta.ts +15 -11
  233. package/src/lib/provision-instance.ts +4 -12
  234. package/src/lib/self.ts +5 -147
  235. package/src/lib/update.ts +1 -1
  236. package/src/lib/use-target.ts +1 -1
  237. package/src/lib/validation.ts +2 -2
  238. package/src/lib/view/open-intent.ts +7 -37
  239. package/src/lib/view/port-allocation.ts +1 -1
  240. package/src/lib/view/resolve.ts +52 -45
  241. package/src/lib/view/server.ts +16 -33
  242. package/src/lib/view/session.ts +51 -22
  243. package/src/paths/.spec/api.d.ts +13 -0
  244. package/src/paths/.spec/architecture.md +5 -0
  245. package/src/paths/.spec/layout.ts +3 -0
  246. package/src/paths/index.ts +13 -0
  247. package/src/program/.spec/api.d.ts +42 -0
  248. package/src/program/.spec/architecture.md +18 -0
  249. package/src/program/.spec/laws/program.ts +29 -0
  250. package/src/program/.spec/layout.ts +14 -0
  251. package/src/program/__tests__/program.test.ts +359 -0
  252. package/src/program/build.ts +190 -0
  253. package/src/program/command.ts +42 -0
  254. package/src/program/index.ts +2 -0
  255. package/src/program/options.ts +40 -0
  256. package/src/{registry.ts → program/registry.ts} +5 -1
  257. package/src/program/tsconfig.json +6 -0
  258. package/src/setup/steps/admin.ts +18 -3
  259. package/src/setup/steps/instance.ts +1 -1
  260. package/src/state/.spec/api.d.ts +133 -0
  261. package/src/state/.spec/architecture.md +31 -0
  262. package/src/state/.spec/capabilities/state.ts +7 -0
  263. package/src/state/.spec/flows/identity-update.ts +22 -0
  264. package/src/state/.spec/laws/state.ts +73 -0
  265. package/src/state/.spec/layout.ts +14 -0
  266. package/src/state/__tests__/exchange-credentials.test.ts +188 -0
  267. package/src/state/__tests__/files.test.ts +113 -0
  268. package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
  269. package/src/state/__tests__/identities.test.ts +142 -0
  270. package/src/state/__tests__/paths.test.ts +27 -0
  271. package/src/state/exchange-credentials.ts +219 -0
  272. package/src/state/files.ts +129 -0
  273. package/src/state/identities.ts +236 -0
  274. package/src/state/index.ts +29 -0
  275. package/src/state/paths.ts +61 -0
  276. package/src/state/tsconfig.json +6 -0
  277. package/src/telemetry/__tests__/redact.test.ts +3 -3
  278. package/src/telemetry/__tests__/trigger.test.ts +1 -1
  279. package/src/telemetry/settings.ts +1 -1
  280. package/src/telemetry/store.ts +1 -1
  281. package/src/test-utils.ts +1 -1
  282. package/studio/client/dist/assets/{elk-api-DVw5WDTH.js → elk-api-D0cBetPW.js} +1 -1
  283. package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
  284. package/studio/client/dist/assets/{index-Dl709Ra1.js → index-bVD2KJgz.js} +4 -4
  285. package/studio/client/dist/index.html +1 -1
  286. package/studio/package.json +10 -1
  287. package/studio/server/introspect/extractor.ts +1 -1
  288. package/studio/server/state/views.test.ts +3 -7
  289. package/studio/server/state/views.ts +23 -30
  290. package/tsconfig.json +1 -1
  291. package/viewer/dist/main.js +65 -39
  292. package/src/command.ts +0 -42
  293. package/src/commands/__tests__/help-contract.test.ts +0 -164
  294. package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
  295. package/src/kernel/__tests__/errors.test.ts +0 -43
  296. package/src/kernel/__tests__/expand.test.ts +0 -123
  297. package/src/kernel/client.ts +0 -214
  298. package/src/kernel/expand.ts +0 -192
  299. package/src/kernel/graph.ts +0 -96
  300. package/src/kernel/index.ts +0 -29
  301. package/src/kernel/options.ts +0 -22
  302. package/src/kernel/types.ts +0 -14
  303. package/src/lib/__tests__/domain-identity.test.ts +0 -60
  304. package/src/lib/__tests__/fs-atomic.test.ts +0 -104
  305. package/src/lib/__tests__/identity.test.ts +0 -79
  306. package/src/lib/__tests__/keys.test.ts +0 -129
  307. package/src/lib/domain-identity.ts +0 -49
  308. package/src/lib/env.ts +0 -49
  309. package/src/lib/fs-atomic.ts +0 -126
  310. package/src/lib/identity.ts +0 -256
  311. package/src/lib/keys.ts +0 -294
  312. package/src/lib/paths.ts +0 -11
  313. package/src/program.ts +0 -213
  314. package/studio/client/dist/assets/index-CTbkDp3z.js +0 -81
@@ -1,14 +1,18 @@
1
- import { K } from '@astrale-os/kernel-core'
1
+ import { Path } from '@astrale-os/sdk/graph/path'
2
+ import { syscalls } from '@astrale-os/sdk/schema/kernel'
2
3
  import chalk from 'chalk'
3
4
 
4
- import type { CommandDefinition } from '../../command'
5
- import type { KernelCommandOpts } from '../../kernel'
5
+ import type { KernelCommandOpts } from '../../connection'
6
+ import type { CommandDefinition } from '../../program/index'
6
7
 
8
+ import { createPathCall, runKernelCommand, withAdminClientSession } from '../../connection'
7
9
  import { AstraleError } from '../../errors'
8
- import { runKernelCommand } from '../../kernel'
9
- import { withAdminKernelClient } from '../../kernel/client'
10
- import { adminDomainMethod, type DomainInfo } from '../../lib/admin-domain'
11
- import { callOwnedInstances, type OwnedInstanceInfo } from '../../lib/admin-instance'
10
+ import {
11
+ installAdminDomainInContext,
12
+ listAdminDomainsInContext,
13
+ type DomainInfo,
14
+ } from '../../lib/admin-domain'
15
+ import { listOwnedInstancesInContext, type OwnedInstanceInfo } from '../../lib/admin-instance'
12
16
  import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
13
17
  import { getActive } from '../../lib/instance'
14
18
  import { fatal, log, withSpinner } from '../../lib/log'
@@ -16,18 +20,35 @@ import { isMachine, output } from '../../lib/output'
16
20
  import { confirmWithInput, promptText, selectFrom } from '../../lib/prompt'
17
21
  import { isHttpUrl } from '../../lib/validation'
18
22
 
19
- /** Mirrors the admin worker's `DomainInstallResult` (schema/domains.ts). */
20
- type DomainInstallResult = {
21
- name: string
22
- origin: string
23
- instanceId: string
24
- url: string
25
- ok: boolean
26
- error?: string | null
23
+ /** Public Kernel install syscall input for one remote URL. */
24
+ export function directInstallCallInput(
25
+ url: string,
26
+ token?: string,
27
+ operation: string = crypto.randomUUID(),
28
+ ) {
29
+ return Object.freeze({
30
+ operation,
31
+ domains: [
32
+ Object.freeze({
33
+ source: Object.freeze({
34
+ kind: 'remote' as const,
35
+ url,
36
+ ...(token === undefined ? {} : { token }),
37
+ }),
38
+ }),
39
+ ],
40
+ })
27
41
  }
28
42
 
29
- /** Mirrors the kernel's `Domain.install` return. */
30
- type DirectInstallResult = { domainId: string; origin: string }
43
+ type DirectInstallResult = {
44
+ readonly operation: string
45
+ readonly transitions: readonly {
46
+ readonly intent: {
47
+ readonly origin: string
48
+ readonly target?: { readonly schemaRevision?: string } | null
49
+ }
50
+ }[]
51
+ }
31
52
 
32
53
  type InstallOpts = KernelCommandOpts &
33
54
  AdminTargetCommandOpts & {
@@ -50,8 +71,8 @@ Behavior:
50
71
  interactively. The target instance is the active one, or -i <slug>; it must be
51
72
  admin-managed (otherwise the command fails loudly and points you at --direct).
52
73
 
53
- --direct: installs a url STRAIGHT onto the instance kernel (Domain.install),
54
- bypassing the admin/catalog. Works on any instance you can authenticate to
74
+ --direct: installs a url through the public Kernel install syscall,
75
+ bypassing the admin catalog. Works on any instance you can authenticate to
55
76
  (managed, bookmarked, or local), using your own authority. This is the only
56
77
  mode that runs the identity-override consent gate: when the domain's declared
57
78
  origin differs from its serving host, it requires explicit consent (an
@@ -108,7 +129,12 @@ Examples:
108
129
  * an admin-kernel override). The admin kernel is chosen via --admin/--admin-url
109
130
  * or config, exactly like `domain publish`.
110
131
  */
111
- async function installViaAdmin(target: string | undefined, opts: InstallOpts): Promise<void> {
132
+ export async function installViaAdmin(
133
+ target: string | undefined,
134
+ opts: InstallOpts,
135
+ dependencies: Partial<AdminInstallDependencies> = {},
136
+ ): Promise<void> {
137
+ const admin = { ...defaultAdminInstallDependencies, ...dependencies }
112
138
  const interactive = !!process.stdin.isTTY && !(opts.ci || opts.noPrompt || process.env.CI)
113
139
  if (!target && !interactive) {
114
140
  fatal(
@@ -121,13 +147,17 @@ async function installViaAdmin(target: string | undefined, opts: InstallOpts): P
121
147
  }
122
148
  // Strip the install-target selector so the admin client resolves only the
123
149
  // admin kernel (via --admin/--admin-url/config), not the instance under `-i`.
124
- const adminOpts: InstallOpts = { ...opts, instance: undefined }
150
+ const adminOpts = {
151
+ admin: opts.admin,
152
+ adminUrl: opts.adminUrl,
153
+ timeout: opts.timeout,
154
+ as: opts.as,
155
+ creds: opts.creds,
156
+ }
125
157
 
126
158
  try {
127
- await withAdminKernelClient(adminOpts, async (ctx) => {
128
- const instances = await callOwnedInstances(ctx.client)
129
-
130
- const ref = await resolveDomainRef(ctx, target, interactive)
159
+ await withAdminClientSession(adminOpts, async (ctx) => {
160
+ const instances = await admin.listInstances(ctx)
131
161
  const slug = await resolveTargetSlug(opts, target, interactive, instances)
132
162
 
133
163
  const match = instances.find((i) => i.slug === slug)
@@ -141,15 +171,14 @@ async function installViaAdmin(target: string | undefined, opts: InstallOpts): P
141
171
  }
142
172
  assertInstallTargetReady(match)
143
173
 
144
- const label = ref.origin ?? ref.url ?? 'domain'
174
+ const domains = await admin.listDomains(ctx)
175
+ const domain = await resolveDomain(domains, target, interactive)
176
+
177
+ const label = domain.origin
145
178
  const result = await withSpinner(
146
179
  `Installing ${label} on ${match.slug}`,
147
180
  !isMachine(opts),
148
- () =>
149
- ctx.client.call(adminDomainMethod('install'), {
150
- instanceId: match.slug,
151
- ...ref,
152
- }) as Promise<DomainInstallResult>,
181
+ () => admin.install(ctx, match, domain),
153
182
  { success: (r) => `Installed ${r.origin} on ${match.slug}` },
154
183
  )
155
184
 
@@ -193,15 +222,25 @@ export function domainRefFromTarget(target: string): { origin?: string; url?: st
193
222
  }
194
223
 
195
224
  /** Resolve the domain to install: the positional `target`, or an interactive pick. */
196
- async function resolveDomainRef(
197
- ctx: { client: { call: (path: string, params: unknown) => Promise<unknown> } },
225
+ async function resolveDomain(
226
+ catalog: readonly DomainInfo[],
198
227
  target: string | undefined,
199
228
  interactive: boolean,
200
- ): Promise<{ origin?: string; url?: string }> {
201
- if (target) return domainRefFromTarget(target)
229
+ ): Promise<DomainInfo> {
230
+ if (target) {
231
+ const ref = domainRefFromTarget(target)
232
+ const found = catalog.find((domain) =>
233
+ ref.origin === undefined ? domain.url === ref.url : domain.origin === ref.origin,
234
+ )
235
+ if (found !== undefined) return found
236
+ throw new AstraleError(
237
+ 'DOMAIN_NOT_FOUND',
238
+ `No published domain matches "${target}".`,
239
+ 'Run `astrale domain list` to see the Admin catalog.',
240
+ )
241
+ }
202
242
  if (!interactive) throw new AstraleError('MISSING_ARG', 'No domain given.')
203
243
 
204
- const catalog = (await ctx.client.call(adminDomainMethod('list'), {})) as DomainInfo[]
205
244
  const installable = catalog.filter((d) => d.url)
206
245
  if (installable.length === 0) {
207
246
  throw new AstraleError(
@@ -218,9 +257,21 @@ async function resolveDomainRef(
218
257
  })),
219
258
  )
220
259
  if (!origin) throw new AstraleError('CANCELLED', 'No domain selected.')
221
- return { origin }
260
+ return catalog.find((domain) => domain.origin === origin)!
222
261
  }
223
262
 
263
+ interface AdminInstallDependencies {
264
+ readonly listInstances: typeof listOwnedInstancesInContext
265
+ readonly listDomains: typeof listAdminDomainsInContext
266
+ readonly install: typeof installAdminDomainInContext
267
+ }
268
+
269
+ const defaultAdminInstallDependencies: AdminInstallDependencies = Object.freeze({
270
+ listInstances: listOwnedInstancesInContext,
271
+ listDomains: listAdminDomainsInContext,
272
+ install: installAdminDomainInContext,
273
+ })
274
+
224
275
  /**
225
276
  * Resolve the target instance slug: `-i`, else the active instance. When run
226
277
  * bare (no positional) in a TTY, prompt for it with the active instance
@@ -271,8 +322,8 @@ async function activeSlug(): Promise<string | undefined> {
271
322
  // ── Direct path (--direct) ────────────────────────────────────────────────────
272
323
 
273
324
  /**
274
- * Install a url straight onto the instance kernel (`Domain.install`),
275
- * bypassing the admin/catalog — the classic path, with the §5 identity-override
325
+ * Install a url through the public Kernel install syscall,
326
+ * bypassing the admin catalog, with the identity-override
276
327
  * consent gate. Works on any instance the caller can authenticate to.
277
328
  */
278
329
  async function installDirect(target: string | undefined, opts: InstallOpts): Promise<void> {
@@ -300,26 +351,32 @@ async function installDirect(target: string | undefined, opts: InstallOpts): Pro
300
351
  await runKernelCommand<DirectInstallResult>({
301
352
  opts,
302
353
  label: `Installing domain from ${url}`,
303
- fn: async (ctx) =>
304
- (await ctx.client.call(K.Domain.install.path.method.raw, {
305
- url,
306
- ...(opts.token ? { token: opts.token } : {}),
307
- })) as DirectInstallResult,
354
+ fn: async ({ session }) =>
355
+ (await session.call(
356
+ createPathCall(
357
+ Path.project(syscalls.install.ref).raw,
358
+ directInstallCallInput(url, opts.token),
359
+ ),
360
+ )) as DirectInstallResult,
308
361
  format: (result, fmtOpts, isRaw) => {
309
362
  if (isRaw) {
310
363
  output(result, fmtOpts)
311
364
  return
312
365
  }
313
- log.success(`Domain installed: ${result.origin}`)
314
- log.dim(` domainId: ${result.domainId}`)
366
+ const installed = result.transitions[0]?.intent
367
+ if (installed === undefined) {
368
+ throw new Error('Kernel install returned no committed Domain transition.')
369
+ }
370
+ const revision = installed.target?.schemaRevision ?? result.operation
371
+ log.success(`Domain installed: ${installed.origin}@${revision}`)
315
372
  // Belt-and-braces: the kernel-confirmed origin is authoritative. If it
316
373
  // aliases the host and the pre-install gate never consented to THAT
317
374
  // origin (lying or absent `/meta`), say so loudly after the fact.
318
- if (isIdentityOverride(result.origin, host) && result.origin !== consentedOrigin) {
375
+ if (isIdentityOverride(installed.origin, host) && installed.origin !== consentedOrigin) {
319
376
  log.warn(
320
- `Installed origin "${result.origin}" differs from the serving host "${host}" ` +
377
+ `Installed origin "${installed.origin}" differs from the serving host "${host}" ` +
321
378
  `and was not confirmed before install (the worker's /meta did not declare it). ` +
322
- `Every ${result.origin}/* call on this instance now routes to ${host}.`,
379
+ `Every ${installed.origin}/* call on this instance now routes to ${host}.`,
323
380
  )
324
381
  }
325
382
  },
@@ -1,11 +1,10 @@
1
1
  import chalk from 'chalk'
2
2
 
3
- import type { CommandDefinition } from '../../command'
4
- import type { KernelCommandOpts } from '../../kernel'
3
+ import type { KernelCommandOpts } from '../../connection'
5
4
  import type { ListProjection, RawOutputOpts } from '../../lib/output'
5
+ import type { CommandDefinition } from '../../program/index'
6
6
 
7
- import { withAdminKernelClient } from '../../kernel/client'
8
- import { adminDomainMethod, type DomainInfo } from '../../lib/admin-domain'
7
+ import { listAdminDomains, type DomainInfo } from '../../lib/admin-domain'
9
8
  import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
10
9
  import { fatal, withSpinner } from '../../lib/log'
11
10
  import { isMachine, presentList } from '../../lib/output'
@@ -100,10 +99,7 @@ Examples:
100
99
  'Fetching domains',
101
100
  !isMachine(opts),
102
101
  async (): Promise<DomainRow[]> => {
103
- const list = await withAdminKernelClient(
104
- opts,
105
- async (ctx) => (await ctx.client.call(adminDomainMethod('list'), {})) as DomainInfo[],
106
- )
102
+ const list = await listAdminDomains(opts)
107
103
  const filtered = opts.defaultOnly ? list.filter((d) => d.installByDefault) : list
108
104
  filtered.sort(byDefaultThenName)
109
105
  if (!opts.check) return filtered as DomainRow[]
@@ -1,10 +1,9 @@
1
1
  import chalk from 'chalk'
2
2
 
3
- import type { CommandDefinition } from '../../command'
4
- import type { KernelCommandOpts } from '../../kernel'
3
+ import type { KernelCommandOpts } from '../../connection'
4
+ import type { CommandDefinition } from '../../program/index'
5
5
 
6
- import { withAdminKernelClient } from '../../kernel/client'
7
- import { adminDomainMethod, type DomainInfo } from '../../lib/admin-domain'
6
+ import { publishAdminDomain } from '../../lib/admin-domain'
8
7
  import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
9
8
  import { fatal, withSpinner } from '../../lib/log'
10
9
  import { isMachine, output } from '../../lib/output'
@@ -108,33 +107,12 @@ Examples:
108
107
  `Publishing ${name} → ${publicUrl}`,
109
108
  !isMachine(opts),
110
109
  () =>
111
- withAdminKernelClient(opts, async (ctx) => {
112
- // Read the current catalog entry first: a re-publish that would write
113
- // the same origin/name/url/description/install flag is a no-op we
114
- // report as "already up to date" (and skip the write) rather than
115
- // silently bumping `updatedAt`. `info` throws when absent → treat as
116
- // a fresh publish.
117
- const existing = (await ctx.client
118
- .call(adminDomainMethod('info'), { origin })
119
- .catch(() => null)) as DomainInfo | null
120
- if (
121
- existing &&
122
- existing.name === name &&
123
- existing.url === publicUrl &&
124
- (opts.description === undefined || existing.description === opts.description) &&
125
- (opts.installByDefault === undefined ||
126
- (existing.installByDefault ?? false) === opts.installByDefault)
127
- ) {
128
- return { entry: existing, changed: false as const, isNew: false }
129
- }
130
- const entry = (await ctx.client.call(adminDomainMethod('publish'), {
131
- origin,
132
- name,
133
- url: publicUrl,
134
- ...(opts.description ? { description: opts.description } : {}),
135
- ...(opts.installByDefault ? { installByDefault: true } : {}),
136
- })) as DomainInfo
137
- return { entry, changed: true as const, isNew: !existing }
110
+ publishAdminDomain(opts, {
111
+ origin,
112
+ name,
113
+ url: publicUrl,
114
+ ...(opts.description ? { description: opts.description } : {}),
115
+ ...(opts.installByDefault ? { installByDefault: true } : {}),
138
116
  }),
139
117
  {
140
118
  success: ({ entry, changed, isNew }) =>
@@ -1,85 +1,52 @@
1
- import type { CommandDefinition } from '../command'
2
- import type { KernelCommandOpts } from '../kernel'
1
+ import { Path } from '@astrale-os/sdk/graph/path'
3
2
 
4
- import { bindGraph, expandSelfInPath, runKernelCommand, withSelfHint } from '../kernel'
5
- import { log } from '../lib/log'
6
- import { output } from '../lib/output'
3
+ import type { KernelCommandOpts } from '../connection'
4
+ import type { CommandDefinition } from '../program/index'
7
5
 
8
- type GetOpts = KernelCommandOpts & {
9
- long?: boolean
10
- }
11
-
12
- const INTERNAL_KEYS = new Set(['__labels', 'classId'])
6
+ import { expandSelfInPath, runKernelCommand, withSelfHint } from '../connection'
7
+ import { formatKernelError } from '../connection/errors'
8
+ import { isMachine, output } from '../lib/output'
13
9
 
14
- export async function getCommand(pathArg: string, opts: GetOpts): Promise<void> {
15
- if (!pathArg) {
16
- log.error('Usage: astrale get <path>')
17
- process.exit(1)
18
- return
19
- }
10
+ type GetOpts = KernelCommandOpts
20
11
 
12
+ /** Read one exact canonical Node. The caller's Path is not fabricated into the Node value. */
13
+ export async function getCommand(target: string, opts: GetOpts): Promise<void> {
21
14
  let path: string
22
15
  let meta
23
16
  try {
24
- ;({ path, meta } = await expandSelfInPath(pathArg, opts))
25
- } catch (e) {
26
- log.error(e instanceof Error ? e.message : 'Invalid @self expansion')
17
+ ;({ path, meta } = await expandSelfInPath(target, opts))
18
+ } catch (error) {
19
+ await formatKernelError(error, isMachine(opts), undefined, opts.debug)
27
20
  process.exit(1)
28
- return
29
21
  }
30
22
 
31
23
  await runKernelCommand({
32
24
  opts,
33
- label: 'Node ' + path,
34
- fn: async (ctx) => {
35
- const node = await withSelfHint(() => bindGraph(ctx).get(path), meta)
36
- if (node === null) {
37
- log.error('node "' + path + '" not found or not visible')
38
- process.exit(1)
39
- }
40
- return node
41
- },
42
- format: (node, fmtOpts) => {
43
- output(opts.long ? node : cleanNode(node), fmtOpts)
44
- },
25
+ label: `Node ${path}`,
26
+ fn: ({ graph }) => withSelfHint(() => graph.getOrThrow(Path.parse(path)), meta),
27
+ format: (node, format) => output(node, format),
45
28
  })
46
29
  }
47
30
 
48
- function cleanNode(data: unknown): unknown {
49
- if (!data || typeof data !== 'object') return data
50
- const result: Record<string, unknown> = {}
51
- for (const [k, v] of Object.entries(data as Record<string, unknown>)) {
52
- if (INTERNAL_KEYS.has(k)) continue
53
- result[k] = v
54
- }
55
- return result
56
- }
57
-
58
31
  export default {
59
32
  name: 'get',
60
- description: 'Get one node by path or id',
61
- afterHelpText: [
62
- '',
63
- 'Behavior:',
64
- ' Point read through the kernel-client GraphApi.get shorthand. Accepts exactly',
65
- ' one node path (tree path /domain/class.Name, or an id @nodeId). @self is',
66
- ' expanded before dispatch. Missing and masked nodes share one opaque error:',
67
- ' node "<path>" not found or not visible.',
68
- '',
69
- ' Output is the historical flat wire node projection { id, class, path, props }.',
70
- ' Add -l to keep the internal fields (__labels, classId). Richer reads -',
71
- ' multiple roots, child expansion, edge expansion, cursors, or full GraphData -',
72
- ' live on astrale query.',
73
- '',
74
- 'Examples:',
75
- ' $ astrale get /kernel.astrale.ai/class.Root',
76
- ' $ astrale get @abc123 -l',
77
- ' $ astrale get @self --json',
78
- '',
79
- ].join('\n'),
80
- arguments: [{ name: 'path', description: 'Node path (/domain/Class) or ID (@nodeId)' }],
81
- options: [{ flags: '-l, --long', description: 'Include internal fields (__labels, classId)' }],
82
- action: async (path, opts) => {
83
- await getCommand(path as string, opts as GetOpts)
33
+ description: 'Get one canonical node by Path or ID',
34
+ afterHelpText: `
35
+ Behavior:
36
+ Resolves one exact Kernel V2 Path and prints the canonical Node
37
+ { id, class, props }. Missing and authorization-masked nodes remain
38
+ intentionally indistinguishable. @self is expanded before dispatch.
39
+
40
+ A Node does not contain a synthetic path, labels, or backend class ID.
41
+ Use astrale query for graph-shaped reads.
42
+
43
+ Examples:
44
+ $ astrale get /:notes.example.dev:class.Note
45
+ $ astrale get @abc123 --json
46
+ $ astrale get @self
47
+ `,
48
+ arguments: [{ name: 'target', description: 'Canonical Node Path or @id' }],
49
+ action: async (target, opts) => {
50
+ await getCommand(target as string, opts as GetOpts)
84
51
  },
85
52
  } satisfies CommandDefinition
@@ -0,0 +1,93 @@
1
+ import { issuer, jwk, provision } from '@astrale-os/sdk/auth'
2
+ import { ClassPath } from '@astrale-os/sdk/graph/class'
3
+ import { normalizeProperties } from '@astrale-os/sdk/graph/properties'
4
+ import { expect, mock, test } from 'bun:test'
5
+ import { exportJWK, generateKeyPair, jwtVerify } from 'jose'
6
+
7
+ import { formatIdentityRegistration, prepareIdentityProvision } from '../register'
8
+
9
+ /** @evidence TEST-CLI-IDENTITY-REGISTER-JSON-EXACT */
10
+ test('emits exactly one structured value for machine registration output', () => {
11
+ const writes: string[] = []
12
+ const logs: string[] = []
13
+ const originalWrite = process.stdout.write
14
+ const originalLog = console.log
15
+ process.stdout.write = mock((chunk: string | Uint8Array) => {
16
+ writes.push(typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk))
17
+ return true
18
+ }) as typeof process.stdout.write
19
+ console.log = mock((...values: unknown[]) => logs.push(values.map(String).join(' ')))
20
+
21
+ try {
22
+ formatIdentityRegistration(
23
+ { iss: 'https://identity.example', sub: 'self', nodeId: 'operator-node' },
24
+ { json: true },
25
+ true,
26
+ )
27
+ } finally {
28
+ process.stdout.write = originalWrite
29
+ console.log = originalLog
30
+ }
31
+
32
+ expect(JSON.parse(writes.join(''))).toEqual({
33
+ iss: 'https://identity.example',
34
+ sub: 'self',
35
+ nodeId: 'operator-node',
36
+ })
37
+ expect(logs).toEqual([])
38
+ })
39
+
40
+ test('builds one exact Mutation V3 identity birth bound to a self proof', async () => {
41
+ const { privateKey, publicKey } = await generateKeyPair('ES256', { extractable: true })
42
+ const privateJwk = { ...(await exportJWK(privateKey)), alg: 'ES256', kid: 'alice-key' }
43
+ const publicJwk = jwk.acceptPublic({
44
+ ...(await exportJWK(publicKey)),
45
+ alg: 'ES256',
46
+ kid: 'alice-key',
47
+ })
48
+ const kernelIssuer = issuer.accept('https://kernel.example')
49
+ const classPath = ClassPath.parse('/:accounts.example:class.User')
50
+ const properties = normalizeProperties({
51
+ 'accounts.example:class.User.property.name': 'Alice',
52
+ })
53
+
54
+ const prepared = await prepareIdentityProvision({
55
+ name: 'alice',
56
+ classPath,
57
+ properties,
58
+ privateKey: privateJwk,
59
+ publicKey: publicJwk,
60
+ kernelIssuer,
61
+ })
62
+
63
+ expect(String(prepared.binding)).toBe('identity')
64
+ expect(prepared.request.idempotencyKey).toBe('identity-register:alice')
65
+ expect(JSON.parse(JSON.stringify(prepared.request.mutation))).toEqual({
66
+ format: 'astrale.graph.mutation',
67
+ version: 'v3',
68
+ preconditions: [],
69
+ operations: [
70
+ {
71
+ op: 'node.create',
72
+ as: 'identity',
73
+ class: '/:accounts.example:class.User',
74
+ props: { 'accounts.example:class.User.property.name': 'Alice' },
75
+ },
76
+ ],
77
+ })
78
+
79
+ const credentials = prepared.request.identities[prepared.binding]?.credentials
80
+ expect(credentials?.publicKey).toEqual(publicJwk)
81
+ expect(typeof credentials?.proof).toBe('string')
82
+ if (typeof credentials?.proof !== 'string') throw new TypeError('Expected compact JWT proof')
83
+
84
+ const expectedFingerprint = await provision.fingerprint(prepared.request)
85
+ const expectedIssuer = await provision.selfIssuer(kernelIssuer, publicJwk)
86
+ const verified = await jwtVerify(credentials.proof, publicKey, {
87
+ algorithms: ['ES256'],
88
+ issuer: expectedIssuer,
89
+ subject: 'self',
90
+ audience: kernelIssuer,
91
+ })
92
+ expect(verified.payload.provision).toBe(expectedFingerprint)
93
+ })
@@ -1,6 +1,6 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
- import { createIdentity } from '../../lib/identity'
3
+ import { createIdentity } from '../../identity/index'
4
4
  import { fatal, log } from '../../lib/log'
5
5
 
6
6
  export default {
@@ -1,6 +1,6 @@
1
- import type { CommandDefinition } from '../../command'
1
+ import type { CommandDefinition } from '../../program/index'
2
2
 
3
- import { deleteIdentity } from '../../lib/identity'
3
+ import { deleteIdentity } from '../../identity/index'
4
4
  import { fatal, log } from '../../lib/log'
5
5
 
6
6
  export default {
@@ -1,10 +1,6 @@
1
- import { CompactEncrypt } from 'jose'
2
- import { chmod, readFile, writeFile } from 'node:fs/promises'
1
+ import type { CommandDefinition } from '../../program/index'
3
2
 
4
- import type { CommandDefinition } from '../../command'
5
-
6
- import { getIdentity } from '../../lib/identity'
7
- import { keypairPaths } from '../../lib/keys'
3
+ import { encodeIdentityExport, exportIdentity, writeIdentityExport } from '../../identity/index'
8
4
  import { fatal, log } from '../../lib/log'
9
5
  import { readPassphrase } from '../../lib/prompt'
10
6
 
@@ -25,33 +21,11 @@ export default {
25
21
  ],
26
22
  action: async (name: string, path: string, opts: { encrypt?: boolean }) => {
27
23
  try {
28
- const identity = await getIdentity(name)
29
- const { privatePath, publicPath } = keypairPaths(identity.subject)
30
- const [privateJwk, publicJwk] = await Promise.all([
31
- readFile(privatePath, 'utf-8').then(JSON.parse),
32
- readFile(publicPath, 'utf-8').then(JSON.parse),
33
- ])
34
- const envelope = {
35
- version: 1,
36
- subject: identity.subject,
37
- mode: identity.mode ?? 'local',
38
- kid: identity.kid,
39
- issuer: identity.issuer,
40
- privateJwk,
41
- publicJwk,
42
- }
43
- const plain = JSON.stringify(envelope, null, 2)
44
-
45
- if (opts.encrypt) {
46
- const passphrase = await readPassphrase('Passphrase (min 8 chars): ', { minLength: 8 })
47
- const enc = await new CompactEncrypt(new TextEncoder().encode(plain))
48
- .setProtectedHeader({ alg: 'PBES2-HS256+A128KW', enc: 'A256GCM' })
49
- .encrypt(new TextEncoder().encode(passphrase))
50
- await writeFile(path, enc)
51
- } else {
52
- await writeFile(path, plain)
53
- }
54
- await chmod(path, 0o600)
24
+ const envelope = await exportIdentity(name)
25
+ const passphrase = opts.encrypt
26
+ ? await readPassphrase('Passphrase (min 8 chars): ', { minLength: 8 })
27
+ : undefined
28
+ await writeIdentityExport(path, await encodeIdentityExport(envelope, passphrase))
55
29
 
56
30
  log.success(`Exported identity "${name}" → ${path}${opts.encrypt ? ' (encrypted)' : ''}`)
57
31
  if (!opts.encrypt) {