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

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 (310) hide show
  1. package/dist/astrale.js +62959 -41238
  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.test.ts +21 -68
  66. package/src/commands/__tests__/domain-install-owned.test.ts +26 -27
  67. package/src/commands/__tests__/domain-list.test.ts +2 -6
  68. package/src/commands/__tests__/logs.test.ts +50 -98
  69. package/src/commands/__tests__/ls.test.ts +21 -17
  70. package/src/commands/__tests__/read-commands.test.ts +171 -101
  71. package/src/commands/__tests__/view.test.ts +100 -51
  72. package/src/commands/admin/status.ts +3 -3
  73. package/src/commands/admin/use.ts +29 -6
  74. package/src/commands/auth/login.ts +1 -1
  75. package/src/commands/auth/logout.ts +4 -2
  76. package/src/commands/auth/status.ts +2 -2
  77. package/src/commands/auth/token.ts +2 -2
  78. package/src/commands/browser.ts +1 -1
  79. package/src/commands/call.ts +55 -91
  80. package/src/commands/describe.ts +52 -147
  81. package/src/commands/domain/install.ts +81 -50
  82. package/src/commands/domain/list.ts +4 -8
  83. package/src/commands/domain/publish.ts +9 -31
  84. package/src/commands/get.ts +32 -65
  85. package/src/commands/identity/__tests__/register.test.ts +93 -0
  86. package/src/commands/identity/create.ts +2 -2
  87. package/src/commands/identity/delete.ts +2 -2
  88. package/src/commands/identity/export.ts +7 -33
  89. package/src/commands/identity/import.ts +21 -70
  90. package/src/commands/identity/list.ts +2 -2
  91. package/src/commands/identity/register.ts +148 -117
  92. package/src/commands/identity/sync.ts +2 -2
  93. package/src/commands/identity/unsync.ts +2 -2
  94. package/src/commands/identity/use.ts +2 -2
  95. package/src/commands/identity/whoami.ts +2 -2
  96. package/src/commands/idp/add.ts +1 -1
  97. package/src/commands/idp/list.ts +1 -1
  98. package/src/commands/idp/refresh.ts +1 -1
  99. package/src/commands/idp/remove.ts +2 -2
  100. package/src/commands/idp/show.ts +1 -1
  101. package/src/commands/instance/active.ts +2 -2
  102. package/src/commands/instance/bookmark.ts +8 -2
  103. package/src/commands/instance/create.ts +1 -1
  104. package/src/commands/instance/delete.ts +4 -10
  105. package/src/commands/instance/forget.ts +1 -1
  106. package/src/commands/instance/list.ts +3 -3
  107. package/src/commands/instance/status.ts +5 -7
  108. package/src/commands/instance/use.ts +3 -3
  109. package/src/commands/logs.ts +152 -279
  110. package/src/commands/ls.ts +78 -204
  111. package/src/commands/mutate.ts +70 -139
  112. package/src/commands/query.ts +100 -278
  113. package/src/commands/session/analyze.ts +1 -1
  114. package/src/commands/session/list.ts +1 -1
  115. package/src/commands/setup.ts +1 -1
  116. package/src/commands/status.ts +1 -1
  117. package/src/commands/studio.ts +1 -1
  118. package/src/commands/token.ts +26 -21
  119. package/src/commands/update.ts +1 -1
  120. package/src/commands/use.ts +2 -3
  121. package/src/commands/view-serve.ts +1 -1
  122. package/src/commands/view.ts +67 -109
  123. package/src/connect-core.test.ts +38 -0
  124. package/src/connect-core.ts +42 -0
  125. package/src/connection/.spec/api.d.ts +111 -0
  126. package/src/connection/.spec/architecture.md +41 -0
  127. package/src/connection/.spec/capabilities/connection.ts +13 -0
  128. package/src/connection/.spec/examples/call.ts +7 -0
  129. package/src/connection/.spec/flows/command.ts +20 -0
  130. package/src/connection/.spec/flows/session.ts +61 -0
  131. package/src/connection/.spec/laws/connection.ts +181 -0
  132. package/src/connection/.spec/layout.ts +19 -0
  133. package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
  134. package/src/connection/__tests__/ca-fetch.test.ts +53 -0
  135. package/src/connection/__tests__/call.test.ts +17 -0
  136. package/src/connection/__tests__/credential.test.ts +109 -0
  137. package/src/connection/__tests__/errors.test.ts +96 -0
  138. package/src/connection/__tests__/exchange.test.ts +224 -0
  139. package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
  140. package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
  141. package/src/connection/__tests__/managed.test.ts +39 -0
  142. package/src/connection/__tests__/self.test.ts +51 -0
  143. package/src/connection/__tests__/session.test.ts +140 -0
  144. package/src/connection/__tests__/target.test.ts +93 -0
  145. package/src/{kernel → connection}/auth.ts +8 -8
  146. package/src/connection/call.ts +7 -0
  147. package/src/{kernel/run.ts → connection/command.ts} +22 -13
  148. package/src/connection/credential.ts +114 -0
  149. package/src/{kernel → connection}/errors.ts +32 -9
  150. package/src/connection/exchange.ts +274 -0
  151. package/src/connection/index.ts +8 -0
  152. package/src/connection/self.ts +120 -0
  153. package/src/connection/session.ts +176 -0
  154. package/src/connection/target.ts +111 -0
  155. package/src/connection/tsconfig.json +6 -0
  156. package/src/graph/.spec/api.d.ts +34 -0
  157. package/src/graph/.spec/laws/documents.ts +24 -0
  158. package/src/graph/.spec/laws/projection.ts +13 -0
  159. package/src/graph/.spec/layout.ts +6 -0
  160. package/src/graph/__tests__/mutation.test.ts +43 -0
  161. package/src/graph/__tests__/projection.test.ts +22 -0
  162. package/src/graph/__tests__/query.test.ts +154 -0
  163. package/src/graph/index.ts +3 -0
  164. package/src/graph/mutation.ts +18 -0
  165. package/src/graph/projection.ts +21 -0
  166. package/src/graph/query.ts +108 -0
  167. package/src/graph/tsconfig.json +6 -0
  168. package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
  169. package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
  170. package/src/identity/.spec/api.d.ts +124 -0
  171. package/src/identity/.spec/architecture.md +33 -0
  172. package/src/identity/.spec/capabilities/identity.ts +7 -0
  173. package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
  174. package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
  175. package/src/identity/.spec/flows/import.ts +29 -0
  176. package/src/identity/.spec/laws/identity.ts +56 -0
  177. package/src/identity/.spec/layout.ts +13 -0
  178. package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
  179. package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
  180. package/src/identity/__tests__/registration.test.ts +64 -0
  181. package/src/identity/__tests__/registry.test.ts +70 -0
  182. package/src/identity/__tests__/transfer.test.ts +198 -0
  183. package/src/identity/index.ts +23 -0
  184. package/src/identity/registration.ts +72 -0
  185. package/src/identity/registry.ts +172 -0
  186. package/src/identity/transfer.ts +187 -0
  187. package/src/identity/tsconfig.json +6 -0
  188. package/src/index.ts +1 -0
  189. package/src/keys/.spec/api.d.ts +74 -0
  190. package/src/keys/.spec/architecture.md +13 -0
  191. package/src/keys/.spec/laws/keys.ts +43 -0
  192. package/src/keys/.spec/layout.ts +6 -0
  193. package/src/keys/__tests__/algorithm.test.ts +39 -0
  194. package/src/keys/__tests__/keys.test.ts +168 -0
  195. package/src/keys/algorithm.ts +33 -0
  196. package/src/keys/credential.ts +112 -0
  197. package/src/keys/index.ts +14 -0
  198. package/src/keys/pair.ts +219 -0
  199. package/src/keys/tsconfig.json +6 -0
  200. package/src/lib/__tests__/admin-target.test.ts +67 -7
  201. package/src/lib/__tests__/binary.test.ts +2 -3
  202. package/src/lib/__tests__/command-dx.test.ts +2 -2
  203. package/src/lib/__tests__/config.test.ts +3 -2
  204. package/src/lib/__tests__/idp.test.ts +4 -6
  205. package/src/lib/__tests__/instance-target.test.ts +7 -4
  206. package/src/lib/__tests__/local-status.test.ts +1 -1
  207. package/src/lib/__tests__/log.test.ts +31 -0
  208. package/src/lib/__tests__/meta.test.ts +55 -0
  209. package/src/lib/__tests__/sdk-deps.test.ts +1 -1
  210. package/src/lib/__tests__/self.test.ts +8 -230
  211. package/src/lib/__tests__/use-target.test.ts +1 -1
  212. package/src/lib/__tests__/view-open-intent.test.ts +78 -104
  213. package/src/lib/__tests__/view-session.test.ts +99 -0
  214. package/src/lib/admin-domain.ts +39 -25
  215. package/src/lib/admin-instance.ts +38 -43
  216. package/src/lib/admin-target.ts +67 -13
  217. package/src/lib/binary.ts +13 -34
  218. package/src/lib/browser.ts +1 -1
  219. package/src/{kernel → lib}/ca-fetch.ts +35 -27
  220. package/src/lib/config.ts +1 -1
  221. package/src/lib/idp-session.ts +1 -1
  222. package/src/lib/idp.ts +3 -8
  223. package/src/lib/instance-target.ts +12 -8
  224. package/src/lib/instance.ts +34 -2
  225. package/src/lib/local-status.ts +2 -2
  226. package/src/lib/log.ts +3 -1
  227. package/src/lib/login-flow.ts +32 -5
  228. package/src/lib/meta.ts +15 -11
  229. package/src/lib/provision-instance.ts +4 -12
  230. package/src/lib/self.ts +5 -147
  231. package/src/lib/update.ts +1 -1
  232. package/src/lib/use-target.ts +1 -1
  233. package/src/lib/validation.ts +2 -2
  234. package/src/lib/view/open-intent.ts +7 -37
  235. package/src/lib/view/port-allocation.ts +1 -1
  236. package/src/lib/view/resolve.ts +52 -45
  237. package/src/lib/view/server.ts +16 -33
  238. package/src/lib/view/session.ts +51 -22
  239. package/src/paths/.spec/api.d.ts +13 -0
  240. package/src/paths/.spec/architecture.md +5 -0
  241. package/src/paths/.spec/layout.ts +3 -0
  242. package/src/paths/index.ts +13 -0
  243. package/src/program/.spec/api.d.ts +42 -0
  244. package/src/program/.spec/architecture.md +18 -0
  245. package/src/program/.spec/laws/program.ts +29 -0
  246. package/src/program/.spec/layout.ts +14 -0
  247. package/src/program/__tests__/program.test.ts +359 -0
  248. package/src/program/build.ts +190 -0
  249. package/src/program/command.ts +42 -0
  250. package/src/program/index.ts +2 -0
  251. package/src/program/options.ts +40 -0
  252. package/src/{registry.ts → program/registry.ts} +5 -1
  253. package/src/program/tsconfig.json +6 -0
  254. package/src/setup/steps/admin.ts +18 -3
  255. package/src/setup/steps/instance.ts +1 -1
  256. package/src/state/.spec/api.d.ts +133 -0
  257. package/src/state/.spec/architecture.md +31 -0
  258. package/src/state/.spec/capabilities/state.ts +7 -0
  259. package/src/state/.spec/flows/identity-update.ts +22 -0
  260. package/src/state/.spec/laws/state.ts +73 -0
  261. package/src/state/.spec/layout.ts +14 -0
  262. package/src/state/__tests__/exchange-credentials.test.ts +188 -0
  263. package/src/state/__tests__/files.test.ts +113 -0
  264. package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
  265. package/src/state/__tests__/identities.test.ts +142 -0
  266. package/src/state/__tests__/paths.test.ts +27 -0
  267. package/src/state/exchange-credentials.ts +219 -0
  268. package/src/state/files.ts +129 -0
  269. package/src/state/identities.ts +236 -0
  270. package/src/state/index.ts +29 -0
  271. package/src/state/paths.ts +61 -0
  272. package/src/state/tsconfig.json +6 -0
  273. package/src/telemetry/__tests__/redact.test.ts +3 -3
  274. package/src/telemetry/__tests__/trigger.test.ts +1 -1
  275. package/src/telemetry/settings.ts +1 -1
  276. package/src/telemetry/store.ts +1 -1
  277. package/src/test-utils.ts +1 -1
  278. package/studio/client/dist/assets/{elk-api-DVw5WDTH.js → elk-api-D0cBetPW.js} +1 -1
  279. package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
  280. package/studio/client/dist/assets/{index-Dl709Ra1.js → index-bVD2KJgz.js} +4 -4
  281. package/studio/client/dist/index.html +1 -1
  282. package/studio/package.json +10 -1
  283. package/studio/server/introspect/extractor.ts +1 -1
  284. package/studio/server/state/views.test.ts +3 -7
  285. package/studio/server/state/views.ts +23 -30
  286. package/tsconfig.json +1 -1
  287. package/viewer/dist/main.js +65 -39
  288. package/src/command.ts +0 -42
  289. package/src/commands/__tests__/help-contract.test.ts +0 -164
  290. package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
  291. package/src/kernel/__tests__/errors.test.ts +0 -43
  292. package/src/kernel/__tests__/expand.test.ts +0 -123
  293. package/src/kernel/client.ts +0 -214
  294. package/src/kernel/expand.ts +0 -192
  295. package/src/kernel/graph.ts +0 -96
  296. package/src/kernel/index.ts +0 -29
  297. package/src/kernel/options.ts +0 -22
  298. package/src/kernel/types.ts +0 -14
  299. package/src/lib/__tests__/domain-identity.test.ts +0 -60
  300. package/src/lib/__tests__/fs-atomic.test.ts +0 -104
  301. package/src/lib/__tests__/identity.test.ts +0 -79
  302. package/src/lib/__tests__/keys.test.ts +0 -129
  303. package/src/lib/domain-identity.ts +0 -49
  304. package/src/lib/env.ts +0 -49
  305. package/src/lib/fs-atomic.ts +0 -126
  306. package/src/lib/identity.ts +0 -256
  307. package/src/lib/keys.ts +0 -294
  308. package/src/lib/paths.ts +0 -11
  309. package/src/program.ts +0 -213
  310. 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,12 @@ 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
23
+ /** Exact output of the public Kernel install syscall for one requested root. */
24
+ type DirectInstallResult = readonly {
22
25
  origin: string
23
- instanceId: string
24
- url: string
25
- ok: boolean
26
- error?: string | null
27
- }
28
-
29
- /** Mirrors the kernel's `Domain.install` return. */
30
- type DirectInstallResult = { domainId: string; origin: string }
26
+ revision: string
27
+ etag?: string
28
+ }[]
31
29
 
32
30
  type InstallOpts = KernelCommandOpts &
33
31
  AdminTargetCommandOpts & {
@@ -50,8 +48,8 @@ Behavior:
50
48
  interactively. The target instance is the active one, or -i <slug>; it must be
51
49
  admin-managed (otherwise the command fails loudly and points you at --direct).
52
50
 
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
51
+ --direct: installs a url through the public Kernel install syscall,
52
+ bypassing the admin catalog. Works on any instance you can authenticate to
55
53
  (managed, bookmarked, or local), using your own authority. This is the only
56
54
  mode that runs the identity-override consent gate: when the domain's declared
57
55
  origin differs from its serving host, it requires explicit consent (an
@@ -108,7 +106,12 @@ Examples:
108
106
  * an admin-kernel override). The admin kernel is chosen via --admin/--admin-url
109
107
  * or config, exactly like `domain publish`.
110
108
  */
111
- async function installViaAdmin(target: string | undefined, opts: InstallOpts): Promise<void> {
109
+ export async function installViaAdmin(
110
+ target: string | undefined,
111
+ opts: InstallOpts,
112
+ dependencies: Partial<AdminInstallDependencies> = {},
113
+ ): Promise<void> {
114
+ const admin = { ...defaultAdminInstallDependencies, ...dependencies }
112
115
  const interactive = !!process.stdin.isTTY && !(opts.ci || opts.noPrompt || process.env.CI)
113
116
  if (!target && !interactive) {
114
117
  fatal(
@@ -121,13 +124,17 @@ async function installViaAdmin(target: string | undefined, opts: InstallOpts): P
121
124
  }
122
125
  // Strip the install-target selector so the admin client resolves only the
123
126
  // admin kernel (via --admin/--admin-url/config), not the instance under `-i`.
124
- const adminOpts: InstallOpts = { ...opts, instance: undefined }
127
+ const adminOpts = {
128
+ admin: opts.admin,
129
+ adminUrl: opts.adminUrl,
130
+ timeout: opts.timeout,
131
+ as: opts.as,
132
+ creds: opts.creds,
133
+ }
125
134
 
126
135
  try {
127
- await withAdminKernelClient(adminOpts, async (ctx) => {
128
- const instances = await callOwnedInstances(ctx.client)
129
-
130
- const ref = await resolveDomainRef(ctx, target, interactive)
136
+ await withAdminClientSession(adminOpts, async (ctx) => {
137
+ const instances = await admin.listInstances(ctx)
131
138
  const slug = await resolveTargetSlug(opts, target, interactive, instances)
132
139
 
133
140
  const match = instances.find((i) => i.slug === slug)
@@ -141,15 +148,14 @@ async function installViaAdmin(target: string | undefined, opts: InstallOpts): P
141
148
  }
142
149
  assertInstallTargetReady(match)
143
150
 
144
- const label = ref.origin ?? ref.url ?? 'domain'
151
+ const domains = await admin.listDomains(ctx)
152
+ const domain = await resolveDomain(domains, target, interactive)
153
+
154
+ const label = domain.origin
145
155
  const result = await withSpinner(
146
156
  `Installing ${label} on ${match.slug}`,
147
157
  !isMachine(opts),
148
- () =>
149
- ctx.client.call(adminDomainMethod('install'), {
150
- instanceId: match.slug,
151
- ...ref,
152
- }) as Promise<DomainInstallResult>,
158
+ () => admin.install(ctx, match, domain),
153
159
  { success: (r) => `Installed ${r.origin} on ${match.slug}` },
154
160
  )
155
161
 
@@ -193,15 +199,25 @@ export function domainRefFromTarget(target: string): { origin?: string; url?: st
193
199
  }
194
200
 
195
201
  /** 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> } },
202
+ async function resolveDomain(
203
+ catalog: readonly DomainInfo[],
198
204
  target: string | undefined,
199
205
  interactive: boolean,
200
- ): Promise<{ origin?: string; url?: string }> {
201
- if (target) return domainRefFromTarget(target)
206
+ ): Promise<DomainInfo> {
207
+ if (target) {
208
+ const ref = domainRefFromTarget(target)
209
+ const found = catalog.find((domain) =>
210
+ ref.origin === undefined ? domain.url === ref.url : domain.origin === ref.origin,
211
+ )
212
+ if (found !== undefined) return found
213
+ throw new AstraleError(
214
+ 'DOMAIN_NOT_FOUND',
215
+ `No published domain matches "${target}".`,
216
+ 'Run `astrale domain list` to see the Admin catalog.',
217
+ )
218
+ }
202
219
  if (!interactive) throw new AstraleError('MISSING_ARG', 'No domain given.')
203
220
 
204
- const catalog = (await ctx.client.call(adminDomainMethod('list'), {})) as DomainInfo[]
205
221
  const installable = catalog.filter((d) => d.url)
206
222
  if (installable.length === 0) {
207
223
  throw new AstraleError(
@@ -218,9 +234,21 @@ async function resolveDomainRef(
218
234
  })),
219
235
  )
220
236
  if (!origin) throw new AstraleError('CANCELLED', 'No domain selected.')
221
- return { origin }
237
+ return catalog.find((domain) => domain.origin === origin)!
222
238
  }
223
239
 
240
+ interface AdminInstallDependencies {
241
+ readonly listInstances: typeof listOwnedInstancesInContext
242
+ readonly listDomains: typeof listAdminDomainsInContext
243
+ readonly install: typeof installAdminDomainInContext
244
+ }
245
+
246
+ const defaultAdminInstallDependencies: AdminInstallDependencies = Object.freeze({
247
+ listInstances: listOwnedInstancesInContext,
248
+ listDomains: listAdminDomainsInContext,
249
+ install: installAdminDomainInContext,
250
+ })
251
+
224
252
  /**
225
253
  * Resolve the target instance slug: `-i`, else the active instance. When run
226
254
  * bare (no positional) in a TTY, prompt for it with the active instance
@@ -271,8 +299,8 @@ async function activeSlug(): Promise<string | undefined> {
271
299
  // ── Direct path (--direct) ────────────────────────────────────────────────────
272
300
 
273
301
  /**
274
- * Install a url straight onto the instance kernel (`Domain.install`),
275
- * bypassing the admin/catalog — the classic path, with the §5 identity-override
302
+ * Install a url through the public Kernel install syscall,
303
+ * bypassing the admin catalog, with the identity-override
276
304
  * consent gate. Works on any instance the caller can authenticate to.
277
305
  */
278
306
  async function installDirect(target: string | undefined, opts: InstallOpts): Promise<void> {
@@ -300,26 +328,29 @@ async function installDirect(target: string | undefined, opts: InstallOpts): Pro
300
328
  await runKernelCommand<DirectInstallResult>({
301
329
  opts,
302
330
  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,
331
+ fn: async ({ session }) =>
332
+ (await session.call(
333
+ createPathCall(Path.project(syscalls.install.ref).raw, {
334
+ domains: [{ url, ...(opts.token ? { token: opts.token } : {}) }],
335
+ }),
336
+ )) as DirectInstallResult,
308
337
  format: (result, fmtOpts, isRaw) => {
309
338
  if (isRaw) {
310
339
  output(result, fmtOpts)
311
340
  return
312
341
  }
313
- log.success(`Domain installed: ${result.origin}`)
314
- log.dim(` domainId: ${result.domainId}`)
342
+ const installed = result[0]
343
+ if (!installed) throw new Error('Kernel install returned no installed Domain receipt.')
344
+ log.success(`Domain installed: ${installed.origin}@${installed.revision}`)
345
+ if (installed.etag) log.dim(` publication: ${installed.etag}`)
315
346
  // Belt-and-braces: the kernel-confirmed origin is authoritative. If it
316
347
  // aliases the host and the pre-install gate never consented to THAT
317
348
  // origin (lying or absent `/meta`), say so loudly after the fact.
318
- if (isIdentityOverride(result.origin, host) && result.origin !== consentedOrigin) {
349
+ if (isIdentityOverride(installed.origin, host) && installed.origin !== consentedOrigin) {
319
350
  log.warn(
320
- `Installed origin "${result.origin}" differs from the serving host "${host}" ` +
351
+ `Installed origin "${installed.origin}" differs from the serving host "${host}" ` +
321
352
  `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}.`,
353
+ `Every ${installed.origin}/* call on this instance now routes to ${host}.`,
323
354
  )
324
355
  }
325
356
  },
@@ -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'
3
+ import type { KernelCommandOpts } from '../connection'
4
+ import type { CommandDefinition } from '../program/index'
5
+
6
+ import { expandSelfInPath, runKernelCommand, withSelfHint } from '../connection'
5
7
  import { log } from '../lib/log'
6
8
  import { output } from '../lib/output'
7
9
 
8
- type GetOpts = KernelCommandOpts & {
9
- long?: boolean
10
- }
11
-
12
- const INTERNAL_KEYS = new Set(['__labels', 'classId'])
13
-
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
+ log.error(error instanceof Error ? error.message : 'Invalid target')
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) {