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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +16 -1
  2. package/dist/astrale.js +51105 -58057
  3. package/dist/public/connect-core.js +2085 -30702
  4. package/dist/public/keys/index.js +884 -29476
  5. package/dist/public/paths/index.js +13101 -41452
  6. package/dist/types/admin/contract.d.ts +26 -0
  7. package/dist/types/admin/instance/client.d.ts +20 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +6 -10
  10. package/dist/types/connection/auth.d.ts +3 -0
  11. package/dist/types/connection/call.d.ts +3 -0
  12. package/dist/types/connection/command.d.ts +27 -0
  13. package/dist/types/connection/credential.d.ts +14 -0
  14. package/dist/types/connection/errors.d.ts +1 -0
  15. package/dist/types/connection/exchange.d.ts +9 -0
  16. package/dist/types/connection/failure/classify.d.ts +2 -0
  17. package/dist/types/connection/failure/index.d.ts +5 -0
  18. package/dist/types/connection/failure/model.d.ts +25 -0
  19. package/dist/types/connection/failure/render.d.ts +3 -0
  20. package/dist/types/connection/index.d.ts +8 -0
  21. package/dist/types/connection/reasons.d.ts +37 -0
  22. package/dist/types/connection/self.d.ts +37 -0
  23. package/dist/types/connection/session.d.ts +31 -0
  24. package/dist/types/connection/target.d.ts +38 -0
  25. package/dist/types/errors.d.ts +1 -1
  26. package/dist/types/lib/admin-instance.d.ts +17 -0
  27. package/dist/types/lib/admin-target.d.ts +11 -36
  28. package/dist/types/lib/config.d.ts +7 -59
  29. package/dist/types/lib/failure-debug.d.ts +1 -0
  30. package/dist/types/lib/idp.d.ts +25 -173
  31. package/dist/types/lib/instance-target.d.ts +1 -1
  32. package/dist/types/lib/instance.d.ts +30 -88
  33. package/dist/types/lib/log.d.ts +5 -3
  34. package/dist/types/lib/output.d.ts +1 -1
  35. package/dist/types/lib/proc.d.ts +43 -0
  36. package/dist/types/lib/self.d.ts +9 -0
  37. package/dist/types/lib/update.d.ts +109 -0
  38. package/dist/types/lib/validation.d.ts +4 -1
  39. package/package.json +15 -20
  40. package/src/__tests__/fixtures/publication.ts +20 -0
  41. package/src/admin/.spec/architecture.md +12 -5
  42. package/src/admin/__tests__/fixture.ts +25 -0
  43. package/src/admin/catalog/.spec/api.d.ts +1 -3
  44. package/src/admin/catalog/.spec/architecture.md +5 -4
  45. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  46. package/src/admin/catalog/client.ts +40 -54
  47. package/src/admin/catalog/model.ts +3 -4
  48. package/src/admin/contract.ts +46 -0
  49. package/src/admin/graph/.spec/api.d.ts +4 -10
  50. package/src/admin/graph/nodes.ts +1 -1
  51. package/src/admin/instance/.spec/api.d.ts +5 -9
  52. package/src/admin/instance/.spec/architecture.md +7 -7
  53. package/src/admin/instance/__tests__/client.test.ts +157 -175
  54. package/src/admin/instance/client.ts +39 -128
  55. package/src/admin/instance/index.ts +1 -2
  56. package/src/admin/instance/model.ts +12 -18
  57. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  58. package/src/commands/__tests__/call-describe.test.ts +46 -33
  59. package/src/commands/__tests__/call.test.ts +34 -0
  60. package/src/commands/__tests__/domain-install-operation.test.ts +7 -22
  61. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  62. package/src/commands/__tests__/domain-list.test.ts +5 -25
  63. package/src/commands/__tests__/install-direct.test.ts +115 -8
  64. package/src/commands/__tests__/install-identity-override.test.ts +3 -14
  65. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  66. package/src/commands/__tests__/instance-status.test.ts +139 -0
  67. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  68. package/src/commands/__tests__/logs.test.ts +250 -2
  69. package/src/commands/__tests__/read-commands.test.ts +62 -10
  70. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  71. package/src/commands/__tests__/update.test.ts +25 -3
  72. package/src/commands/__tests__/view.test.ts +49 -0
  73. package/src/commands/call-describe.ts +60 -68
  74. package/src/commands/call.ts +71 -59
  75. package/src/commands/domain/install.ts +54 -35
  76. package/src/commands/domain/list.ts +1 -1
  77. package/src/commands/domain/publish.ts +14 -11
  78. package/src/commands/domain/uninstall.ts +2 -2
  79. package/src/commands/get.ts +14 -15
  80. package/src/commands/identity/__tests__/register.test.ts +3 -3
  81. package/src/commands/identity/register.ts +5 -4
  82. package/src/commands/instance/create.ts +8 -16
  83. package/src/commands/instance/delete.ts +12 -9
  84. package/src/commands/instance/list.ts +2 -2
  85. package/src/commands/instance/status.ts +66 -20
  86. package/src/commands/instance/use.ts +16 -12
  87. package/src/commands/introspect.ts +48 -35
  88. package/src/commands/logs.ts +123 -33
  89. package/src/commands/mutate.ts +6 -5
  90. package/src/commands/query.ts +35 -32
  91. package/src/commands/token.ts +42 -27
  92. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  93. package/src/commands/ui/add.ts +51 -0
  94. package/src/commands/ui/doctor.ts +13 -0
  95. package/src/commands/ui/init.ts +38 -0
  96. package/src/commands/ui/list.ts +26 -0
  97. package/src/commands/ui/preset-apply.ts +19 -0
  98. package/src/commands/ui/preset-list.ts +12 -0
  99. package/src/commands/ui/shared.ts +25 -0
  100. package/src/commands/update.ts +31 -11
  101. package/src/commands/view.ts +82 -37
  102. package/src/connection/.spec/api.d.ts +7 -8
  103. package/src/connection/.spec/architecture.md +5 -4
  104. package/src/connection/.spec/flows/session.ts +2 -2
  105. package/src/connection/.spec/laws/connection.ts +6 -2
  106. package/src/connection/.spec/layout.ts +1 -0
  107. package/src/connection/__tests__/credential.test.ts +20 -1
  108. package/src/connection/__tests__/errors.test.ts +27 -20
  109. package/src/connection/__tests__/exchange.test.ts +30 -1
  110. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  111. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  112. package/src/connection/__tests__/self.test.ts +31 -11
  113. package/src/connection/__tests__/session.test.ts +3 -3
  114. package/src/connection/__tests__/target.test.ts +1 -0
  115. package/src/connection/auth.ts +13 -0
  116. package/src/connection/call.ts +1 -1
  117. package/src/connection/credential.ts +2 -2
  118. package/src/connection/errors.ts +1 -355
  119. package/src/connection/exchange.ts +47 -23
  120. package/src/connection/failure/classify.ts +110 -0
  121. package/src/connection/failure/index.ts +18 -0
  122. package/src/connection/failure/model.ts +28 -0
  123. package/src/connection/failure/render.ts +116 -0
  124. package/src/connection/reasons.ts +16 -0
  125. package/src/connection/self.ts +39 -21
  126. package/src/connection/session.ts +19 -8
  127. package/src/errors.ts +3 -2
  128. package/src/graph/.spec/api.d.ts +12 -5
  129. package/src/graph/.spec/layout.ts +9 -1
  130. package/src/graph/__tests__/mutation.test.ts +2 -2
  131. package/src/graph/__tests__/query.test.ts +25 -6
  132. package/src/graph/class.ts +30 -0
  133. package/src/graph/index.ts +1 -0
  134. package/src/graph/query.ts +21 -32
  135. package/src/identity/.spec/api.d.ts +2 -2
  136. package/src/identity/__tests__/registration.test.ts +1 -1
  137. package/src/identity/registration.ts +3 -3
  138. package/src/keys/__tests__/keys.test.ts +4 -0
  139. package/src/keys/credential.ts +1 -0
  140. package/src/lib/__tests__/admin-target.test.ts +12 -0
  141. package/src/lib/__tests__/binary.test.ts +16 -1
  142. package/src/lib/__tests__/command-dx.test.ts +1 -1
  143. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  144. package/src/lib/__tests__/idp.test.ts +34 -4
  145. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  146. package/src/lib/__tests__/instance-target.test.ts +7 -39
  147. package/src/lib/__tests__/log-errors.test.ts +64 -0
  148. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  149. package/src/lib/__tests__/update.test.ts +264 -11
  150. package/src/lib/__tests__/view-assets.test.ts +14 -0
  151. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  152. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  153. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  154. package/src/lib/__tests__/view-server.test.ts +26 -3
  155. package/src/lib/__tests__/view-session.test.ts +2 -1
  156. package/src/lib/admin-instance.ts +14 -8
  157. package/src/lib/admin-target.ts +21 -2
  158. package/src/lib/binary.ts +22 -5
  159. package/src/lib/command-dx.ts +9 -9
  160. package/src/lib/domain-publication.ts +9 -7
  161. package/src/lib/failure-debug.ts +26 -0
  162. package/src/lib/idp.ts +34 -3
  163. package/src/lib/instance-candidates.ts +2 -2
  164. package/src/lib/instance-target.ts +3 -14
  165. package/src/lib/instance.ts +16 -11
  166. package/src/lib/log.ts +16 -9
  167. package/src/lib/output.ts +1 -1
  168. package/src/lib/proc.ts +7 -2
  169. package/src/lib/provision-instance.ts +34 -57
  170. package/src/lib/update.ts +298 -45
  171. package/src/lib/validation.ts +2 -2
  172. package/src/lib/view/assets.ts +11 -2
  173. package/src/lib/view/external-open-intent.ts +41 -0
  174. package/src/lib/view/external-open-origins.ts +37 -0
  175. package/src/lib/view/host-capabilities.ts +17 -0
  176. package/src/lib/view/resolve.ts +1 -1
  177. package/src/lib/view/server.ts +18 -3
  178. package/src/lib/view/session.ts +2 -0
  179. package/src/program/.spec/api.d.ts +1 -0
  180. package/src/program/__tests__/program.test.ts +163 -6
  181. package/src/program/argv.ts +14 -0
  182. package/src/program/build.ts +26 -4
  183. package/src/program/command.ts +1 -0
  184. package/src/program/index.ts +1 -0
  185. package/src/program/registry.ts +2 -1
  186. package/src/setup/__tests__/instance-step.test.ts +27 -10
  187. package/src/setup/steps/instance.ts +22 -15
  188. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  189. package/src/telemetry/recorder.ts +9 -1
  190. package/src/ui/.spec/api.d.ts +14 -0
  191. package/src/ui/.spec/architecture.md +10 -0
  192. package/src/ui/.spec/laws.ts +36 -0
  193. package/src/ui/.spec/layout.ts +16 -0
  194. package/src/ui/__tests__/ui.test.ts +981 -0
  195. package/src/ui/index.ts +13 -0
  196. package/src/ui/lock.ts +87 -0
  197. package/src/ui/model.ts +83 -0
  198. package/src/ui/operations.ts +710 -0
  199. package/src/ui/project.ts +219 -0
  200. package/src/ui/release.ts +321 -0
  201. package/src/ui/runner.ts +18 -0
  202. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  203. package/studio/client/dist/assets/index-7YHdeCxp.js +81 -0
  204. package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
  205. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  206. package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
  207. package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
  208. package/studio/client/dist/index.html +3 -2
  209. package/studio/package.json +7 -13
  210. package/studio/server/agent/ask.test.ts +1 -1
  211. package/studio/server/agent/bridge/grant.test.ts +1 -1
  212. package/studio/server/agent/bridge/routes.test.ts +1 -1
  213. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  214. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  215. package/studio/server/agent/harness/gateway/token.ts +3 -3
  216. package/studio/server/agent/prompts/anchors.test.ts +32 -36
  217. package/studio/server/agent/prompts/anchors.ts +20 -49
  218. package/studio/server/agent/prompts/system.test.ts +3 -2
  219. package/studio/server/agent/prompts/system.ts +3 -3
  220. package/studio/server/agent/routes.test.ts +1 -1
  221. package/studio/server/agent/run/coordinator.test.ts +1 -1
  222. package/studio/server/api/canvas.ts +0 -1
  223. package/studio/server/api-agent-loadout.test.ts +1 -1
  224. package/studio/server/api.test.ts +1 -1
  225. package/studio/server/cache.test.ts +23 -12
  226. package/studio/server/cache.ts +12 -15
  227. package/studio/server/domain.test.ts +50 -68
  228. package/studio/server/domain.ts +82 -63
  229. package/studio/server/handoff/copy.ts +1 -1
  230. package/studio/server/index.ts +1 -1
  231. package/studio/server/instances/probe.test.ts +1 -1
  232. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  233. package/studio/server/introspect/anatomy/views.ts +9 -14
  234. package/studio/server/introspect/anatomy-extras.test.ts +26 -42
  235. package/studio/server/introspect/anatomy.test.ts +22 -14
  236. package/studio/server/introspect/anatomy.ts +6 -9
  237. package/studio/server/introspect/bundle.ts +0 -3
  238. package/studio/server/introspect/canonical-schema.test.ts +94 -389
  239. package/studio/server/introspect/canonical-schema.ts +227 -457
  240. package/studio/server/introspect/core-extractor.ts +18 -127
  241. package/studio/server/introspect/core.ts +1 -2
  242. package/studio/server/introspect/diff.test.ts +11 -7
  243. package/studio/server/introspect/diff.ts +18 -55
  244. package/studio/server/introspect/extractor.ts +22 -117
  245. package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
  246. package/studio/server/introspect/overlay.test.ts +26 -54
  247. package/studio/server/introspect/overlay.ts +10 -37
  248. package/studio/server/introspect/revision.test.ts +1 -1
  249. package/studio/server/introspect/revision.ts +1 -1
  250. package/studio/server/introspect/runtime.test.ts +65 -183
  251. package/studio/server/introspect/runtime.ts +8 -25
  252. package/studio/server/introspect/schema-ir-json.test.ts +70 -0
  253. package/studio/server/introspect/schema-ir-json.ts +38 -68
  254. package/studio/server/introspect/schema-refs.test.ts +43 -88
  255. package/studio/server/introspect/schema-refs.ts +14 -49
  256. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  257. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  258. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  259. package/studio/server/state/baseline.test.ts +14 -9
  260. package/studio/server/state/baseline.ts +3 -5
  261. package/studio/server/state/visibility.ts +1 -5
  262. package/studio/server/views/model.ts +3 -3
  263. package/studio/server/views/target.test.ts +17 -74
  264. package/studio/server/views/target.ts +24 -50
  265. package/studio/server/watch.test.ts +11 -11
  266. package/studio/server/watch.ts +8 -4
  267. package/studio/server/workspace-watch.ts +2 -2
  268. package/studio/shared/contracts/schema.ts +38 -107
  269. package/studio/shared/contracts/surface.test.ts +13 -15
  270. package/studio/shared/contracts/workspace.ts +2 -11
  271. package/studio/shared/schema/identity.test.ts +20 -44
  272. package/studio/shared/schema/identity.ts +9 -23
  273. package/viewer/dist/main.js +30 -51
  274. package/.check-workspace.cjs +0 -40
  275. package/src/admin/binding.ts +0 -168
  276. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  277. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  278. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  279. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  280. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  281. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  282. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
@@ -58,7 +58,7 @@ describe('view session private state', () => {
58
58
  issuer: issuer('https://example.test'),
59
59
  etag: `sha256:${'a'.repeat(64)}`,
60
60
  revision: revision('b'),
61
- declaration: { target: { kind: 'domain' }, auth: 'required' },
61
+ declaration: { target: { kind: 'domain' } },
62
62
  },
63
63
  },
64
64
  createdAt: '2026-08-12T00:00:00.000Z',
@@ -69,6 +69,7 @@ describe('view session private state', () => {
69
69
  issuer: 'https://kernel.test',
70
70
  direct: true,
71
71
  },
72
+ externalOrigins: [],
72
73
  idleMs: 60_000,
73
74
  } satisfies ViewServeConfig
74
75
 
@@ -6,7 +6,7 @@ import { withAdminClientSession } from '../connection'
6
6
  export {
7
7
  AdminInstanceNotFoundError,
8
8
  findOwnedInstance,
9
- formatInstanceLocation,
9
+ formatInstanceState,
10
10
  type InstanceInfo,
11
11
  type InstanceState,
12
12
  type OwnedInstanceInfo,
@@ -19,19 +19,25 @@ export function listOwnedInstances(options: AdminConnectionOptions) {
19
19
  )
20
20
  }
21
21
 
22
+ /** Read caller-visible inventory together with the exact local identity used for the Admin call. */
23
+ export function listOwnedInstancesWithIdentity(options: AdminConnectionOptions) {
24
+ return withAdminClientSession(options, async (context) =>
25
+ Object.freeze({
26
+ instances: await (await connectAdminInstances(context)).list(),
27
+ ...(context.identity === undefined ? {} : { identity: context.identity }),
28
+ }),
29
+ )
30
+ }
31
+
22
32
  /** Reuse an already-open Admin session for a caller-visible Instance inventory. */
23
33
  export async function listOwnedInstancesInContext(context: ConnectionContext) {
24
34
  return (await connectAdminInstances(context)).list()
25
35
  }
26
36
 
27
- /** Provision through Fleet placement or one explicitly resolved Host receiver. */
28
- export function createOwnedInstance(
29
- options: AdminConnectionOptions,
30
- slug: string,
31
- hostId?: string,
32
- ) {
37
+ /** Request one managed Instance; Admin owns infrastructure placement. */
38
+ export function createOwnedInstance(options: AdminConnectionOptions, slug: string) {
33
39
  return withAdminClientSession(options, async (context) =>
34
- (await connectAdminInstances(context)).create(slug, hostId),
40
+ (await connectAdminInstances(context)).create(slug),
35
41
  )
36
42
  }
37
43
 
@@ -5,11 +5,30 @@ import type { InstanceStore } from './instance'
5
5
 
6
6
  import { AstraleError } from '../errors'
7
7
  import { readInstances, resolveInstanceKey } from './instance'
8
+ import { DEFAULT_UPDATE_CHANNEL } from './update'
8
9
  import { isHttpUrl } from './validation'
9
10
 
10
11
  export const DEFAULT_ADMIN_TARGET_NAME = 'admin'
11
- export const DEFAULT_ADMIN_TARGET_URL = 'https://admin.eu.astrale.ai/api'
12
- export const DEFAULT_ADMIN_DOMAIN_ISSUER = 'https://admin.beta.astrale.ai'
12
+
13
+ export function defaultAdminTargetForChannel(channel: string): {
14
+ readonly url: string
15
+ readonly domainIssuer: string
16
+ } {
17
+ return channel === 'stable'
18
+ ? {
19
+ url: 'https://admin.eu.astrale.ai/api',
20
+ domainIssuer: 'https://admin.astrale.ai',
21
+ }
22
+ : {
23
+ url: 'https://admin.eu.beta.astrale.ai/api',
24
+ domainIssuer: 'https://admin.beta.astrale.ai',
25
+ }
26
+ }
27
+
28
+ const DEFAULT_ADMIN_TARGET = defaultAdminTargetForChannel(DEFAULT_UPDATE_CHANNEL)
29
+
30
+ export const DEFAULT_ADMIN_TARGET_URL = DEFAULT_ADMIN_TARGET.url
31
+ export const DEFAULT_ADMIN_DOMAIN_ISSUER = DEFAULT_ADMIN_TARGET.domainIssuer
13
32
 
14
33
  export const DEFAULT_ADMIN_TARGET_CONFIG = {
15
34
  name: DEFAULT_ADMIN_TARGET_NAME,
package/src/lib/binary.ts CHANGED
@@ -7,14 +7,31 @@ import { output } from './output'
7
7
 
8
8
  /** A binary kernel result: bytes (or a stream of them) plus its content-type. */
9
9
  export type BinaryLike = {
10
- readonly body: Uint8Array
10
+ readonly body: Uint8Array | AsyncIterable<Uint8Array>
11
11
  readonly mediaType: string
12
+ readonly status?: number
12
13
  readonly headers?: Readonly<Record<string, string>>
13
14
  }
14
15
 
15
16
  /** Collapse a (possibly streamed) binary body into a single byte array. */
16
- export async function readBinaryBody(body: Uint8Array): Promise<Uint8Array> {
17
- return body
17
+ export async function readBinaryBody(
18
+ body: Uint8Array | AsyncIterable<Uint8Array>,
19
+ ): Promise<Uint8Array> {
20
+ if (body instanceof Uint8Array) return new Uint8Array(body)
21
+ const chunks: Uint8Array[] = []
22
+ let size = 0
23
+ for await (const chunk of body) {
24
+ const detached = new Uint8Array(chunk)
25
+ chunks.push(detached)
26
+ size += detached.byteLength
27
+ }
28
+ const bytes = new Uint8Array(size)
29
+ let offset = 0
30
+ for (const chunk of chunks) {
31
+ bytes.set(chunk, offset)
32
+ offset += chunk.byteLength
33
+ }
34
+ return bytes
18
35
  }
19
36
 
20
37
  function isTextLike(contentType: string): boolean {
@@ -43,13 +60,13 @@ function humanSize(bytes: number): string {
43
60
  function jsonEnvelope(resp: BinaryLike, bytes: Uint8Array): Record<string, unknown> {
44
61
  if (isTextLike(resp.mediaType)) {
45
62
  return {
46
- status: 200,
63
+ status: resp.status ?? 200,
47
64
  contentType: resp.mediaType,
48
65
  body: new TextDecoder().decode(bytes),
49
66
  }
50
67
  }
51
68
  return {
52
- status: 200,
69
+ status: resp.status ?? 200,
53
70
  contentType: resp.mediaType,
54
71
  bodyBase64: Buffer.from(bytes).toString('base64'),
55
72
  }
@@ -2,8 +2,6 @@ import type { Command, CommanderError } from 'commander'
2
2
 
3
3
  import chalk from 'chalk'
4
4
 
5
- import { isMachine } from './output'
6
-
7
5
  export type CommandCatalogEntry = {
8
6
  path: string[]
9
7
  usage: string
@@ -30,27 +28,27 @@ export function renderCommanderError(
30
28
  program: Command,
31
29
  error: CommanderError,
32
30
  argv = process.argv.slice(2),
31
+ machine = argv.some((token) => token === '--ci' || token === '--json' || token === '--raw') ||
32
+ !(process.stdout.isTTY ?? false),
33
33
  ): string {
34
34
  const tokens = stripOptions(argv)
35
35
  const catalog = collectCommandCatalog(program)
36
36
  const matched = matchRegisteredPrefix(program, tokens)
37
37
 
38
38
  if (matched.path.length === 0 && tokens.length > 0) {
39
- return maybeMachine(renderUnknownCommand(tokens, catalog))
39
+ return maybeMachine(renderUnknownCommand(tokens, catalog), machine)
40
40
  }
41
41
 
42
42
  if (error.code === 'commander.missingArgument') {
43
43
  const usage = usageFor(matched.path, matched.command)
44
- const argName = error.message.match(/'([^']+)'/)?.[1]
45
44
  return maybeMachine(
46
45
  [
47
- `Missing required argument${argName ? ` ${chalk.bold(`<${argName}>`)}` : ''} for ${chalk.bold(
48
- `astrale ${matched.path.join(' ')}`,
49
- )}`,
46
+ `Missing required argument for ${chalk.bold(`astrale ${matched.path.join(' ')}`)}`,
50
47
  '',
51
48
  'Usage:',
52
49
  ` astrale ${usage}`,
53
50
  ].join('\n'),
51
+ machine,
54
52
  )
55
53
  }
56
54
 
@@ -66,6 +64,7 @@ export function renderCommanderError(
66
64
  'Usage:',
67
65
  ` astrale ${usage}`,
68
66
  ].join('\n'),
67
+ machine,
69
68
  )
70
69
  }
71
70
 
@@ -77,6 +76,7 @@ export function renderCommanderError(
77
76
  ? ['', 'Did you mean:', ...suggestions.map((s) => ` astrale ${s}`)]
78
77
  : []),
79
78
  ].join('\n'),
79
+ machine,
80
80
  )
81
81
  }
82
82
 
@@ -143,8 +143,8 @@ function usageFor(path: string[], command: Command): string {
143
143
 
144
144
  const ANSI_RE = new RegExp(String.fromCharCode(27) + '\\[[0-9;]*m', 'g')
145
145
 
146
- function maybeMachine(text: string): string {
147
- if (!isMachine()) return text
146
+ function maybeMachine(text: string, machine: boolean): string {
147
+ if (!machine) return text
148
148
  const plain = text.replace(ANSI_RE, '')
149
149
  const first = plain.split('\n').find((line) => line.trim().length > 0) ?? plain
150
150
  return JSON.stringify({ error: 'USAGE_ERROR', message: first, detail: plain })
@@ -1,4 +1,4 @@
1
- import { publication } from '@astrale-os/kernel-protocol'
1
+ import { publication } from '@astrale-os/sdk/publication'
2
2
 
3
3
  const MAXIMUM_PUBLICATION_BYTES = 1024 * 1024
4
4
  type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
@@ -9,8 +9,7 @@ export async function fetchDomainPublication(
9
9
  signal?: AbortSignal,
10
10
  fetchImpl: FetchLike = globalThis.fetch,
11
11
  ): Promise<publication.Publication> {
12
- const root = deploymentRoot(input)
13
- const url = new URL(publication.PATH, root)
12
+ const url = domainPublicationUrl(input)
14
13
  const response = await fetchImpl(url, {
15
14
  redirect: 'error',
16
15
  ...(signal === undefined ? {} : { signal }),
@@ -81,7 +80,8 @@ async function cancel(body: ReadableStream<Uint8Array> | null): Promise<void> {
81
80
  if (body !== null) await body.cancel().catch(() => undefined)
82
81
  }
83
82
 
84
- function deploymentRoot(input: string): URL {
83
+ /** Canonical discovery document for a Domain origin or exact Publication URL. */
84
+ export function domainPublicationUrl(input: string): URL {
85
85
  let url: URL
86
86
  try {
87
87
  url = new URL(input)
@@ -94,9 +94,11 @@ function deploymentRoot(input: string): URL {
94
94
  url.password !== '' ||
95
95
  url.search !== '' ||
96
96
  url.hash !== '' ||
97
- !['', '/'].includes(url.pathname)
97
+ !['', '/', publication.PATH].includes(url.pathname)
98
98
  ) {
99
- throw new TypeError(`Domain deployment URL must be an HTTP(S) origin: ${input}`)
99
+ throw new TypeError(
100
+ `Domain deployment URL must be an HTTP(S) origin or canonical Publication URL: ${input}`,
101
+ )
100
102
  }
101
- return new URL(url.origin)
103
+ return new URL(publication.PATH, url.origin)
102
104
  }
@@ -0,0 +1,26 @@
1
+ import chalk from 'chalk'
2
+
3
+ const MAXIMUM_DEBUG_FAILURES = 32
4
+
5
+ export function printFailureDebug(error: unknown, url: string): void {
6
+ process.stderr.write(`\n${chalk.dim('── debug ─────────────────')}\n`)
7
+ if (url) process.stderr.write(`${chalk.dim(`url: ${url}`)}\n`)
8
+ const seen = new Set<unknown>()
9
+ const pending: Array<{ value: unknown; relation: string }> = [{ value: error, relation: 'error' }]
10
+ while (pending.length > 0 && seen.size < MAXIMUM_DEBUG_FAILURES) {
11
+ const { value, relation } = pending.shift()!
12
+ if (seen.has(value)) continue
13
+ seen.add(value)
14
+ if (!(value instanceof Error)) {
15
+ process.stderr.write(`${chalk.dim(`${relation}: ${String(value)}`)}\n`)
16
+ continue
17
+ }
18
+ process.stderr.write(
19
+ `${chalk.dim(`${relation}: ${value.constructor.name}: ${value.message}`)}\n`,
20
+ )
21
+ if (value.stack) process.stderr.write(`${chalk.dim(value.stack)}\n`)
22
+ if (value.cause !== undefined) pending.push({ value: value.cause, relation: 'caused by' })
23
+ if (value instanceof AggregateError)
24
+ for (const child of value.errors) pending.push({ value: child, relation: 'aggregate' })
25
+ }
26
+ }
package/src/lib/idp.ts CHANGED
@@ -214,6 +214,8 @@ export type IdpConfig = {
214
214
 
215
215
  export const BUILTIN_WORKOS_IDP_NAME = 'workos'
216
216
  const DEFAULT_WORKOS_API_HOST = 'https://api.workos.com'
217
+ const DEFAULT_WORKOS_CLIENT_ID = 'client_01KC29HET5F3QAQ8GNTPZ7F320'
218
+ const LEGACY_WORKOS_CLIENT_ID = 'client_01KC29HEGD7B40TV2C4QZ436BG'
217
219
  const WORKOS_CLIENT_ID_ENV_NAMES = ['WORKOS_CLIENT_ID', 'VITE_WORKOS_CLIENT_ID'] as const
218
220
 
219
221
  export function idpDir(name: string): string {
@@ -280,7 +282,17 @@ export async function readIdpConfigOrBuiltin(
280
282
  validateName(name, 'IdP')
281
283
  const store = await readIdpStore()
282
284
  if (store.idps[name]) {
283
- return await readIdpConfig(name)
285
+ const existing = await readIdpConfig(name)
286
+ const replacement = opts.persist
287
+ ? legacyBuiltinWorkosReplacement(existing, opts.clientId)
288
+ : undefined
289
+ if (!replacement) return existing
290
+ return upsertIdpConfig({
291
+ name: replacement.name,
292
+ metadata: replacement.metadata,
293
+ client: replacement.client,
294
+ builtIn: true,
295
+ })
284
296
  }
285
297
 
286
298
  const builtin = builtinIdpConfig(name, opts.clientId)
@@ -413,11 +425,30 @@ export function builtinIdpConfig(
413
425
  }
414
426
 
415
427
  export function workosClientIdFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {
416
- return WORKOS_CLIENT_ID_ENV_NAMES.map((name) => env[name]?.trim()).find(
417
- (value): value is string => typeof value === 'string' && value.length > 0,
428
+ return (
429
+ WORKOS_CLIENT_ID_ENV_NAMES.map((name) => env[name]?.trim()).find(
430
+ (value): value is string => typeof value === 'string' && value.length > 0,
431
+ ) ?? DEFAULT_WORKOS_CLIENT_ID
418
432
  )
419
433
  }
420
434
 
435
+ export function legacyBuiltinWorkosReplacement(
436
+ existing: IdpConfig,
437
+ clientIdOverride?: string,
438
+ env: NodeJS.ProcessEnv = process.env,
439
+ ): IdpConfig | undefined {
440
+ if (
441
+ existing.name !== BUILTIN_WORKOS_IDP_NAME ||
442
+ !existing.entry.builtIn ||
443
+ existing.client.client_id !== LEGACY_WORKOS_CLIENT_ID
444
+ ) {
445
+ return undefined
446
+ }
447
+ const replacement = builtinIdpConfig(existing.name, clientIdOverride, env)
448
+ if (!replacement || replacement.client.client_id === LEGACY_WORKOS_CLIENT_ID) return undefined
449
+ return replacement
450
+ }
451
+
421
452
  async function fetchOAuthAuthorizationServerMetadata(
422
453
  issuer: string,
423
454
  ): Promise<Partial<OidcMetadata> | undefined> {
@@ -1,6 +1,6 @@
1
1
  import type { InstanceEntry, InstanceStore } from './instance'
2
2
 
3
- import { formatInstanceLocation, type OwnedInstanceInfo } from './admin-instance'
3
+ import { formatInstanceState, type OwnedInstanceInfo } from './admin-instance'
4
4
  import { normalizeInstanceKernelUrl, resolveInstanceKey } from './instance'
5
5
 
6
6
  export type InstanceCandidate =
@@ -44,6 +44,6 @@ export function describeInstanceCandidate(candidate: InstanceCandidate): string
44
44
  if (candidate.source === 'bookmark') {
45
45
  return `${candidate.key} (bookmark) ${candidate.url}`
46
46
  }
47
- const extra = formatInstanceLocation(candidate.info)
47
+ const extra = formatInstanceState(candidate.info)
48
48
  return `${candidate.key} (managed) ${candidate.url}${extra ? ` — ${extra}` : ''}`
49
49
  }
@@ -1,6 +1,6 @@
1
- import type { InstanceInfo } from '../admin/instance/model'
2
1
  import type { AstraleConfig } from './config'
3
2
 
3
+ import { AdminInstanceNotFoundError, type InstanceInfo } from '../admin/instance/model'
4
4
  import { AstraleError } from '../errors'
5
5
  import {
6
6
  DEFAULT_ADMIN_TARGET_NAME,
@@ -190,19 +190,8 @@ export function isAdminDiscoveryFailure(error: unknown): boolean {
190
190
  }
191
191
 
192
192
  export function isManagedInstanceNotFound(error: unknown): boolean {
193
- if (error instanceof AstraleError && error.code === 'INSTANCE_NOT_FOUND') return true
194
- if (!(error instanceof Error)) return false
195
- if (error.name === 'NotFoundError') return true
196
- // The admin kernel reports a missing instance node as InternalKernelError
197
- // with a NOT_FOUND-prefixed message. In this lookup that's an instance
198
- // miss (config problem), not a kernel fault — map it so callers get the
199
- // typed INSTANCE_NOT_FOUND with remediation instead of a raw kernel error.
200
- if (error.name === 'InternalKernelError' && /^NOT_FOUND\b/.test(error.message)) return true
201
- const data = (error as Error & { data?: unknown }).data
202
193
  return (
203
- error.name === 'KernelError' &&
204
- !!data &&
205
- typeof data === 'object' &&
206
- (data as { type?: unknown }).type === 'NotFoundError'
194
+ error instanceof AdminInstanceNotFoundError ||
195
+ (error instanceof AstraleError && error.code === 'INSTANCE_NOT_FOUND')
207
196
  )
208
197
  }
@@ -297,22 +297,26 @@ export async function upsertInstance(
297
297
  * bookmark was repointed to a different kernel so callers can warn.
298
298
  */
299
299
  export async function upsertManagedBookmark(
300
- key: string,
301
- slug: string,
302
- rawUrl: string,
303
- organizationId?: string,
300
+ input: Readonly<{
301
+ key: string
302
+ slug: string
303
+ url: string
304
+ organizationId?: string
305
+ defaultIdentity?: string
306
+ }>,
304
307
  ): Promise<{ entry: InstanceEntry; repointedFrom?: string }> {
305
308
  const store = await readInstances()
306
- const previousUrl = store.instances[key]?.url
307
- const url = normalizeInstanceKernelUrl(rawUrl)
308
- const { entry } = await upsertInstance(key, {
309
+ const previousUrl = store.instances[input.key]?.url
310
+ const url = normalizeInstanceKernelUrl(input.url)
311
+ const { entry } = await upsertInstance(input.key, {
309
312
  url,
310
313
  issuer: url,
311
- slug,
312
- name: slug,
314
+ slug: input.slug,
315
+ name: input.slug,
313
316
  kind: 'bookmark',
314
317
  mode: 'remote',
315
- ...(organizationId ? { organizationId } : {}),
318
+ ...(input.organizationId ? { organizationId: input.organizationId } : {}),
319
+ ...(input.defaultIdentity ? { defaultIdentity: input.defaultIdentity } : {}),
316
320
  })
317
321
  return {
318
322
  entry,
@@ -362,8 +366,9 @@ export async function getActive(config?: AstraleConfig): Promise<InstanceEntry &
362
366
  export async function resolveInstance(
363
367
  identifier: string,
364
368
  config?: AstraleConfig,
369
+ opts: { persist?: boolean } = {},
365
370
  ): Promise<ResolvedInstance> {
366
- const store = await readInstances(config)
371
+ const store = await readInstances(config, opts)
367
372
  const key = resolveInstanceKey(store, identifier)
368
373
  const entry = key ? store.instances[key] : undefined
369
374
  if (!key || !entry?.url) {
package/src/lib/log.ts CHANGED
@@ -2,6 +2,7 @@ import chalk from 'chalk'
2
2
  import ora, { type Ora } from 'ora'
3
3
 
4
4
  import { AstraleError, NotImplementedError } from '../errors'
5
+ import { printFailureDebug } from './failure-debug'
5
6
  import { formatElapsed } from './format'
6
7
  import { isMachine, type MachineOpts } from './output'
7
8
 
@@ -18,28 +19,34 @@ export const log = {
18
19
 
19
20
  /** Report an error with hint (when present) and exit. `--json` / `--ci` / a
20
21
  * non-TTY stdout always get one structured JSON line on stderr. */
21
- export function fatal(e: unknown, opts?: MachineOpts): never {
22
+ export function fatal(e: unknown, opts?: MachineOpts & { readonly debug?: boolean }): never {
22
23
  // Ctrl-C at an interactive (@inquirer/prompts) prompt — exit quietly with the
23
24
  // SIGINT convention, not a red error line.
24
25
  if (e instanceof Error && e.name === 'ExitPromptError') process.exit(130)
25
- const msg = e instanceof Error ? e.message : String(e)
26
- const code = e instanceof AstraleError ? e.code : e instanceof Error ? e.name : 'Error'
26
+ const msg =
27
+ e instanceof AstraleError ? e.message : 'The CLI encountered an unexpected internal failure.'
28
+ const code = e instanceof AstraleError ? e.code : 'UNEXPECTED_ERROR'
27
29
  if (isMachine(opts)) {
28
30
  const payload: Record<string, unknown> = { error: code, message: msg }
29
31
  if (e instanceof AstraleError && e.hint) payload.hint = e.hint
30
32
  process.stderr.write(JSON.stringify(payload) + '\n')
31
- process.exit(1)
33
+ } else {
34
+ log.error(`${code}: ${msg}`)
35
+ if (e instanceof AstraleError && e.hint) log.dim(` hint: ${e.hint}`)
32
36
  }
33
- log.error(e instanceof AstraleError ? `${code}: ${msg}` : msg)
34
- if (e instanceof AstraleError && e.hint) log.dim(` hint: ${e.hint}`)
37
+ if (opts?.debug) printFailureDebug(e, '')
35
38
  process.exit(1)
36
39
  }
37
40
 
38
- /** Admit expected invalid input and exit through {@link fatal}. */
39
- export function failClosed(error: unknown, opts?: MachineOpts): never {
41
+ /** Project only a caller-authored admission failure into the CLI input family. */
42
+ export function failInput(error: unknown, opts?: MachineOpts): never {
40
43
  if (error instanceof AstraleError) fatal(error, opts)
44
+ if (!(error instanceof TypeError)) fatal(error, opts)
41
45
  fatal(
42
- new AstraleError('INVALID_INPUT', error instanceof Error ? error.message : String(error)),
46
+ new AstraleError(
47
+ 'INVALID_INPUT',
48
+ error.message.trim() ? error.message : 'CLI input is invalid.',
49
+ ),
43
50
  opts,
44
51
  )
45
52
  }
package/src/lib/output.ts CHANGED
@@ -17,7 +17,7 @@ export type RawOutputOpts = Pick<OutputOpts, 'raw' | 'json'>
17
17
  export type MachineOpts = RawOutputOpts & { readonly ci?: boolean }
18
18
 
19
19
  export const RAW_OUTPUT_OPTIONS = [
20
- { flags: '--json', description: 'Always-valid JSON (for jq)' },
20
+ { flags: '--json', description: 'JSON; streaming commands emit one JSON value per line' },
21
21
  { flags: '--raw', description: 'Unwrapped: bare scalar / raw bytes / JSON for objects' },
22
22
  ] as const
23
23
 
package/src/lib/proc.ts CHANGED
@@ -22,7 +22,11 @@ export function run(
22
22
  opts: { cwd?: string } = {},
23
23
  ): Promise<RunResult> {
24
24
  return new Promise((resolve, reject) => {
25
- const child = spawn(file, args, { cwd: opts.cwd, stdio: ['ignore', 'pipe', 'pipe'] })
25
+ const child = spawn(file, args, {
26
+ cwd: opts.cwd,
27
+ shell: false,
28
+ stdio: ['ignore', 'pipe', 'pipe'],
29
+ })
26
30
  let stdout = ''
27
31
  let stderr = ''
28
32
  child.stdout?.setEncoding('utf8')
@@ -50,7 +54,7 @@ export function runInherit(
50
54
  opts: { cwd?: string } = {},
51
55
  ): Promise<number> {
52
56
  return new Promise((resolve, reject) => {
53
- const child = spawn(file, args, { cwd: opts.cwd, stdio: 'inherit' })
57
+ const child = spawn(file, args, { cwd: opts.cwd, shell: false, stdio: 'inherit' })
54
58
  child.on('error', reject)
55
59
  child.on('close', (code) => resolve(code ?? -1))
56
60
  })
@@ -73,6 +77,7 @@ export function spawnHandle(
73
77
  return spawn(file, args, {
74
78
  cwd: opts.cwd,
75
79
  env: opts.env,
80
+ shell: false,
76
81
  // `detached` makes the child its own process-group leader so the caller can
77
82
  // tear down the whole tree (the child AND its grandchildren) with a single
78
83
  // group signal — `process.kill(-child.pid, …)`.