@astrale-os/cli 0.8.0-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 (317) hide show
  1. package/README.md +4 -1
  2. package/dist/astrale.js +62968 -41240
  3. package/dist/public/connect-core.js +55030 -0
  4. package/dist/public/keys/index.js +45631 -0
  5. package/dist/public/paths/index.js +43990 -0
  6. package/dist/types/admin/instance/model.d.ts +40 -0
  7. package/dist/types/connect-core.d.ts +22 -0
  8. package/dist/types/connection/auth.d.ts +33 -0
  9. package/dist/types/errors.d.ts +23 -0
  10. package/dist/types/identity/registry.d.ts +25 -0
  11. package/dist/types/keys/algorithm.d.ts +6 -0
  12. package/dist/types/keys/credential.d.ts +21 -0
  13. package/dist/types/keys/index.d.ts +4 -0
  14. package/dist/types/keys/pair.d.ts +34 -0
  15. package/dist/types/lib/admin-target.d.ts +74 -0
  16. package/dist/types/lib/ca-fetch.d.ts +2 -0
  17. package/dist/types/lib/config.d.ts +71 -0
  18. package/dist/types/lib/format.d.ts +1 -0
  19. package/dist/types/lib/idp-session.d.ts +33 -0
  20. package/dist/types/lib/idp.d.ts +397 -0
  21. package/dist/types/lib/instance-target.d.ts +36 -0
  22. package/dist/types/lib/instance.d.ts +186 -0
  23. package/dist/types/lib/log.d.ts +30 -0
  24. package/dist/types/lib/login-flow.d.ts +49 -0
  25. package/dist/types/lib/meta.d.ts +36 -0
  26. package/dist/types/lib/output.d.ts +83 -0
  27. package/dist/types/lib/table.d.ts +18 -0
  28. package/dist/types/lib/validation.d.ts +11 -0
  29. package/dist/types/paths/index.d.ts +2 -0
  30. package/dist/types/state/exchange-credentials.d.ts +25 -0
  31. package/dist/types/state/files.d.ts +9 -0
  32. package/dist/types/state/identities.d.ts +42 -0
  33. package/dist/types/state/index.d.ts +8 -0
  34. package/dist/types/state/paths.d.ts +32 -0
  35. package/package.json +30 -8
  36. package/src/admin/.spec/api.d.ts +3 -0
  37. package/src/admin/.spec/architecture.md +15 -0
  38. package/src/admin/.spec/icon.svg +3 -0
  39. package/src/admin/.spec/layout.ts +6 -0
  40. package/src/admin/catalog/.spec/api.d.ts +65 -0
  41. package/src/admin/catalog/.spec/architecture.md +6 -0
  42. package/src/admin/catalog/.spec/icon.svg +3 -0
  43. package/src/admin/catalog/.spec/layout.ts +6 -0
  44. package/src/admin/catalog/__tests__/client.test.ts +171 -0
  45. package/src/admin/catalog/client.ts +214 -0
  46. package/src/admin/catalog/index.ts +13 -0
  47. package/src/admin/catalog/model.ts +42 -0
  48. package/src/admin/catalog/tsconfig.json +8 -0
  49. package/src/admin/graph/.spec/api.d.ts +28 -0
  50. package/src/admin/graph/.spec/architecture.md +5 -0
  51. package/src/admin/graph/.spec/icon.svg +3 -0
  52. package/src/admin/graph/.spec/layout.ts +3 -0
  53. package/src/admin/graph/index.ts +6 -0
  54. package/src/admin/graph/nodes.ts +56 -0
  55. package/src/admin/index.ts +2 -0
  56. package/src/admin/instance/.spec/api.d.ts +66 -0
  57. package/src/admin/instance/.spec/architecture.md +10 -0
  58. package/src/admin/instance/.spec/icon.svg +3 -0
  59. package/src/admin/instance/.spec/layout.ts +6 -0
  60. package/src/admin/instance/__tests__/client.test.ts +289 -0
  61. package/src/admin/instance/client.ts +298 -0
  62. package/src/admin/instance/index.ts +16 -0
  63. package/src/admin/instance/model.ts +65 -0
  64. package/src/admin/instance/tsconfig.json +8 -0
  65. package/src/commands/__tests__/admin-instance.test.ts +3 -36
  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__/logs.test.ts +50 -98
  70. package/src/commands/__tests__/ls.test.ts +21 -17
  71. package/src/commands/__tests__/read-commands.test.ts +171 -101
  72. package/src/commands/__tests__/view.test.ts +100 -51
  73. package/src/commands/admin/status.ts +3 -3
  74. package/src/commands/admin/use.ts +29 -6
  75. package/src/commands/auth/login.ts +1 -1
  76. package/src/commands/auth/logout.ts +4 -2
  77. package/src/commands/auth/status.ts +2 -2
  78. package/src/commands/auth/token.ts +2 -2
  79. package/src/commands/browser.ts +1 -1
  80. package/src/commands/call.ts +55 -91
  81. package/src/commands/describe.ts +52 -147
  82. package/src/commands/domain/install.ts +81 -50
  83. package/src/commands/domain/list.ts +4 -8
  84. package/src/commands/domain/publish.ts +9 -31
  85. package/src/commands/get.ts +32 -65
  86. package/src/commands/identity/__tests__/register.test.ts +93 -0
  87. package/src/commands/identity/create.ts +2 -2
  88. package/src/commands/identity/delete.ts +2 -2
  89. package/src/commands/identity/export.ts +7 -33
  90. package/src/commands/identity/import.ts +21 -70
  91. package/src/commands/identity/list.ts +2 -2
  92. package/src/commands/identity/register.ts +148 -117
  93. package/src/commands/identity/sync.ts +2 -2
  94. package/src/commands/identity/unsync.ts +2 -2
  95. package/src/commands/identity/use.ts +2 -2
  96. package/src/commands/identity/whoami.ts +2 -2
  97. package/src/commands/idp/add.ts +1 -1
  98. package/src/commands/idp/list.ts +1 -1
  99. package/src/commands/idp/refresh.ts +1 -1
  100. package/src/commands/idp/remove.ts +2 -2
  101. package/src/commands/idp/show.ts +1 -1
  102. package/src/commands/instance/active.ts +2 -2
  103. package/src/commands/instance/bookmark.ts +8 -2
  104. package/src/commands/instance/create.ts +1 -1
  105. package/src/commands/instance/delete.ts +4 -10
  106. package/src/commands/instance/forget.ts +1 -1
  107. package/src/commands/instance/list.ts +3 -3
  108. package/src/commands/instance/status.ts +5 -7
  109. package/src/commands/instance/use.ts +3 -3
  110. package/src/commands/logs.ts +152 -279
  111. package/src/commands/ls.ts +78 -204
  112. package/src/commands/mutate.ts +70 -139
  113. package/src/commands/query.ts +100 -278
  114. package/src/commands/session/analyze.ts +1 -1
  115. package/src/commands/session/list.ts +1 -1
  116. package/src/commands/setup.ts +1 -1
  117. package/src/commands/status.ts +1 -1
  118. package/src/commands/studio.ts +1 -1
  119. package/src/commands/token.ts +26 -21
  120. package/src/commands/update.ts +1 -1
  121. package/src/commands/use.ts +2 -3
  122. package/src/commands/view-serve.ts +1 -1
  123. package/src/commands/view.ts +67 -109
  124. package/src/connect-core.test.ts +38 -0
  125. package/src/connect-core.ts +42 -0
  126. package/src/connection/.spec/api.d.ts +111 -0
  127. package/src/connection/.spec/architecture.md +41 -0
  128. package/src/connection/.spec/capabilities/connection.ts +13 -0
  129. package/src/connection/.spec/examples/call.ts +7 -0
  130. package/src/connection/.spec/flows/command.ts +20 -0
  131. package/src/connection/.spec/flows/session.ts +61 -0
  132. package/src/connection/.spec/laws/connection.ts +181 -0
  133. package/src/connection/.spec/layout.ts +19 -0
  134. package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
  135. package/src/connection/__tests__/ca-fetch.test.ts +53 -0
  136. package/src/connection/__tests__/call.test.ts +17 -0
  137. package/src/connection/__tests__/credential.test.ts +109 -0
  138. package/src/connection/__tests__/errors.test.ts +96 -0
  139. package/src/connection/__tests__/exchange.test.ts +224 -0
  140. package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
  141. package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
  142. package/src/connection/__tests__/managed.test.ts +39 -0
  143. package/src/connection/__tests__/self.test.ts +51 -0
  144. package/src/connection/__tests__/session.test.ts +140 -0
  145. package/src/connection/__tests__/target.test.ts +93 -0
  146. package/src/{kernel → connection}/auth.ts +8 -8
  147. package/src/connection/call.ts +7 -0
  148. package/src/{kernel/run.ts → connection/command.ts} +22 -13
  149. package/src/connection/credential.ts +114 -0
  150. package/src/{kernel → connection}/errors.ts +32 -9
  151. package/src/connection/exchange.ts +274 -0
  152. package/src/connection/index.ts +8 -0
  153. package/src/connection/self.ts +120 -0
  154. package/src/connection/session.ts +176 -0
  155. package/src/connection/target.ts +111 -0
  156. package/src/connection/tsconfig.json +6 -0
  157. package/src/graph/.spec/api.d.ts +34 -0
  158. package/src/graph/.spec/laws/documents.ts +24 -0
  159. package/src/graph/.spec/laws/projection.ts +13 -0
  160. package/src/graph/.spec/layout.ts +6 -0
  161. package/src/graph/__tests__/mutation.test.ts +43 -0
  162. package/src/graph/__tests__/projection.test.ts +22 -0
  163. package/src/graph/__tests__/query.test.ts +154 -0
  164. package/src/graph/index.ts +3 -0
  165. package/src/graph/mutation.ts +18 -0
  166. package/src/graph/projection.ts +21 -0
  167. package/src/graph/query.ts +108 -0
  168. package/src/graph/tsconfig.json +6 -0
  169. package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
  170. package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
  171. package/src/identity/.spec/api.d.ts +124 -0
  172. package/src/identity/.spec/architecture.md +33 -0
  173. package/src/identity/.spec/capabilities/identity.ts +7 -0
  174. package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
  175. package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
  176. package/src/identity/.spec/flows/import.ts +29 -0
  177. package/src/identity/.spec/laws/identity.ts +56 -0
  178. package/src/identity/.spec/layout.ts +13 -0
  179. package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
  180. package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
  181. package/src/identity/__tests__/registration.test.ts +64 -0
  182. package/src/identity/__tests__/registry.test.ts +70 -0
  183. package/src/identity/__tests__/transfer.test.ts +198 -0
  184. package/src/identity/index.ts +23 -0
  185. package/src/identity/registration.ts +72 -0
  186. package/src/identity/registry.ts +172 -0
  187. package/src/identity/transfer.ts +187 -0
  188. package/src/identity/tsconfig.json +6 -0
  189. package/src/index.ts +1 -0
  190. package/src/keys/.spec/api.d.ts +74 -0
  191. package/src/keys/.spec/architecture.md +13 -0
  192. package/src/keys/.spec/laws/keys.ts +43 -0
  193. package/src/keys/.spec/layout.ts +6 -0
  194. package/src/keys/__tests__/algorithm.test.ts +39 -0
  195. package/src/keys/__tests__/keys.test.ts +168 -0
  196. package/src/keys/algorithm.ts +33 -0
  197. package/src/keys/credential.ts +112 -0
  198. package/src/keys/index.ts +14 -0
  199. package/src/keys/pair.ts +219 -0
  200. package/src/keys/tsconfig.json +6 -0
  201. package/src/lib/__tests__/admin-target.test.ts +67 -7
  202. package/src/lib/__tests__/binary.test.ts +2 -3
  203. package/src/lib/__tests__/command-dx.test.ts +2 -2
  204. package/src/lib/__tests__/config.test.ts +3 -2
  205. package/src/lib/__tests__/idp.test.ts +4 -6
  206. package/src/lib/__tests__/instance-target.test.ts +7 -4
  207. package/src/lib/__tests__/local-status.test.ts +1 -1
  208. package/src/lib/__tests__/log.test.ts +31 -0
  209. package/src/lib/__tests__/meta.test.ts +55 -0
  210. package/src/lib/__tests__/sdk-deps.test.ts +1 -1
  211. package/src/lib/__tests__/self.test.ts +8 -230
  212. package/src/lib/__tests__/use-target.test.ts +1 -1
  213. package/src/lib/__tests__/view-open-intent.test.ts +78 -104
  214. package/src/lib/__tests__/view-session.test.ts +99 -0
  215. package/src/lib/admin-domain.ts +39 -25
  216. package/src/lib/admin-instance.ts +38 -43
  217. package/src/lib/admin-target.ts +67 -13
  218. package/src/lib/binary.ts +13 -34
  219. package/src/lib/browser.ts +1 -1
  220. package/src/{kernel → lib}/ca-fetch.ts +35 -27
  221. package/src/lib/config.ts +1 -1
  222. package/src/lib/idp-session.ts +1 -1
  223. package/src/lib/idp.ts +3 -8
  224. package/src/lib/instance-target.ts +12 -8
  225. package/src/lib/instance.ts +34 -2
  226. package/src/lib/local-status.ts +2 -2
  227. package/src/lib/log.ts +3 -1
  228. package/src/lib/login-flow.ts +32 -5
  229. package/src/lib/meta.ts +15 -11
  230. package/src/lib/provision-instance.ts +4 -12
  231. package/src/lib/self.ts +5 -147
  232. package/src/lib/update.ts +1 -1
  233. package/src/lib/use-target.ts +1 -1
  234. package/src/lib/validation.ts +2 -2
  235. package/src/lib/view/open-intent.ts +7 -37
  236. package/src/lib/view/port-allocation.ts +1 -1
  237. package/src/lib/view/resolve.ts +52 -45
  238. package/src/lib/view/server.ts +16 -33
  239. package/src/lib/view/session.ts +51 -22
  240. package/src/paths/.spec/api.d.ts +13 -0
  241. package/src/paths/.spec/architecture.md +5 -0
  242. package/src/paths/.spec/layout.ts +3 -0
  243. package/src/paths/index.ts +13 -0
  244. package/src/program/.spec/api.d.ts +42 -0
  245. package/src/program/.spec/architecture.md +18 -0
  246. package/src/program/.spec/laws/program.ts +29 -0
  247. package/src/program/.spec/layout.ts +14 -0
  248. package/src/program/__tests__/program.test.ts +359 -0
  249. package/src/program/build.ts +190 -0
  250. package/src/program/command.ts +42 -0
  251. package/src/program/index.ts +2 -0
  252. package/src/program/options.ts +40 -0
  253. package/src/{registry.ts → program/registry.ts} +5 -1
  254. package/src/program/tsconfig.json +6 -0
  255. package/src/setup/steps/admin.ts +18 -3
  256. package/src/setup/steps/instance.ts +1 -1
  257. package/src/state/.spec/api.d.ts +133 -0
  258. package/src/state/.spec/architecture.md +31 -0
  259. package/src/state/.spec/capabilities/state.ts +7 -0
  260. package/src/state/.spec/flows/identity-update.ts +22 -0
  261. package/src/state/.spec/laws/state.ts +73 -0
  262. package/src/state/.spec/layout.ts +14 -0
  263. package/src/state/__tests__/exchange-credentials.test.ts +188 -0
  264. package/src/state/__tests__/files.test.ts +113 -0
  265. package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
  266. package/src/state/__tests__/identities.test.ts +142 -0
  267. package/src/state/__tests__/paths.test.ts +27 -0
  268. package/src/state/exchange-credentials.ts +219 -0
  269. package/src/state/files.ts +129 -0
  270. package/src/state/identities.ts +236 -0
  271. package/src/state/index.ts +29 -0
  272. package/src/state/paths.ts +61 -0
  273. package/src/state/tsconfig.json +6 -0
  274. package/src/telemetry/__tests__/redact.test.ts +3 -3
  275. package/src/telemetry/__tests__/trigger.test.ts +1 -1
  276. package/src/telemetry/settings.ts +1 -1
  277. package/src/telemetry/store.ts +1 -1
  278. package/src/test-utils.ts +1 -1
  279. package/studio/client/dist/assets/elk-api-D0cBetPW.js +1 -0
  280. package/studio/client/dist/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  281. package/studio/client/dist/assets/index-BQnJ5sgd.css +1 -0
  282. package/studio/client/dist/assets/index-ChOr3yP0.css +1 -0
  283. package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
  284. package/studio/client/dist/assets/index-bVD2KJgz.js +8 -0
  285. package/studio/client/dist/index.html +2 -2
  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/studio/tsconfig.json +23 -0
  291. package/tsconfig.json +1 -1
  292. package/viewer/dist/main.js +65 -39
  293. package/viewer/tsconfig.json +13 -0
  294. package/src/command.ts +0 -42
  295. package/src/commands/__tests__/help-contract.test.ts +0 -164
  296. package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
  297. package/src/kernel/__tests__/errors.test.ts +0 -43
  298. package/src/kernel/__tests__/expand.test.ts +0 -123
  299. package/src/kernel/client.ts +0 -214
  300. package/src/kernel/expand.ts +0 -192
  301. package/src/kernel/graph.ts +0 -96
  302. package/src/kernel/index.ts +0 -29
  303. package/src/kernel/options.ts +0 -22
  304. package/src/kernel/types.ts +0 -14
  305. package/src/lib/__tests__/domain-identity.test.ts +0 -60
  306. package/src/lib/__tests__/fs-atomic.test.ts +0 -104
  307. package/src/lib/__tests__/identity.test.ts +0 -79
  308. package/src/lib/__tests__/keys.test.ts +0 -129
  309. package/src/lib/domain-identity.ts +0 -49
  310. package/src/lib/env.ts +0 -49
  311. package/src/lib/fs-atomic.ts +0 -126
  312. package/src/lib/identity.ts +0 -256
  313. package/src/lib/keys.ts +0 -294
  314. package/src/lib/paths.ts +0 -11
  315. package/src/program.ts +0 -213
  316. package/studio/client/dist/assets/index-BaqEuIQJ.js +0 -109
  317. package/studio/client/dist/assets/index-jRdExahh.css +0 -1
@@ -0,0 +1,176 @@
1
+ import type { AuthApi } from '@astrale-os/kernel-client/auth'
2
+ import type { GraphApi } from '@astrale-os/kernel-client/graph'
3
+ import type { ClientSessionOptions, SessionAuth } from '@astrale-os/kernel-client/session'
4
+
5
+ import { call } from '@astrale-os/kernel-client'
6
+ import { createAuth } from '@astrale-os/kernel-client/auth'
7
+ import { createGraph } from '@astrale-os/kernel-client/graph'
8
+ import { ClientSession } from '@astrale-os/kernel-client/session'
9
+
10
+ import type { AstraleConfig } from '../lib/config'
11
+ import type { AdminConnectionOptions, ConnectionOptions, ConnectionTarget } from './target'
12
+
13
+ import {
14
+ AdminInstanceNotFoundError,
15
+ connectAdminInstances,
16
+ findOwnedInstance,
17
+ type InstanceInfo,
18
+ } from '../admin/instance'
19
+ import { AstraleError } from '../errors'
20
+ import { fetchWithCaFile } from '../lib/ca-fetch'
21
+ import { readConfig } from '../lib/config'
22
+ import { createCliCredential, validateCredentialSelection } from './credential'
23
+ import { resolveAdminConnectionTarget, resolveConnectionTarget } from './target'
24
+
25
+ const DEFAULT_TIMEOUT_MS = 30_000
26
+ const MAXIMUM_ROUTE_AGE_MS = 5 * 60_000
27
+
28
+ export interface ConnectionContext {
29
+ readonly session: ClientSession
30
+ readonly graph: GraphApi
31
+ readonly auth: AuthApi
32
+ readonly target: ConnectionTarget
33
+ }
34
+
35
+ interface OwnedConnection {
36
+ readonly context: ConnectionContext
37
+ close(): void
38
+ }
39
+
40
+ export type ConnectionFactory = (
41
+ target: ConnectionTarget,
42
+ timeoutMs: number,
43
+ options: ConnectionOptions,
44
+ config: AstraleConfig,
45
+ ) => OwnedConnection
46
+
47
+ /** Resolve one ordinary target, run a command action, then close terminally. */
48
+ export async function withClientSession<Value>(
49
+ options: ConnectionOptions,
50
+ action: (context: ConnectionContext) => Promise<Value>,
51
+ ): Promise<Value> {
52
+ validateCredentialSelection(options)
53
+ const timeoutMs = resolveTimeoutMs(options.timeout)
54
+ const config = await readConfig()
55
+ const target = await resolveConnectionTarget(options, config, {
56
+ managed: (slug) => lookupManagedInstance(slug, options),
57
+ })
58
+ return runResolvedClientSession(target, timeoutMs, options, config, action, openConnection)
59
+ }
60
+
61
+ /** Resolve the configured Admin Domain target under the same terminal lifecycle. */
62
+ export async function withAdminClientSession<Value>(
63
+ options: AdminConnectionOptions,
64
+ action: (context: ConnectionContext) => Promise<Value>,
65
+ ): Promise<Value> {
66
+ validateCredentialSelection(options)
67
+ const timeoutMs = resolveTimeoutMs(options.timeout)
68
+ const config = await readConfig()
69
+ const target = await resolveAdminConnectionTarget(options, config)
70
+ return runResolvedClientSession(target, timeoutMs, options, config, action, openConnection)
71
+ }
72
+
73
+ /** Owner-private seam used to prove validation order and cleanup without network I/O. */
74
+ export async function withResolvedClientSession<Value>(
75
+ target: ConnectionTarget,
76
+ options: ConnectionOptions,
77
+ config: AstraleConfig,
78
+ action: (context: ConnectionContext) => Promise<Value>,
79
+ open: ConnectionFactory = openConnection,
80
+ ): Promise<Value> {
81
+ validateCredentialSelection(options)
82
+ const timeoutMs = resolveTimeoutMs(options.timeout)
83
+ return runResolvedClientSession(target, timeoutMs, options, config, action, open)
84
+ }
85
+
86
+ async function runResolvedClientSession<Value>(
87
+ target: ConnectionTarget,
88
+ timeoutMs: number,
89
+ options: ConnectionOptions,
90
+ config: AstraleConfig,
91
+ action: (context: ConnectionContext) => Promise<Value>,
92
+ open: ConnectionFactory,
93
+ ): Promise<Value> {
94
+ const connection = open(target, timeoutMs, options, config)
95
+ try {
96
+ return await action(connection.context)
97
+ } catch (error) {
98
+ if (error instanceof Error) (error as Error & { url?: string }).url = target.url
99
+ throw error
100
+ } finally {
101
+ connection.close()
102
+ }
103
+ }
104
+
105
+ export function resolveTimeoutMs(raw: string | undefined): number {
106
+ if (raw === undefined) return DEFAULT_TIMEOUT_MS
107
+ if (!/^\d+$/.test(raw)) {
108
+ throw new AstraleError(
109
+ 'INVALID_FLAG',
110
+ `Invalid --timeout value "${raw}" — expected a positive integer (milliseconds)`,
111
+ )
112
+ }
113
+ const value = Number.parseInt(raw, 10)
114
+ if (!Number.isSafeInteger(value) || value <= 0) {
115
+ throw new AstraleError(
116
+ 'INVALID_FLAG',
117
+ `Invalid --timeout value "${raw}" — must be a positive integer`,
118
+ )
119
+ }
120
+ return value
121
+ }
122
+
123
+ function openConnection(
124
+ target: ConnectionTarget,
125
+ timeoutMs: number,
126
+ options: ConnectionOptions,
127
+ config: AstraleConfig,
128
+ ): OwnedConnection {
129
+ const fetch = target.caFile === undefined ? globalThis.fetch : fetchWithCaFile(target.caFile)
130
+ const auth = createCliCredential(target, options, config, fetch, timeoutMs)
131
+ const session = new ClientSession(createClientSessionOptions(target, fetch, auth, timeoutMs))
132
+ const graph = createGraph((call, request) => session.call(call, request))
133
+ const authApi = createAuth((path, input, request) => session.call(call(path, input), request))
134
+ return {
135
+ context: Object.freeze({ session, graph, auth: authApi, target }),
136
+ close() {
137
+ session.close()
138
+ },
139
+ }
140
+ }
141
+
142
+ /** Owner-private construction seam proving that transport and source identity stay distinct. */
143
+ export function createClientSessionOptions(
144
+ target: ConnectionTarget,
145
+ fetch: NonNullable<ClientSessionOptions['fetch']>,
146
+ auth: SessionAuth | undefined,
147
+ timeoutMs: number,
148
+ ): ClientSessionOptions {
149
+ return {
150
+ kernel: target.kernelIssuer,
151
+ fetch,
152
+ ...(auth === undefined ? {} : { auth }),
153
+ policy: {
154
+ maximumRouteAgeMs: MAXIMUM_ROUTE_AGE_MS,
155
+ ...(new URL(target.url).protocol === 'http:' ? { allowInsecureHttp: true } : {}),
156
+ },
157
+ envelopeTransport: 'http',
158
+ timeoutMs,
159
+ }
160
+ }
161
+
162
+ export async function lookupManagedInstance(
163
+ slug: string,
164
+ options: ConnectionOptions,
165
+ openAdmin: typeof withAdminClientSession = withAdminClientSession,
166
+ connect: typeof connectAdminInstances = connectAdminInstances,
167
+ ): Promise<InstanceInfo> {
168
+ return openAdmin(
169
+ Object.freeze(options.timeout === undefined ? {} : { timeout: options.timeout }),
170
+ async (context) => {
171
+ const found = findOwnedInstance(await (await connect(context)).list(), slug)
172
+ if (found === undefined) throw new AdminInstanceNotFoundError(slug)
173
+ return found
174
+ },
175
+ )
176
+ }
@@ -0,0 +1,111 @@
1
+ import type { IssuerId } from '@astrale-os/sdk/auth'
2
+
3
+ import { issuer } from '@astrale-os/sdk/auth'
4
+
5
+ import type { InstanceInfo } from '../lib/admin-instance'
6
+ import type { AdminTargetCommandOpts } from '../lib/admin-target'
7
+ import type { AstraleConfig } from '../lib/config'
8
+ import type { InstanceStore } from '../lib/instance'
9
+ import type { ResolvedInstanceTarget } from '../lib/instance-target'
10
+
11
+ import { resolveInstanceTarget } from '../lib/instance-target'
12
+
13
+ export interface ConnectionOptions {
14
+ readonly url?: string
15
+ readonly instance?: string
16
+ readonly timeout?: string
17
+ readonly as?: string
18
+ readonly creds?: string
19
+ readonly anonymous?: boolean
20
+ }
21
+
22
+ export interface AdminConnectionOptions extends ConnectionOptions {
23
+ readonly admin?: string
24
+ readonly adminUrl?: string
25
+ readonly domainIssuer?: string
26
+ }
27
+
28
+ export interface ConnectionTarget {
29
+ readonly url: string
30
+ readonly kernelIssuer: IssuerId
31
+ readonly domainIssuer?: IssuerId
32
+ readonly slug?: string
33
+ readonly defaultIdentity?: string
34
+ readonly caFile?: string
35
+ }
36
+
37
+ export interface TargetDependencies {
38
+ readonly instances?: InstanceStore
39
+ readonly managed?: (slug: string) => Promise<InstanceInfo>
40
+ }
41
+
42
+ /** Stable local identity-registration key for the exact selected source Kernel. */
43
+ export function registrationKeyForTarget(target: ConnectionTarget): string {
44
+ return target.slug ?? target.url
45
+ }
46
+
47
+ /** Resolve the existing URL / instance / active precedence into one exact source Kernel. */
48
+ export async function resolveConnectionTarget(
49
+ options: ConnectionOptions,
50
+ config: AstraleConfig,
51
+ dependencies: TargetDependencies = {},
52
+ ): Promise<ConnectionTarget> {
53
+ const request =
54
+ options.url !== undefined && options.instance === undefined
55
+ ? ({ source: 'url', url: options.url } as const)
56
+ : options.instance !== undefined
57
+ ? ({ source: 'name', name: options.instance } as const)
58
+ : ({ source: 'active' } as const)
59
+ const resolved = await resolveInstanceTarget(request, {
60
+ config,
61
+ ...(dependencies.instances === undefined ? {} : { instances: dependencies.instances }),
62
+ admin: {},
63
+ ...(dependencies.managed === undefined ? {} : { managed: dependencies.managed }),
64
+ })
65
+ return connectionTarget(resolved, options.url)
66
+ }
67
+
68
+ /** Resolve the configured Admin Domain target without entering managed-instance discovery. */
69
+ export async function resolveAdminConnectionTarget(
70
+ options: AdminConnectionOptions,
71
+ config: AstraleConfig,
72
+ instances?: InstanceStore,
73
+ ): Promise<ConnectionTarget> {
74
+ const resolved = await resolveInstanceTarget(
75
+ { source: 'admin' },
76
+ {
77
+ config,
78
+ admin: adminLookupOptions(options),
79
+ ...(instances === undefined ? {} : { instances }),
80
+ },
81
+ )
82
+ return connectionTarget(resolved)
83
+ }
84
+
85
+ export function adminLookupOptions(options: AdminConnectionOptions): AdminTargetCommandOpts {
86
+ return {
87
+ ...(options.admin === undefined ? {} : { admin: options.admin }),
88
+ ...(options.adminUrl === undefined ? {} : { adminUrl: options.adminUrl }),
89
+ ...(options.domainIssuer === undefined ? {} : { domainIssuer: options.domainIssuer }),
90
+ ...(options.instance === undefined ? {} : { instance: options.instance }),
91
+ ...(options.url === undefined ? {} : { url: options.url }),
92
+ }
93
+ }
94
+
95
+ function connectionTarget(
96
+ resolved: ResolvedInstanceTarget,
97
+ urlOverride?: string,
98
+ ): ConnectionTarget {
99
+ return Object.freeze({
100
+ url: urlOverride ?? resolved.url,
101
+ kernelIssuer: issuer.accept(resolved.kernelIssuer),
102
+ ...(resolved.domainIssuer === undefined
103
+ ? {}
104
+ : { domainIssuer: issuer.accept(resolved.domainIssuer) }),
105
+ ...(resolved.name === undefined ? {} : { slug: resolved.name }),
106
+ ...(resolved.defaultIdentity === undefined
107
+ ? {}
108
+ : { defaultIdentity: resolved.defaultIdentity }),
109
+ ...(resolved.caFile === undefined ? {} : { caFile: resolved.caFile }),
110
+ })
111
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": { "types": ["@types/node"] },
4
+ "include": ["*.ts"],
5
+ "exclude": ["__tests__"]
6
+ }
@@ -0,0 +1,34 @@
1
+ import type { MutationAST } from '@astrale-os/sdk/mutation'
2
+ import type { QueryAST, QueryDirection } from '@astrale-os/sdk/query'
3
+
4
+ /** Untrusted CLI fields used to author or admit one exact Query V5 request. */
5
+ export interface QueryCommandInput {
6
+ readonly sources: readonly string[]
7
+ readonly definition?: string
8
+ readonly ast?: unknown
9
+ readonly edge?: string
10
+ readonly direction?: QueryDirection
11
+ readonly limit?: string
12
+ readonly cursor?: string
13
+ }
14
+
15
+ /** Canonical query plus the caller-bound continuation token kept outside the AST. */
16
+ export interface PreparedQuery {
17
+ readonly ast: QueryAST
18
+ readonly cursor?: string
19
+ }
20
+
21
+ /** Admit canonical Query V5 or author its supported Path/Definition/one-edge CLI subset. */
22
+ export function prepareQuery(input: QueryCommandInput): PreparedQuery
23
+
24
+ /** Admit canonical Mutation V2 or its exact authoring input; legacy PatchData is rejected. */
25
+ export function prepareMutation(input: unknown): MutationAST
26
+
27
+ /** Return the user-facing leaf of one exact qualified Property key. */
28
+ export function unqualifyProperty(key: string): string
29
+
30
+ /** Read one property by exact key first, then by its qualified leaf name. */
31
+ export function nodeProperty(
32
+ node: { readonly props: Readonly<Record<string, unknown>> } | null | undefined,
33
+ name: string,
34
+ ): unknown
@@ -0,0 +1,24 @@
1
+ import { defineLaw } from '@astrale-os/spec/authoring'
2
+
3
+ export const CLI_GRAPH_QUERY_V5 = defineLaw({
4
+ id: 'CLI-GRAPH-QUERY-V5',
5
+ statement:
6
+ 'CLI query input becomes exactly one canonical Query V5 document with an explicit finite limit; exact Property ordering and Node or Edge projection profiles are admitted through the canonical AST surface, while legacy versions and unsupported selector combinations fail before a Graph call.',
7
+ tests: [
8
+ { file: '__tests__/query.test.ts', id: 'TEST-CLI-GRAPH-AUTHORS-QUERY-V5' },
9
+ { file: '__tests__/query.test.ts', id: 'TEST-CLI-GRAPH-AUTHORS-DEFINITION-QUERY' },
10
+ { file: '__tests__/query.test.ts', id: 'TEST-CLI-GRAPH-ADMITS-QUERY-V5-ORDERING' },
11
+ { file: '__tests__/query.test.ts', id: 'TEST-CLI-GRAPH-ADMITS-QUERY-V5-PROJECTIONS' },
12
+ { file: '__tests__/query.test.ts', id: 'TEST-CLI-GRAPH-REJECTS-LEGACY-QUERY' },
13
+ ],
14
+ })
15
+
16
+ export const CLI_GRAPH_MUTATION_V2 = defineLaw({
17
+ id: 'CLI-GRAPH-MUTATION-V2',
18
+ statement:
19
+ 'CLI mutation input is admitted as canonical Mutation V2 or its exact authoring input; PatchData and other historical shapes never reach GraphApi.mutate.',
20
+ tests: [
21
+ { file: '__tests__/mutation.test.ts', id: 'TEST-CLI-GRAPH-ADMITS-MUTATION-V2' },
22
+ { file: '__tests__/mutation.test.ts', id: 'TEST-CLI-GRAPH-REJECTS-PATCH-DATA' },
23
+ ],
24
+ })
@@ -0,0 +1,13 @@
1
+ import { defineLaw } from '@astrale-os/spec/authoring'
2
+
3
+ export const CLI_GRAPH_PROPERTY_PROJECTION = defineLaw({
4
+ id: 'CLI-GRAPH-PROPERTY-PROJECTION',
5
+ statement:
6
+ 'An exact property key wins over a qualified leaf-name match; otherwise the first canonical property with that leaf is presented without rewriting the Node.',
7
+ tests: [
8
+ {
9
+ file: '__tests__/projection.test.ts',
10
+ id: 'TEST-CLI-GRAPH-PROJECTS-QUALIFIED-PROPERTIES',
11
+ },
12
+ ],
13
+ })
@@ -0,0 +1,6 @@
1
+ import { defineLayout } from '@astrale-os/spec/authoring'
2
+
3
+ export default defineLayout({
4
+ entries: ['__tests__/', 'index.ts', 'mutation.ts', 'projection.ts', 'query.ts', 'tsconfig.json'],
5
+ exact: true,
6
+ })
@@ -0,0 +1,43 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import { prepareMutation } from '../mutation'
4
+
5
+ describe('prepareMutation', () => {
6
+ /** @evidence TEST-CLI-GRAPH-ADMITS-MUTATION-V3 */
7
+ test('creates one canonical Mutation V3 document from exact authoring input', () => {
8
+ expect(
9
+ JSON.parse(
10
+ JSON.stringify(
11
+ prepareMutation({
12
+ preconditions: [],
13
+ operations: [
14
+ {
15
+ op: 'node.create',
16
+ as: 'note',
17
+ class: '/:notes.example.dev:class.Note',
18
+ props: {},
19
+ },
20
+ ],
21
+ }),
22
+ ),
23
+ ),
24
+ ).toEqual({
25
+ format: 'astrale.graph.mutation',
26
+ version: 'v3',
27
+ preconditions: [],
28
+ operations: [
29
+ {
30
+ op: 'node.create',
31
+ as: 'note',
32
+ class: '/:notes.example.dev:class.Note',
33
+ props: {},
34
+ },
35
+ ],
36
+ })
37
+ })
38
+
39
+ /** @evidence TEST-CLI-GRAPH-REJECTS-PATCH-DATA */
40
+ test('rejects legacy PatchData instead of narrowing it', () => {
41
+ expect(() => prepareMutation({ nodes: { create: [] }, edges: { create: [] } })).toThrow()
42
+ })
43
+ })
@@ -0,0 +1,22 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import { nodeProperty, unqualifyProperty } from '../projection'
4
+
5
+ describe('graph presentation properties', () => {
6
+ /** @evidence TEST-CLI-GRAPH-PROJECTS-QUALIFIED-PROPERTIES */
7
+ test('prefers exact keys and otherwise projects a qualified leaf', () => {
8
+ const node = {
9
+ props: {
10
+ name: 'exact',
11
+ '/:example.test:class.Widget.property.name': 'qualified',
12
+ '/:example.test:class.Widget.property.description': 'shown',
13
+ },
14
+ }
15
+
16
+ expect(nodeProperty(node, 'name')).toBe('exact')
17
+ expect(nodeProperty(node, 'description')).toBe('shown')
18
+ expect(unqualifyProperty('/:example.test:class.Widget.property.description')).toBe(
19
+ 'description',
20
+ )
21
+ })
22
+ })
@@ -0,0 +1,154 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import { prepareQuery } from '../query'
4
+
5
+ describe('prepareQuery', () => {
6
+ /** @evidence TEST-CLI-GRAPH-AUTHORS-QUERY-V6 */
7
+ test('authors the supported Path and exact-edge subset as canonical Query V6', () => {
8
+ const prepared = prepareQuery({
9
+ sources: ['/:notes.example.dev:class.Note'],
10
+ edge: '/:notes.example.dev:class.references',
11
+ direction: 'incoming',
12
+ limit: '25',
13
+ cursor: 'next-page',
14
+ })
15
+
16
+ expect(JSON.parse(JSON.stringify(prepared))).toEqual({
17
+ ast: {
18
+ format: 'astrale.graph.query',
19
+ version: 'v6',
20
+ source: {
21
+ kind: 'node',
22
+ terms: [{ kind: 'path', path: '/:notes.example.dev:class.Note' }],
23
+ binding: 'n0',
24
+ },
25
+ steps: [
26
+ {
27
+ op: 'expand',
28
+ from: 'n0',
29
+ via: [{ origin: 'notes.example.dev', kind: 'class', name: 'references' }],
30
+ direction: 'incoming',
31
+ bindings: { edge: 'e0', node: 'n1' },
32
+ },
33
+ ],
34
+ select: { kind: 'graph', binding: 'n1' },
35
+ },
36
+ page: { size: 25, after: 'next-page' },
37
+ })
38
+ })
39
+
40
+ /** @evidence TEST-CLI-GRAPH-AUTHORS-DEFINITION-QUERY */
41
+ test('authors an exact Definition source without backend query text', () => {
42
+ const prepared = prepareQuery({
43
+ sources: [],
44
+ definition: '/:issues.astrale.ai:class.Issue',
45
+ limit: '201',
46
+ })
47
+
48
+ expect(JSON.parse(JSON.stringify(prepared.ast))).toEqual({
49
+ format: 'astrale.graph.query',
50
+ version: 'v6',
51
+ source: {
52
+ kind: 'node',
53
+ terms: [
54
+ {
55
+ kind: 'definition',
56
+ definition: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
57
+ },
58
+ ],
59
+ binding: 'n0',
60
+ },
61
+ steps: [],
62
+ select: { kind: 'graph', binding: 'n0' },
63
+ })
64
+ expect(prepared.page).toEqual({ size: 201 })
65
+ })
66
+
67
+ /** @evidence TEST-CLI-GRAPH-ADMITS-QUERY-V6-ORDERING */
68
+ test('admits one exact Property-ordered Query V6 document', () => {
69
+ const ast = {
70
+ format: 'astrale.graph.query',
71
+ version: 'v6',
72
+ source: {
73
+ kind: 'node',
74
+ terms: [
75
+ {
76
+ kind: 'definition',
77
+ definition: { origin: 'notes.example.dev', kind: 'class', name: 'Note' },
78
+ },
79
+ ],
80
+ binding: 'n0',
81
+ },
82
+ steps: [],
83
+ select: {
84
+ kind: 'graph',
85
+ binding: 'n0',
86
+ order: {
87
+ property: 'notes.example.dev:class.Note.property.sequence',
88
+ direction: 'desc',
89
+ unranked: 'last',
90
+ },
91
+ },
92
+ }
93
+
94
+ const prepared = prepareQuery({ sources: [], ast, limit: '3' })
95
+ expect(JSON.parse(JSON.stringify(prepared.ast))).toEqual(ast)
96
+ expect(prepared.page).toEqual({ size: 3 })
97
+ })
98
+
99
+ /** @evidence TEST-CLI-GRAPH-ADMITS-QUERY-V6-PROJECTIONS */
100
+ test('admits exact Node and Edge projection profiles in Query V6 documents', () => {
101
+ const source = {
102
+ terms: [{ kind: 'path', path: '/:notes.example.dev:class.Note' }],
103
+ binding: 'n0',
104
+ kind: 'node',
105
+ } as const
106
+ const node = {
107
+ format: 'astrale.graph.query',
108
+ version: 'v6',
109
+ source,
110
+ steps: [],
111
+ select: { kind: 'nodes', binding: 'n0', projection: { kind: 'reference' } },
112
+ }
113
+ const edge = {
114
+ format: 'astrale.graph.query',
115
+ version: 'v6',
116
+ source,
117
+ steps: [
118
+ {
119
+ op: 'expand',
120
+ from: 'n0',
121
+ via: [{ origin: 'notes.example.dev', kind: 'class', name: 'references' }],
122
+ direction: 'outgoing',
123
+ bindings: { edge: 'e0', node: 'n1' },
124
+ },
125
+ ],
126
+ select: {
127
+ kind: 'edges',
128
+ binding: 'e0',
129
+ projection: { edge: 'value', source: 'reference', target: 'value' },
130
+ },
131
+ }
132
+
133
+ expect(
134
+ JSON.parse(JSON.stringify(prepareQuery({ sources: [], ast: node, limit: '5' }).ast)),
135
+ ).toEqual(node)
136
+ expect(
137
+ JSON.parse(JSON.stringify(prepareQuery({ sources: [], ast: edge, limit: '5' }).ast)),
138
+ ).toEqual(edge)
139
+ })
140
+
141
+ /** @evidence TEST-CLI-GRAPH-REJECTS-LEGACY-QUERY */
142
+ test('rejects V1 queries and unsupported combinations locally', () => {
143
+ expect(() => prepareQuery({ sources: [], ast: { version: 1, from: ['/'] } })).toThrow()
144
+ expect(() =>
145
+ prepareQuery({ sources: ['/:notes.example.dev:class.Note'], direction: 'incident' }),
146
+ ).toThrow('--direction requires --edge')
147
+ expect(() =>
148
+ prepareQuery({ sources: ['/:notes.example.dev:class.Note'], limit: 'all' }),
149
+ ).toThrow('--limit must be a positive integer')
150
+ expect(() =>
151
+ prepareQuery({ sources: [], definition: '/:notes.example.dev:view.note' }),
152
+ ).toThrow('--definition must be one canonical Class or Interface Path')
153
+ })
154
+ })
@@ -0,0 +1,3 @@
1
+ export { prepareMutation } from './mutation'
2
+ export { nodeProperty, unqualifyProperty } from './projection'
3
+ export { prepareQuery, type PreparedQuery, type QueryCommandInput } from './query'
@@ -0,0 +1,18 @@
1
+ import type { MutationAST as MutationASTValue, MutationInput } from '@astrale-os/sdk/mutation'
2
+
3
+ import { MutationAST } from '@astrale-os/sdk/mutation'
4
+
5
+ /** Admit the canonical document or Core's exact rich authoring input at the CLI JSON boundary. */
6
+ export function prepareMutation(input: unknown): MutationASTValue {
7
+ if (isCanonicalCandidate(input)) return MutationAST.decode(input)
8
+ return MutationAST.create(input as MutationInput)
9
+ }
10
+
11
+ function isCanonicalCandidate(input: unknown): boolean {
12
+ return (
13
+ input !== null &&
14
+ typeof input === 'object' &&
15
+ !Array.isArray(input) &&
16
+ (Object.hasOwn(input, 'format') || Object.hasOwn(input, 'version'))
17
+ )
18
+ }
@@ -0,0 +1,21 @@
1
+ /** Strip a fully-qualified Property key to the leaf used by CLI presentation. */
2
+ export function unqualifyProperty(key: string): string {
3
+ const property = key.lastIndexOf('.property.')
4
+ if (property >= 0) return key.slice(property + '.property.'.length)
5
+ const slash = key.lastIndexOf('/')
6
+ return slash >= 0 ? key.slice(slash + 1) : key
7
+ }
8
+
9
+ /** Read an exact key first, then one canonical qualified key with the requested leaf. */
10
+ export function nodeProperty(
11
+ node: { readonly props: Readonly<Record<string, unknown>> } | null | undefined,
12
+ name: string,
13
+ ): unknown {
14
+ const props = node?.props
15
+ if (props === undefined) return undefined
16
+ if (Object.hasOwn(props, name)) return props[name]
17
+ for (const [key, value] of Object.entries(props)) {
18
+ if (unqualifyProperty(key) === name) return value
19
+ }
20
+ return undefined
21
+ }