@astrale-os/cli 0.4.0-alpha.13

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 (219) hide show
  1. package/.check-workspace.cjs +40 -0
  2. package/README.md +151 -0
  3. package/dist/astrale.js +59749 -0
  4. package/package.json +90 -0
  5. package/src/command.ts +40 -0
  6. package/src/commands/__tests__/admin-instance.test.ts +73 -0
  7. package/src/commands/__tests__/auth-login.test.ts +178 -0
  8. package/src/commands/__tests__/auth-token.test.ts +223 -0
  9. package/src/commands/__tests__/call.test.ts +72 -0
  10. package/src/commands/__tests__/domain-list.test.ts +74 -0
  11. package/src/commands/__tests__/help-contract.test.ts +136 -0
  12. package/src/commands/__tests__/install-identity-override.test.ts +65 -0
  13. package/src/commands/__tests__/instance-bookmark.test.ts +101 -0
  14. package/src/commands/__tests__/instance-create-hosts.test.ts +29 -0
  15. package/src/commands/__tests__/instance-list-rows.test.ts +63 -0
  16. package/src/commands/__tests__/logs.test.ts +117 -0
  17. package/src/commands/__tests__/ls.test.ts +25 -0
  18. package/src/commands/__tests__/setup-plan.test.ts +61 -0
  19. package/src/commands/admin/status.ts +61 -0
  20. package/src/commands/admin/use.ts +77 -0
  21. package/src/commands/auth/login.ts +82 -0
  22. package/src/commands/auth/logout.ts +50 -0
  23. package/src/commands/auth/status.ts +86 -0
  24. package/src/commands/auth/token.ts +162 -0
  25. package/src/commands/browser.ts +207 -0
  26. package/src/commands/call.ts +300 -0
  27. package/src/commands/describe.ts +182 -0
  28. package/src/commands/domain/install.ts +420 -0
  29. package/src/commands/domain/list.ts +154 -0
  30. package/src/commands/domain/publish.ts +155 -0
  31. package/src/commands/get.ts +60 -0
  32. package/src/commands/identity/create.ts +26 -0
  33. package/src/commands/identity/delete.ts +18 -0
  34. package/src/commands/identity/export.ts +66 -0
  35. package/src/commands/identity/import.ts +101 -0
  36. package/src/commands/identity/list.ts +56 -0
  37. package/src/commands/identity/register.ts +170 -0
  38. package/src/commands/identity/sync.ts +32 -0
  39. package/src/commands/identity/unsync.ts +24 -0
  40. package/src/commands/identity/use.ts +18 -0
  41. package/src/commands/identity/whoami.ts +34 -0
  42. package/src/commands/idp/add.ts +150 -0
  43. package/src/commands/idp/list.ts +57 -0
  44. package/src/commands/idp/refresh.ts +38 -0
  45. package/src/commands/idp/remove.ts +36 -0
  46. package/src/commands/idp/show.ts +29 -0
  47. package/src/commands/instance/active.ts +64 -0
  48. package/src/commands/instance/bookmark.ts +72 -0
  49. package/src/commands/instance/create.ts +69 -0
  50. package/src/commands/instance/delete.ts +72 -0
  51. package/src/commands/instance/forget.ts +26 -0
  52. package/src/commands/instance/list.ts +149 -0
  53. package/src/commands/instance/status.ts +42 -0
  54. package/src/commands/instance/use.ts +210 -0
  55. package/src/commands/logs.ts +347 -0
  56. package/src/commands/ls.ts +229 -0
  57. package/src/commands/query.ts +32 -0
  58. package/src/commands/setup.ts +54 -0
  59. package/src/commands/status.ts +60 -0
  60. package/src/commands/studio.ts +401 -0
  61. package/src/commands/token.ts +77 -0
  62. package/src/commands/update.ts +267 -0
  63. package/src/commands/use.ts +87 -0
  64. package/src/errors.ts +65 -0
  65. package/src/kernel/__tests__/auth.test.ts +77 -0
  66. package/src/kernel/__tests__/errors.test.ts +43 -0
  67. package/src/kernel/__tests__/remote-routing.test.ts +70 -0
  68. package/src/kernel/auth.ts +234 -0
  69. package/src/kernel/ca-fetch.ts +119 -0
  70. package/src/kernel/client.ts +191 -0
  71. package/src/kernel/errors.ts +280 -0
  72. package/src/kernel/expand.ts +217 -0
  73. package/src/kernel/index.ts +14 -0
  74. package/src/kernel/options.ts +22 -0
  75. package/src/kernel/remote-routing.ts +88 -0
  76. package/src/kernel/run.ts +63 -0
  77. package/src/kernel/types.ts +14 -0
  78. package/src/lib/__tests__/admin-target.test.ts +112 -0
  79. package/src/lib/__tests__/binary.test.ts +56 -0
  80. package/src/lib/__tests__/command-dx.test.ts +58 -0
  81. package/src/lib/__tests__/concurrency.test.ts +62 -0
  82. package/src/lib/__tests__/config.test.ts +53 -0
  83. package/src/lib/__tests__/design.test.ts +99 -0
  84. package/src/lib/__tests__/domain-identity.test.ts +60 -0
  85. package/src/lib/__tests__/format.test.ts +22 -0
  86. package/src/lib/__tests__/fs-atomic.test.ts +104 -0
  87. package/src/lib/__tests__/identity.test.ts +79 -0
  88. package/src/lib/__tests__/idp-session.driver.ts +53 -0
  89. package/src/lib/__tests__/idp-session.test.ts +357 -0
  90. package/src/lib/__tests__/idp.test.ts +385 -0
  91. package/src/lib/__tests__/instance-candidates.test.ts +73 -0
  92. package/src/lib/__tests__/instance-target.test.ts +183 -0
  93. package/src/lib/__tests__/instance.test.ts +136 -0
  94. package/src/lib/__tests__/keys.test.ts +129 -0
  95. package/src/lib/__tests__/local-status.test.ts +202 -0
  96. package/src/lib/__tests__/output.test.ts +150 -0
  97. package/src/lib/__tests__/panel.test.ts +40 -0
  98. package/src/lib/__tests__/port.test.ts +44 -0
  99. package/src/lib/__tests__/prompt.test.ts +25 -0
  100. package/src/lib/__tests__/sdk-deps.test.ts +68 -0
  101. package/src/lib/__tests__/self.test.ts +272 -0
  102. package/src/lib/__tests__/studio-server-deps.test.ts +74 -0
  103. package/src/lib/__tests__/table.test.ts +53 -0
  104. package/src/lib/__tests__/update.test.ts +246 -0
  105. package/src/lib/__tests__/use-target.test.ts +56 -0
  106. package/src/lib/__tests__/validation.test.ts +34 -0
  107. package/src/lib/admin-domain.ts +25 -0
  108. package/src/lib/admin-instance.ts +26 -0
  109. package/src/lib/admin-target.ts +217 -0
  110. package/src/lib/binary.ts +131 -0
  111. package/src/lib/browser.ts +150 -0
  112. package/src/lib/command-dx.ts +161 -0
  113. package/src/lib/concurrency.ts +31 -0
  114. package/src/lib/config.ts +45 -0
  115. package/src/lib/domain-identity.ts +49 -0
  116. package/src/lib/env.ts +49 -0
  117. package/src/lib/format.ts +4 -0
  118. package/src/lib/fs-atomic.ts +126 -0
  119. package/src/lib/identity.ts +256 -0
  120. package/src/lib/idp-session.ts +134 -0
  121. package/src/lib/idp.ts +876 -0
  122. package/src/lib/instance-candidates.ts +49 -0
  123. package/src/lib/instance-target.ts +182 -0
  124. package/src/lib/instance.ts +395 -0
  125. package/src/lib/keys.ts +294 -0
  126. package/src/lib/local-status.ts +152 -0
  127. package/src/lib/log.ts +116 -0
  128. package/src/lib/login-flow.ts +164 -0
  129. package/src/lib/meta.ts +86 -0
  130. package/src/lib/output.ts +222 -0
  131. package/src/lib/panel.ts +61 -0
  132. package/src/lib/paths.ts +11 -0
  133. package/src/lib/port.ts +41 -0
  134. package/src/lib/proc.ts +82 -0
  135. package/src/lib/prompt.ts +136 -0
  136. package/src/lib/provision-instance.ts +170 -0
  137. package/src/lib/sdk-deps.ts +104 -0
  138. package/src/lib/self.ts +166 -0
  139. package/src/lib/skills.ts +171 -0
  140. package/src/lib/table.ts +62 -0
  141. package/src/lib/update.ts +315 -0
  142. package/src/lib/use-target.ts +24 -0
  143. package/src/lib/validation.ts +59 -0
  144. package/src/program.ts +200 -0
  145. package/src/registry.ts +59 -0
  146. package/src/setup/__tests__/util.test.ts +29 -0
  147. package/src/setup/engine.ts +83 -0
  148. package/src/setup/render.ts +109 -0
  149. package/src/setup/steps/admin.ts +78 -0
  150. package/src/setup/steps/agent-browser.ts +81 -0
  151. package/src/setup/steps/auth.ts +54 -0
  152. package/src/setup/steps/domain.ts +68 -0
  153. package/src/setup/steps/index.ts +18 -0
  154. package/src/setup/steps/instance.ts +119 -0
  155. package/src/setup/steps/skills-bridge.ts +70 -0
  156. package/src/setup/steps/skills.ts +59 -0
  157. package/src/setup/types.ts +61 -0
  158. package/src/setup/util.ts +34 -0
  159. package/src/test-utils.ts +18 -0
  160. package/studio/client/dist/assets/index-DOwzZAEK.css +1 -0
  161. package/studio/client/dist/assets/index-wtU0Zxhy.js +183 -0
  162. package/studio/client/dist/index.html +13 -0
  163. package/studio/package.json +62 -0
  164. package/studio/server/agent/ask.ts +68 -0
  165. package/studio/server/agent/bridge-mcp.ts +182 -0
  166. package/studio/server/agent/bridge.ts +188 -0
  167. package/studio/server/agent/claude.ts +666 -0
  168. package/studio/server/agent/mock.ts +186 -0
  169. package/studio/server/agent/prompt.ts +202 -0
  170. package/studio/server/agent/registry.ts +29 -0
  171. package/studio/server/agent/runner.ts +484 -0
  172. package/studio/server/agent/schema-map.ts +112 -0
  173. package/studio/server/agent/types.ts +120 -0
  174. package/studio/server/api.ts +574 -0
  175. package/studio/server/cache.ts +138 -0
  176. package/studio/server/detect.ts +81 -0
  177. package/studio/server/domain.ts +70 -0
  178. package/studio/server/index.ts +136 -0
  179. package/studio/server/introspect/anatomy-extras.ts +398 -0
  180. package/studio/server/introspect/anatomy.ts +108 -0
  181. package/studio/server/introspect/bundle.ts +57 -0
  182. package/studio/server/introspect/core-extractor.ts +119 -0
  183. package/studio/server/introspect/core.ts +44 -0
  184. package/studio/server/introspect/diff.ts +133 -0
  185. package/studio/server/introspect/extractor.ts +102 -0
  186. package/studio/server/introspect/hash.ts +21 -0
  187. package/studio/server/introspect/overlay-tsmorph.ts +874 -0
  188. package/studio/server/introspect/overlay.ts +57 -0
  189. package/studio/server/introspect/runtime.ts +99 -0
  190. package/studio/server/introspect/schema-refs.ts +46 -0
  191. package/studio/server/lifecycle.ts +38 -0
  192. package/studio/server/sse.ts +57 -0
  193. package/studio/server/state/baseline.ts +211 -0
  194. package/studio/server/state/catalog.ts +117 -0
  195. package/studio/server/state/comments.ts +321 -0
  196. package/studio/server/state/context.ts +167 -0
  197. package/studio/server/state/copy.ts +156 -0
  198. package/studio/server/state/create.ts +156 -0
  199. package/studio/server/state/documents.ts +70 -0
  200. package/studio/server/state/env.ts +161 -0
  201. package/studio/server/state/git.ts +75 -0
  202. package/studio/server/state/handoff.ts +55 -0
  203. package/studio/server/state/harness-gateway.ts +181 -0
  204. package/studio/server/state/harness-token.ts +0 -0
  205. package/studio/server/state/instance.ts +244 -0
  206. package/studio/server/state/integrations.ts +55 -0
  207. package/studio/server/state/layout.ts +55 -0
  208. package/studio/server/state/settings.ts +39 -0
  209. package/studio/server/state/store.ts +97 -0
  210. package/studio/server/state/updates.ts +63 -0
  211. package/studio/server/state/usage.ts +37 -0
  212. package/studio/server/state/views.ts +138 -0
  213. package/studio/server/state/visibility.ts +33 -0
  214. package/studio/server/watch.ts +81 -0
  215. package/studio/server/workspace-state.ts +26 -0
  216. package/studio/server/workspace-watch.ts +101 -0
  217. package/studio/shared/types.ts +873 -0
  218. package/studio/tsconfig.json +23 -0
  219. package/tsconfig.json +14 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * core.ts — assembles a domain's StudioCore: the genesis node/edge graph
3
+ * extracted from `defineCore`, gated on installed deps. Never throws; a failed
4
+ * import or a domain with no core both become a well-formed (empty) render state.
5
+ */
6
+ import type { StudioCore } from '../../shared/types'
7
+
8
+ import { type DomainHandle, depsInstalled } from '../domain'
9
+ import { readSettings } from '../state/settings'
10
+ import { coreExtract } from './runtime'
11
+
12
+ export async function buildCore(handle: DomainHandle): Promise<StudioCore> {
13
+ const at = new Date().toISOString()
14
+ const empty = (error: StudioCore['error']): StudioCore => ({
15
+ domain: handle.origin ?? handle.id,
16
+ nodes: [],
17
+ edges: [],
18
+ error,
19
+ extractedAt: at,
20
+ })
21
+
22
+ if (!depsInstalled(handle.root)) {
23
+ return empty({
24
+ message: 'dependencies not installed — run `pnpm install` in the domain for core extraction',
25
+ })
26
+ }
27
+
28
+ const r = await coreExtract(
29
+ handle.domainFile,
30
+ handle.root,
31
+ readSettings(handle.root).introspectTimeoutMs,
32
+ )
33
+ // r.ok && r.core===null → the domain simply defines no core (not an error).
34
+ if (!r.ok) return empty(r.error ?? { message: 'core extraction failed' })
35
+ if (!r.core) return empty(null)
36
+
37
+ return {
38
+ domain: r.core.domain,
39
+ nodes: r.core.nodes,
40
+ edges: r.core.edges,
41
+ error: null,
42
+ extractedAt: at,
43
+ }
44
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * diff.ts — structural diff of two Schema IRs + breaking/additive classification.
3
+ * Shared by change tracking (baseline) and data versioning. Pure.
4
+ *
5
+ * Optionality is encoded in JSON Schema as a `type` array containing 'null'.
6
+ * BREAKING: any removal/rename, prop type change, optional→required, method
7
+ * signature change. ADDITIVE: only additions, or required→optional.
8
+ */
9
+ import type {
10
+ IrClass,
11
+ IrInterface,
12
+ IrMethod,
13
+ JsonSchema,
14
+ SchemaChange,
15
+ SchemaIR,
16
+ } from '../../shared/types'
17
+
18
+ export function isOptional(s: JsonSchema | undefined): boolean {
19
+ if (!s) return false
20
+ return Array.isArray(s.type) ? s.type.includes('null') : false
21
+ }
22
+
23
+ export function baseType(s: JsonSchema | undefined): string {
24
+ if (!s) return 'unknown'
25
+ const t = s.type
26
+ if (Array.isArray(t)) {
27
+ const base = t.filter((x) => x !== 'null')
28
+ return (base.length ? base.join('|') : 'null') + (s.enum ? `(${s.enum.join(',')})` : '')
29
+ }
30
+ if (typeof t === 'string') return t + (s.enum ? `(${s.enum.join(',')})` : '')
31
+ return s.enum ? `enum(${s.enum.join(',')})` : 'unknown'
32
+ }
33
+
34
+ function methodSig(m: IrMethod): string {
35
+ const params = Object.entries(m.params ?? {})
36
+ .map(([k, v]) => `${k}:${baseType(v)}${isOptional(v) ? '?' : ''}`)
37
+ .sort()
38
+ .join(',')
39
+ return `static=${m.static};in=${params};out=${baseType(m.returns)};inh=${m.inheritance}`
40
+ }
41
+
42
+ export function diffSchemas(prev: SchemaIR | null, next: SchemaIR | null): SchemaChange[] {
43
+ const changes: SchemaChange[] = []
44
+ if (!prev || !next) return changes
45
+
46
+ diffMembers(prev.interfaces ?? {}, next.interfaces ?? {}, 'interface', changes)
47
+ diffMembers(prev.classes ?? {}, next.classes ?? {}, 'class', changes)
48
+ return changes
49
+ }
50
+
51
+ function memberKind(m: IrClass | IrInterface): 'class' | 'edge' | 'interface' {
52
+ if ((m as IrInterface).type === 'interface') return 'interface'
53
+ return (m as IrClass).type === 'edge' ? 'edge' : 'class'
54
+ }
55
+
56
+ function diffMembers(
57
+ prev: Record<string, IrClass | IrInterface>,
58
+ next: Record<string, IrClass | IrInterface>,
59
+ _scope: 'class' | 'interface',
60
+ out: SchemaChange[],
61
+ ): void {
62
+ for (const name of Object.keys(next)) {
63
+ if (!prev[name]) {
64
+ const k = memberKind(next[name])
65
+ out.push({ kind: `${k}-added` as SchemaChange['kind'], target: name, breaking: false })
66
+ }
67
+ }
68
+ for (const name of Object.keys(prev)) {
69
+ if (!next[name]) {
70
+ const k = memberKind(prev[name])
71
+ out.push({ kind: `${k}-removed` as SchemaChange['kind'], target: name, breaking: true })
72
+ continue
73
+ }
74
+ diffMemberBody(name, prev[name], next[name], out)
75
+ }
76
+ }
77
+
78
+ function diffMemberBody(
79
+ name: string,
80
+ a: IrClass | IrInterface,
81
+ b: IrClass | IrInterface,
82
+ out: SchemaChange[],
83
+ ): void {
84
+ const pa = a.properties ?? {}
85
+ const pb = b.properties ?? {}
86
+ for (const p of Object.keys(pb)) {
87
+ if (!pa[p]) out.push({ kind: 'prop-added', target: `${name}.${p}`, breaking: false })
88
+ }
89
+ for (const p of Object.keys(pa)) {
90
+ if (!pb[p]) {
91
+ out.push({ kind: 'prop-removed', target: `${name}.${p}`, breaking: true })
92
+ continue
93
+ }
94
+ if (baseType(pa[p]) !== baseType(pb[p])) {
95
+ out.push({
96
+ kind: 'prop-type-changed',
97
+ target: `${name}.${p}`,
98
+ detail: `${baseType(pa[p])} → ${baseType(pb[p])}`,
99
+ breaking: true,
100
+ })
101
+ }
102
+ const wasOpt = isOptional(pa[p])
103
+ const nowOpt = isOptional(pb[p])
104
+ if (wasOpt !== nowOpt) {
105
+ out.push({
106
+ kind: 'prop-required-changed',
107
+ target: `${name}.${p}`,
108
+ detail: wasOpt ? 'optional → required' : 'required → optional',
109
+ breaking: wasOpt && !nowOpt, // optional→required is breaking
110
+ })
111
+ }
112
+ }
113
+
114
+ const ma = a.methods ?? {}
115
+ const mb = b.methods ?? {}
116
+ for (const m of Object.keys(mb)) {
117
+ if (!ma[m]) out.push({ kind: 'method-added', target: `${name}.${m}`, breaking: false })
118
+ }
119
+ for (const m of Object.keys(ma)) {
120
+ if (!mb[m]) {
121
+ out.push({ kind: 'method-removed', target: `${name}.${m}`, breaking: true })
122
+ continue
123
+ }
124
+ if (methodSig(ma[m]) !== methodSig(mb[m])) {
125
+ out.push({ kind: 'method-signature-changed', target: `${name}.${m}`, breaking: true })
126
+ }
127
+ }
128
+ }
129
+
130
+ export function classify(changes: SchemaChange[]): 'none' | 'additive' | 'breaking' {
131
+ if (changes.length === 0) return 'none'
132
+ return changes.some((c) => c.breaking) ? 'breaking' : 'additive'
133
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * extractor.ts — the Bun-executed island. Spawned as a short-lived subprocess by
3
+ * runtime.ts. It imports ONLY the domain's pure `schema/index.ts` module graph
4
+ * (never astrale.config.ts or domain.ts → deps → integrations), reads the
5
+ * compiled `D.$.ir`, and prints the SchemaIR as JSON to stdout.
6
+ *
7
+ * bun extractor.ts <schemaIndexPath> <domainDir>
8
+ *
9
+ * `compileDomain`/`serialize` are PURE (no IO, no env), so importing the schema
10
+ * is side-effect-free. A thrown error prints { ok:false } and exits 0 — the
11
+ * driver treats it as a render state, never a crash.
12
+ */
13
+ const schemaPath = process.argv[2]
14
+ const domainDir = process.argv[3] ?? process.cwd()
15
+
16
+ async function main() {
17
+ if (!schemaPath) throw new Error('extractor: missing <schemaIndexPath>')
18
+ const mod: Record<string, any> = await import(schemaPath)
19
+
20
+ // Lazily resolve the domain's own kernel-dsl serializer (used by the fallback
21
+ // path AND to recover imported-interface bodies). Cached after first load.
22
+ let dsl: Record<string, any> | null = null
23
+ const loadDsl = async (): Promise<Record<string, any>> => {
24
+ if (dsl) return dsl
25
+ const loaded: Record<string, any> = await import(
26
+ Bun.resolveSync('@astrale-os/kernel-dsl', domainDir)
27
+ )
28
+ dsl = loaded
29
+ return loaded
30
+ }
31
+
32
+ // Prefer the conventional `D` (compiled), but accept a compiled domain exported
33
+ // under ANY name — e.g. ai-gateway exports its `compileDomain(...)` result as
34
+ // `Gateway`, not `D`. A compiled domain is recognized by shape (`.$.ir`).
35
+ let ir: unknown = mod?.D?.$?.ir
36
+ if (!ir) {
37
+ for (const v of Object.values(mod)) {
38
+ const candidate = (v as any)?.$?.ir
39
+ if (candidate) {
40
+ ir = candidate
41
+ break
42
+ }
43
+ }
44
+ }
45
+ if (!ir) {
46
+ // Fallback: serialize a raw schema — the conventional `schema`, else any
47
+ // exported `defineSchema(...)` result (recognized by its `kind === 'schema'`).
48
+ const schema =
49
+ mod?.schema ??
50
+ Object.values(mod).find(
51
+ (v) =>
52
+ typeof (v as any)?.domain === 'string' && !!(v as any)?.classes && !!(v as any)?.imports,
53
+ )
54
+ if (!schema) {
55
+ throw new Error(
56
+ 'schema entry exports no compiled domain (a `compileDomain(...)` value with `.$.ir`, e.g. `D`) nor a raw `schema`',
57
+ )
58
+ }
59
+ const d = await loadDsl()
60
+ if (typeof d.serialize !== 'function') throw new Error('kernel-dsl.serialize unavailable')
61
+ ir = d.serialize(schema)
62
+ }
63
+
64
+ // Recover member bodies of IMPORTED interfaces (kernel mixins + cross-domain
65
+ // interfaces). `serialize()` emits them in `ir.imports` as name-only
66
+ // descriptors, so re-serialize each imported schema (KernelSchema, other
67
+ // domains) and collect its interface definitions. Best-effort: a single import
68
+ // failing to serialize must never break extraction of the primary IR.
69
+ const importedInterfaces: Record<string, unknown> = {}
70
+ const importSchemas: any[] = Array.isArray(mod?.schema?.imports) ? mod.schema.imports : []
71
+ if (importSchemas.length > 0) {
72
+ try {
73
+ const d = await loadDsl()
74
+ if (typeof d.serialize === 'function') {
75
+ for (const imp of importSchemas) {
76
+ try {
77
+ const sub = d.serialize(imp)
78
+ for (const [name, def] of Object.entries(sub?.interfaces ?? {})) {
79
+ if (!(name in importedInterfaces)) importedInterfaces[name] = def
80
+ }
81
+ } catch {
82
+ /* skip this import — keep going */
83
+ }
84
+ }
85
+ }
86
+ } catch {
87
+ /* dsl unavailable — degrade gracefully (no imported-interface bodies) */
88
+ }
89
+ }
90
+
91
+ process.stdout.write(JSON.stringify({ ok: true, ir, importedInterfaces }))
92
+ }
93
+
94
+ main().catch((err: any) => {
95
+ process.stdout.write(
96
+ JSON.stringify({
97
+ ok: false,
98
+ error: { message: String(err?.message ?? err), stack: String(err?.stack ?? '') },
99
+ }),
100
+ )
101
+ process.exit(0)
102
+ })
@@ -0,0 +1,21 @@
1
+ import { createHash } from 'node:crypto'
2
+
3
+ /** Deterministic JSON with stable key order — so cosmetic edits/reordering don't churn the hash. */
4
+ export function canonicalJSON(value: unknown): string {
5
+ return JSON.stringify(sortKeys(value))
6
+ }
7
+
8
+ function sortKeys(v: any): any {
9
+ if (Array.isArray(v)) return v.map(sortKeys)
10
+ if (v && typeof v === 'object') {
11
+ const out: Record<string, any> = {}
12
+ for (const k of Object.keys(v).sort()) out[k] = sortKeys(v[k])
13
+ return out
14
+ }
15
+ return v
16
+ }
17
+
18
+ /** sha256 over canonical JSON, short-prefixed — the schemaHash (identifies a schema version). */
19
+ export function schemaHashOf(value: unknown): string {
20
+ return 'sha-' + createHash('sha256').update(canonicalJSON(value)).digest('hex').slice(0, 12)
21
+ }