@astrale-os/cli 1.0.0-beta.1 → 1.0.0-beta.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 (180) hide show
  1. package/README.md +7 -11
  2. package/dist/astrale.js +3737 -3523
  3. package/dist/public/connect-core.js +396 -392
  4. package/dist/public/keys/index.js +371 -378
  5. package/dist/public/paths/index.js +371 -378
  6. package/dist/types/lib/idp.d.ts +6 -0
  7. package/dist/types/lib/meta.d.ts +2 -2
  8. package/package.json +6 -4
  9. package/src/admin/binding.ts +168 -0
  10. package/src/admin/catalog/__tests__/client.test.ts +2 -2
  11. package/src/admin/catalog/client.ts +4 -9
  12. package/src/admin/instance/__tests__/client.test.ts +2 -2
  13. package/src/admin/instance/client.ts +6 -11
  14. package/src/commands/__tests__/auth-login.test.ts +54 -1
  15. package/src/commands/__tests__/call.test.ts +5 -1
  16. package/src/commands/__tests__/domain-install-operation.test.ts +23 -7
  17. package/src/commands/__tests__/domain-install-owned.test.ts +5 -1
  18. package/src/commands/__tests__/domain-list.test.ts +83 -4
  19. package/src/commands/__tests__/install-identity-override.test.ts +30 -19
  20. package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
  21. package/src/commands/__tests__/update.test.ts +23 -16
  22. package/src/commands/domain/install.ts +11 -15
  23. package/src/commands/domain/list.ts +17 -12
  24. package/src/commands/studio.ts +20 -0
  25. package/src/commands/update.ts +9 -5
  26. package/src/connection/__tests__/errors.test.ts +16 -12
  27. package/src/connection/__tests__/exchange.test.ts +6 -5
  28. package/src/connection/exchange.ts +5 -4
  29. package/src/lib/__tests__/domain-publication.test.ts +144 -0
  30. package/src/lib/__tests__/idp-session.test.ts +22 -0
  31. package/src/lib/__tests__/idp.test.ts +2 -0
  32. package/src/lib/__tests__/instance.test.ts +6 -0
  33. package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
  34. package/src/lib/__tests__/update.test.ts +12 -0
  35. package/src/lib/domain-publication.ts +102 -0
  36. package/src/lib/idp.ts +5 -1
  37. package/src/lib/instance.ts +6 -1
  38. package/src/lib/login-flow.ts +13 -3
  39. package/src/lib/meta.ts +2 -2
  40. package/src/lib/update.ts +4 -4
  41. package/src/program/__tests__/program.test.ts +1 -1
  42. package/studio/client/dist/assets/{elk-api-D2xgMJvi.js → elk-api-lwhFo7vB.js} +1 -1
  43. package/studio/client/dist/assets/index-B-c-smo9.js +8 -0
  44. package/studio/client/dist/assets/index-BckHuAWk.js +81 -0
  45. package/studio/client/dist/assets/index-C4aNQ6dz.css +1 -0
  46. package/studio/client/dist/index.html +2 -2
  47. package/studio/package.json +3 -1
  48. package/studio/server/agent/bridge/stdio.ts +8 -1
  49. package/studio/server/agent/conversation.test.ts +10 -5
  50. package/studio/server/agent/conversation.ts +56 -8
  51. package/studio/server/agent/harness/codex/loadout.ts +18 -21
  52. package/studio/server/agent/harness/gateway/config.ts +20 -18
  53. package/studio/server/agent/harness/selection.ts +9 -1
  54. package/studio/server/agent/prompts/system.ts +3 -2
  55. package/studio/server/agent/prompts/turn.ts +13 -4
  56. package/studio/server/agent/run/completion.ts +2 -2
  57. package/studio/server/agent/run/preparation.ts +6 -5
  58. package/studio/server/agent/run/transcript.test.ts +35 -1
  59. package/studio/server/agent/run/transcript.ts +169 -3
  60. package/studio/server/agent/run/usage.test.ts +24 -0
  61. package/studio/server/agent/run/usage.ts +26 -1
  62. package/studio/server/api/canvas.ts +44 -0
  63. package/studio/server/api/comments.ts +85 -0
  64. package/studio/server/api/context.ts +61 -0
  65. package/studio/server/api/deployment.ts +39 -0
  66. package/studio/server/api/documents.ts +55 -0
  67. package/studio/server/api/domain.ts +59 -0
  68. package/studio/server/api/http.ts +45 -0
  69. package/studio/server/api/project.ts +57 -0
  70. package/studio/server/api/schema.ts +12 -0
  71. package/studio/server/api/views.ts +57 -0
  72. package/studio/server/api/workspace.ts +60 -0
  73. package/studio/server/api.test.ts +147 -0
  74. package/studio/server/api.ts +20 -422
  75. package/studio/server/cache.test.ts +111 -0
  76. package/studio/server/cache.ts +163 -9
  77. package/studio/server/cli-consumers.test.ts +494 -0
  78. package/studio/server/cli.test.ts +88 -0
  79. package/studio/server/cli.ts +240 -0
  80. package/studio/server/client-package.ts +25 -1
  81. package/studio/server/domain.test.ts +30 -2
  82. package/studio/server/domain.ts +14 -2
  83. package/studio/server/environment/dotenv-preview.test.ts +39 -0
  84. package/studio/server/environment/dotenv-preview.ts +27 -0
  85. package/studio/server/environment/files.test.ts +53 -0
  86. package/studio/server/{state/env.ts → environment/files.ts} +7 -48
  87. package/studio/server/{state → handoff}/copy.ts +21 -6
  88. package/studio/server/handoff/service.test.ts +22 -0
  89. package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
  90. package/studio/server/instances/active.ts +72 -0
  91. package/studio/server/instances/deploy-record.test.ts +31 -0
  92. package/studio/server/instances/deploy-record.ts +37 -0
  93. package/studio/server/instances/deploy.ts +56 -0
  94. package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
  95. package/studio/server/instances/probe.ts +67 -0
  96. package/studio/server/instances/status.test.ts +12 -0
  97. package/studio/server/instances/status.ts +49 -0
  98. package/studio/server/introspect/anatomy/client-tree.ts +57 -0
  99. package/studio/server/introspect/anatomy/env-fields.ts +66 -0
  100. package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
  101. package/studio/server/introspect/anatomy/source.ts +181 -0
  102. package/studio/server/introspect/anatomy/views/legacy.ts +232 -0
  103. package/studio/server/introspect/anatomy/views/routes.ts +105 -0
  104. package/studio/server/introspect/anatomy/views.ts +58 -0
  105. package/studio/server/introspect/anatomy-extras.test.ts +91 -4
  106. package/studio/server/introspect/anatomy-extras.ts +7 -726
  107. package/studio/server/introspect/anatomy.ts +46 -19
  108. package/studio/server/introspect/bundle.ts +15 -6
  109. package/studio/server/introspect/canonical-schema.test.ts +79 -0
  110. package/studio/server/introspect/canonical-schema.ts +80 -18
  111. package/studio/server/introspect/config-preview.test.ts +32 -0
  112. package/studio/server/introspect/config-preview.ts +119 -0
  113. package/studio/server/introspect/diff.test.ts +7 -11
  114. package/studio/server/introspect/diff.ts +28 -37
  115. package/studio/server/introspect/extractor.ts +8 -6
  116. package/studio/server/introspect/hash.ts +5 -2
  117. package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
  118. package/studio/server/introspect/revision.test.ts +54 -0
  119. package/studio/server/introspect/revision.ts +49 -0
  120. package/studio/server/introspect/runtime.test.ts +63 -5
  121. package/studio/server/introspect/runtime.ts +49 -3
  122. package/studio/server/introspect/schema-ir-json.ts +181 -0
  123. package/studio/server/introspect/source-overlay/annotations.ts +14 -0
  124. package/studio/server/introspect/source-overlay/handlers.ts +681 -0
  125. package/studio/server/introspect/source-overlay/kernel-calls.ts +49 -0
  126. package/studio/server/introspect/source-overlay/project.ts +248 -0
  127. package/studio/server/introspect/source-overlay/spans.ts +360 -0
  128. package/studio/server/json.ts +46 -0
  129. package/studio/server/lifecycle.ts +5 -1
  130. package/studio/server/sse.test.ts +26 -0
  131. package/studio/server/sse.ts +4 -1
  132. package/studio/server/state/baseline.test.ts +212 -1
  133. package/studio/server/state/baseline.ts +145 -29
  134. package/studio/server/state/comments.test.ts +66 -1
  135. package/studio/server/state/comments.ts +173 -10
  136. package/studio/server/state/context.ts +34 -2
  137. package/studio/server/state/documents.ts +42 -1
  138. package/studio/server/state/integrations.ts +27 -1
  139. package/studio/server/state/layout.test.ts +34 -0
  140. package/studio/server/state/layout.ts +44 -17
  141. package/studio/server/state/settings.ts +29 -22
  142. package/studio/server/state/store.test.ts +122 -0
  143. package/studio/server/state/store.ts +66 -17
  144. package/studio/server/state/visibility.ts +22 -7
  145. package/studio/server/views/model.test.ts +57 -0
  146. package/studio/server/views/model.ts +66 -0
  147. package/studio/server/views/runtime.ts +40 -0
  148. package/studio/server/views/selection-repository.test.ts +31 -0
  149. package/studio/server/views/selection-repository.ts +60 -0
  150. package/studio/server/views/session.test.ts +73 -0
  151. package/studio/server/views/session.ts +166 -0
  152. package/studio/server/{state/views.test.ts → views/target.test.ts} +8 -159
  153. package/studio/server/views/target.ts +248 -0
  154. package/studio/server/watch.ts +1 -1
  155. package/studio/server/workspace/catalog.test.ts +17 -0
  156. package/studio/server/workspace/catalog.ts +43 -0
  157. package/studio/server/{state → workspace}/create.test.ts +1 -1
  158. package/studio/server/{state → workspace}/create.ts +1 -1
  159. package/studio/server/{state → workspace}/git.ts +1 -1
  160. package/studio/server/workspace/updates.ts +83 -0
  161. package/studio/server/workspace-state.ts +1 -1
  162. package/studio/shared/contracts/README.md +16 -0
  163. package/studio/shared/contracts/agent.ts +270 -0
  164. package/studio/shared/contracts/runtime.ts +50 -0
  165. package/studio/shared/contracts/schema.ts +462 -0
  166. package/studio/shared/contracts/surface.test.ts +319 -0
  167. package/studio/shared/contracts/workspace.ts +274 -0
  168. package/studio/shared/layout.test.ts +116 -0
  169. package/studio/shared/schema/identity.test.ts +58 -0
  170. package/studio/shared/schema/identity.ts +82 -0
  171. package/studio/shared/types.ts +11 -1099
  172. package/studio/tsconfig.json +1 -1
  173. package/viewer/dist/main.js +35 -35
  174. package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
  175. package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
  176. package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
  177. package/studio/server/state/catalog.ts +0 -117
  178. package/studio/server/state/instance.ts +0 -280
  179. package/studio/server/state/updates.ts +0 -63
  180. package/studio/server/state/views.ts +0 -535
@@ -1,117 +0,0 @@
1
- /**
2
- * catalog.ts — the domain catalog for the canvas "Import a domain" picker. It merges:
3
- * - the kernel (always present + required),
4
- * - the LOCAL domains detected in this workspace,
5
- * - a curated set of FAKED external service domains (placeholders until a real
6
- * registry exists).
7
- * Each entry carries a lucide-style SVG icon + a one-line description.
8
- */
9
- import type { DomainCatalogEntry } from '../../shared/types'
10
-
11
- const icon = (inner: string) =>
12
- `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${inner}</svg>`
13
-
14
- const HEXAGON = icon(
15
- '<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><path d="m7.5 4.27 9 5.15"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>',
16
- )
17
- const BOXES = icon(
18
- '<path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/>',
19
- )
20
- const BELL = icon(
21
- '<path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/>',
22
- )
23
- const CARD = icon(
24
- '<rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/>',
25
- )
26
- const USERS = icon(
27
- '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
28
- )
29
- const CHART = icon(
30
- '<line x1="12" x2="12" y1="20" y2="10"/><line x1="18" x2="18" y1="20" y2="4"/><line x1="6" x2="6" y1="20" y2="16"/>',
31
- )
32
- const SEARCH = icon('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>')
33
- const DRIVE = icon(
34
- '<line x1="22" x2="2" y1="12" y2="12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/><line x1="6" x2="6.01" y1="16" y2="16"/><line x1="10" x2="10.01" y1="16" y2="16"/>',
35
- )
36
- const SPARKLES = icon(
37
- '<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/>',
38
- )
39
-
40
- const KERNEL: DomainCatalogEntry = {
41
- origin: 'kernel.astrale.ai',
42
- name: 'Kernel',
43
- kind: 'kernel',
44
- description: 'The typed graph every domain is built on.',
45
- icon: HEXAGON,
46
- required: true,
47
- }
48
-
49
- const EXTERNAL: DomainCatalogEntry[] = [
50
- {
51
- origin: 'notifications.astrale.ai',
52
- name: 'Notifications',
53
- kind: 'external',
54
- description: 'Email, push & SMS delivery.',
55
- icon: BELL,
56
- },
57
- {
58
- origin: 'payments.astrale.ai',
59
- name: 'Payments',
60
- kind: 'external',
61
- description: 'Charges, subscriptions & invoices.',
62
- icon: CARD,
63
- },
64
- {
65
- origin: 'identity.astrale.ai',
66
- name: 'Identity',
67
- kind: 'external',
68
- description: 'SSO, directory sync & users.',
69
- icon: USERS,
70
- },
71
- {
72
- origin: 'analytics.astrale.ai',
73
- name: 'Analytics',
74
- kind: 'external',
75
- description: 'Events, funnels & dashboards.',
76
- icon: CHART,
77
- },
78
- {
79
- origin: 'search.astrale.ai',
80
- name: 'Search',
81
- kind: 'external',
82
- description: 'Full-text & vector search.',
83
- icon: SEARCH,
84
- },
85
- {
86
- origin: 'storage.astrale.ai',
87
- name: 'Storage',
88
- kind: 'external',
89
- description: 'Files, blobs & a CDN.',
90
- icon: DRIVE,
91
- },
92
- {
93
- origin: 'ai.astrale.ai',
94
- name: 'AI Gateway',
95
- kind: 'external',
96
- description: 'LLM routing & embeddings.',
97
- icon: SPARKLES,
98
- },
99
- ]
100
-
101
- function humanize(slug: string): string {
102
- return slug
103
- .replace(/[-_]+/g, ' ')
104
- .replace(/\b\w/g, (c) => c.toUpperCase())
105
- .trim()
106
- }
107
-
108
- export function buildCatalog(locals: { origin: string; id: string }[]): DomainCatalogEntry[] {
109
- const localEntries: DomainCatalogEntry[] = locals.map((d) => ({
110
- origin: d.origin,
111
- name: humanize(d.id),
112
- kind: 'local',
113
- description: 'A domain in this workspace.',
114
- icon: BOXES,
115
- }))
116
- return [KERNEL, ...localEntries, ...EXTERNAL]
117
- }
@@ -1,280 +0,0 @@
1
- /**
2
- * state/instance.ts — the deploy/install bridge to Astrale.
3
- *
4
- * The ACTIVE instance is GLOBAL (not per-domain) and the `astrale` CLI owns it —
5
- * we never keep our own copy: `listInstances` reads `astrale instance list`,
6
- * `setActiveInstance` runs `astrale instance use`. Install + drift are GROUND
7
- * TRUTH, queried from the target instance (`astrale introspect <origin>`) — NOT
8
- * a local record — so a deploy done outside the studio (CLI/terminal) is still
9
- * seen.
10
- *
11
- * Deploy (`pnpm prod`) = the managed astrale adapter: deploy + auto-install on
12
- * the configured instance; we capture the printed service URL.
13
- */
14
- import { readFileSync } from 'node:fs'
15
- import { join } from 'node:path'
16
-
17
- import type {
18
- DeployRecord,
19
- DeployResult,
20
- InstanceInfo,
21
- InstanceStatus,
22
- InstancesState,
23
- } from '../../shared/types'
24
- import type { DomainHandle } from '../domain'
25
-
26
- import { schemaHashOf } from '../introspect/hash'
27
- import { readJson, writeJson } from './store'
28
-
29
- const DEPLOY_REC = 'deploy.json'
30
-
31
- function hasProdScript(root: string): boolean {
32
- try {
33
- const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
34
- return typeof pkg?.scripts?.prod === 'string'
35
- } catch {
36
- return false
37
- }
38
- }
39
-
40
- async function astraleJson(args: string[]): Promise<any | null> {
41
- try {
42
- const proc = Bun.spawn(['astrale', ...args], { stdout: 'pipe', stderr: 'ignore' })
43
- const out = await new Response(proc.stdout).text()
44
- await proc.exited
45
- return JSON.parse(out)
46
- } catch {
47
- return null
48
- }
49
- }
50
-
51
- // ── global: the CLI's instances (source of truth = `astrale instance ...`) ──
52
-
53
- /** The active instance name — from `astrale instance active` (local, reliable). */
54
- async function activeName(): Promise<string | null> {
55
- const a = await astraleJson(['instance', 'active', '--json'])
56
- return a?.name ?? null
57
- }
58
-
59
- /** Public wrapper — the create-domain endpoint stamps this as `--instance` on the scaffold. */
60
- export const activeInstanceName = activeName
61
-
62
- export async function listInstances(): Promise<InstancesState> {
63
- // The plain `instance list` fetches signing keys for EVERY bookmark, so one
64
- // unreachable bookmark (e.g. a stopped localhost kernel) errors out the whole
65
- // command. `--bookmarked` is local-only (reliable); `--admin-only` adds the
66
- // managed instances best-effort (skipped if the admin call is unavailable).
67
- const local = await astraleJson(['instance', 'list', '--bookmarked', '--json'])
68
- const active: string | null = local?.active ?? (await activeName())
69
- const instances: InstanceInfo[] = []
70
- for (const b of local?.bookmarks ?? []) {
71
- instances.push({
72
- name: b.name,
73
- url: b.url ?? '',
74
- active: !!b.active || b.name === active,
75
- kind: 'bookmark',
76
- })
77
- }
78
- const managed = await astraleJson(['instance', 'list', '--admin-only', '--json'])
79
- for (const m of managed?.instances ?? []) {
80
- if (!m.slug || instances.some((i) => i.name === m.slug)) continue
81
- instances.push({ name: m.slug, url: m.url ?? '', active: m.slug === active, kind: 'managed' })
82
- }
83
- if (active && !instances.some((i) => i.name === active)) {
84
- instances.unshift({ name: active, url: '', active: true, kind: 'bookmark' })
85
- }
86
- return { active, instances }
87
- }
88
-
89
- export async function setActiveInstance(
90
- name: string,
91
- ): Promise<{ ok: boolean; active: string | null; output: string }> {
92
- try {
93
- // --adopt-default: never block on an interactive identity prompt;
94
- // --skip-jwks-check: don't do a network /meta↔JWKS check (which fails for an
95
- // unreachable bookmark like a stopped localhost kernel). Switching is then a
96
- // deterministic local write to instances.json.
97
- const proc = Bun.spawn(
98
- ['astrale', 'instance', 'use', name, '--adopt-default', '--skip-jwks-check'],
99
- { stdout: 'pipe', stderr: 'pipe' },
100
- )
101
- const [out, err] = await Promise.all([
102
- new Response(proc.stdout).text(),
103
- new Response(proc.stderr).text(),
104
- ])
105
- const code = await proc.exited
106
- const combined = `${out}\n${err}`.trim()
107
- if (code !== 0) return { ok: false, active: await activeName(), output: combined.slice(-1000) }
108
- return { ok: true, active: await activeName(), output: combined.slice(-1000) }
109
- } catch (e: any) {
110
- return { ok: false, active: null, output: String(e?.message ?? e) }
111
- }
112
- }
113
-
114
- // ── per-domain: deploy target + GROUND-TRUTH install/drift (queried from the instance) ──
115
-
116
- export function lastDeploy(root: string): DeployRecord | null {
117
- return readJson<DeployRecord | null>(root, DEPLOY_REC, null)
118
- }
119
-
120
- /**
121
- * Ask the target instance for its public installation introspection. Installed
122
- * ⇒ the Kernel returns the admitted Bundle, whose root is the exact schema value
123
- * hashed locally; `DOMAIN_NOT_INSTALLED` ⇒ not installed; anything else
124
- * (offline / not authed / unknown instance) ⇒ unknown (never falsely
125
- * "not installed").
126
- */
127
- export function installedDomainCommand(origin: string, instance: string): string[] {
128
- return ['astrale', 'introspect', origin, '--bundle', '--json', '-i', instance]
129
- }
130
-
131
- type InstalledDomainProbe =
132
- | { state: 'installed'; root: unknown }
133
- | { state: 'not-installed' | 'unknown'; root: null }
134
-
135
- function jsonObject(text: string): Record<string, unknown> | null {
136
- try {
137
- const value = JSON.parse(text)
138
- return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null
139
- } catch {
140
- return null
141
- }
142
- }
143
-
144
- export function installedDomainProbeResult(
145
- stdout: string,
146
- stderr: string,
147
- exitCode: number,
148
- ): InstalledDomainProbe {
149
- const output = jsonObject(stdout)
150
- const diagnostic = jsonObject(stderr)
151
- const error = output?.error ?? diagnostic?.error
152
- if (
153
- error === 'DOMAIN_NOT_INSTALLED' ||
154
- error === 'NOT_FOUND' ||
155
- /\b(?:DOMAIN_NOT_INSTALLED|NOT_FOUND)\b/.test(`${stdout}\n${stderr}`)
156
- ) {
157
- return { state: 'not-installed', root: null }
158
- }
159
-
160
- if (exitCode === 0) {
161
- const bundle = output?.bundle
162
- if (
163
- bundle !== null &&
164
- typeof bundle === 'object' &&
165
- !Array.isArray(bundle) &&
166
- Object.prototype.hasOwnProperty.call(bundle, 'root')
167
- ) {
168
- return { state: 'installed', root: (bundle as { root: unknown }).root }
169
- }
170
- }
171
- return { state: 'unknown', root: null }
172
- }
173
-
174
- export function installedBundleRootHash(probe: InstalledDomainProbe): string | null {
175
- return probe.state === 'installed' ? schemaHashOf(probe.root) : null
176
- }
177
-
178
- async function getInstalledDomain(
179
- origin: string,
180
- instance: string,
181
- timeoutMs = 8000,
182
- ): Promise<InstalledDomainProbe> {
183
- try {
184
- const proc = Bun.spawn(installedDomainCommand(origin, instance), {
185
- stdout: 'pipe',
186
- stderr: 'pipe',
187
- })
188
- const timer = setTimeout(() => {
189
- try {
190
- proc.kill()
191
- } catch {
192
- /* already exited */
193
- }
194
- }, timeoutMs)
195
- try {
196
- const [out, err] = await Promise.all([
197
- new Response(proc.stdout).text(),
198
- new Response(proc.stderr).text(),
199
- ])
200
- const code = await proc.exited
201
- return installedDomainProbeResult(out, err, code)
202
- } finally {
203
- clearTimeout(timer)
204
- }
205
- } catch {
206
- return { state: 'unknown', root: null }
207
- }
208
- }
209
-
210
- export async function instanceStatus(
211
- handle: DomainHandle,
212
- deployTarget: string | null,
213
- origin: string | null,
214
- localHash: string | null,
215
- ): Promise<InstanceStatus> {
216
- const deployable = hasProdScript(handle.root)
217
- const last = lastDeploy(handle.root)
218
- let install: InstanceStatus['install'] = 'unknown'
219
- let installedHash: string | null = null
220
- let drift: InstanceStatus['drift'] = 'unknown'
221
-
222
- if (origin && deployTarget) {
223
- const probe = await getInstalledDomain(origin, deployTarget)
224
- if (probe.state === 'installed') {
225
- install = 'installed'
226
- installedHash = installedBundleRootHash(probe)
227
- drift =
228
- installedHash && localHash
229
- ? installedHash === localHash
230
- ? 'in-sync'
231
- : 'drifted'
232
- : 'unknown'
233
- } else if (probe.state === 'not-installed') {
234
- install = 'not-installed'
235
- }
236
- }
237
-
238
- return { deployTarget, deployable, install, drift, localHash, installedHash, lastDeploy: last }
239
- }
240
-
241
- const SVC_URL = /https:\/\/[\w-]+\.svc\.[\w.-]+\.astrale\.ai\b/
242
-
243
- /** Run `pnpm prod` (deploy + managed auto-install). Outward-facing — only call on an explicit request. */
244
- export async function runDeploy(
245
- handle: DomainHandle,
246
- localHash: string | null,
247
- ): Promise<DeployResult> {
248
- if (!hasProdScript(handle.root)) {
249
- return {
250
- ok: false,
251
- url: null,
252
- output:
253
- 'This domain has no "prod" script in package.json — it is not deployable via `pnpm prod`.',
254
- }
255
- }
256
- let out = ''
257
- let err = ''
258
- let code = 1
259
- try {
260
- const proc = Bun.spawn(['pnpm', 'prod'], { cwd: handle.root, stdout: 'pipe', stderr: 'pipe' })
261
- ;[out, err] = await Promise.all([
262
- new Response(proc.stdout).text(),
263
- new Response(proc.stderr).text(),
264
- ])
265
- code = await proc.exited
266
- } catch (e: any) {
267
- return { ok: false, url: null, output: `failed to start pnpm: ${e?.message ?? e}` }
268
- }
269
- const combined = `${out}\n${err}`.trim()
270
- const url = combined.match(SVC_URL)?.[0] ?? null
271
- const ok = code === 0
272
- if (ok)
273
- writeJson(handle.root, DEPLOY_REC, {
274
- at: new Date().toISOString(),
275
- schemaHash: localHash ?? '',
276
- ok: true,
277
- url: url ?? undefined,
278
- } satisfies DeployRecord)
279
- return { ok, url, output: combined.slice(-6000) }
280
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * state/updates.ts — the "is anything stale?" bridge for the header Update badge.
3
- *
4
- * We don't reimplement staleness: the `astrale` CLI owns it. `astrale update
5
- * --check --json`, run in the DOMAIN ROOT (so its @astrale-os/* SDK-dep axis sees
6
- * THIS project), emits a unified `{ stale, cli, sdk }` report. We run it with
7
- * stdout piped and read it regardless of exit code — a stale result exits 10 by
8
- * design, which is not an error here. Best-effort: anything unexpected (no
9
- * `astrale` on PATH, bad output) collapses to "nothing stale" so the badge stays
10
- * hidden rather than nagging on a hiccup.
11
- */
12
- import type { StaleReport } from '../../shared/types'
13
-
14
- const NOT_STALE: StaleReport = {
15
- stale: false,
16
- cli: { stale: false, managed: true },
17
- sdk: { stale: false, inProject: false, outdated: [] },
18
- }
19
-
20
- export async function getUpdates(root: string): Promise<StaleReport> {
21
- try {
22
- const proc = Bun.spawn(['astrale', 'update', '--check', '--json'], {
23
- cwd: root,
24
- stdout: 'pipe',
25
- stderr: 'ignore',
26
- })
27
- const out = await new Response(proc.stdout).text()
28
- await proc.exited // exit 10 = "stale" (expected); we already have stdout
29
- const parsed = JSON.parse(out) as StaleReport
30
- return typeof parsed?.stale === 'boolean' ? parsed : NOT_STALE
31
- } catch {
32
- return NOT_STALE
33
- }
34
- }
35
-
36
- export interface UpdateApplyResult {
37
- ok: boolean
38
- output: string
39
- }
40
-
41
- /**
42
- * Apply the update for the header "Update now" button — `astrale update --yes`,
43
- * run in the domain root. The CLI does the work non-interactively and resiliently
44
- * (binary + skills + SDK deps; a binary that can't self-update warns but doesn't
45
- * block the others), so we just spawn it and capture the combined output to show.
46
- */
47
- export async function applyUpdates(root: string): Promise<UpdateApplyResult> {
48
- try {
49
- const proc = Bun.spawn(['astrale', 'update', '--yes'], {
50
- cwd: root,
51
- stdout: 'pipe',
52
- stderr: 'pipe',
53
- })
54
- const [out, err] = await Promise.all([
55
- new Response(proc.stdout).text(),
56
- new Response(proc.stderr).text(),
57
- ])
58
- const code = await proc.exited
59
- return { ok: code === 0, output: (out + err).trim() }
60
- } catch (e) {
61
- return { ok: false, output: String(e) }
62
- }
63
- }