@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.
- package/README.md +7 -11
- package/dist/astrale.js +3737 -3523
- package/dist/public/connect-core.js +396 -392
- package/dist/public/keys/index.js +371 -378
- package/dist/public/paths/index.js +371 -378
- package/dist/types/lib/idp.d.ts +6 -0
- package/dist/types/lib/meta.d.ts +2 -2
- package/package.json +6 -4
- package/src/admin/binding.ts +168 -0
- package/src/admin/catalog/__tests__/client.test.ts +2 -2
- package/src/admin/catalog/client.ts +4 -9
- package/src/admin/instance/__tests__/client.test.ts +2 -2
- package/src/admin/instance/client.ts +6 -11
- package/src/commands/__tests__/auth-login.test.ts +54 -1
- package/src/commands/__tests__/call.test.ts +5 -1
- package/src/commands/__tests__/domain-install-operation.test.ts +23 -7
- package/src/commands/__tests__/domain-install-owned.test.ts +5 -1
- package/src/commands/__tests__/domain-list.test.ts +83 -4
- package/src/commands/__tests__/install-identity-override.test.ts +30 -19
- package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
- package/src/commands/__tests__/update.test.ts +23 -16
- package/src/commands/domain/install.ts +11 -15
- package/src/commands/domain/list.ts +17 -12
- package/src/commands/studio.ts +20 -0
- package/src/commands/update.ts +9 -5
- package/src/connection/__tests__/errors.test.ts +16 -12
- package/src/connection/__tests__/exchange.test.ts +6 -5
- package/src/connection/exchange.ts +5 -4
- package/src/lib/__tests__/domain-publication.test.ts +144 -0
- package/src/lib/__tests__/idp-session.test.ts +22 -0
- package/src/lib/__tests__/idp.test.ts +2 -0
- package/src/lib/__tests__/instance.test.ts +6 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
- package/src/lib/__tests__/update.test.ts +12 -0
- package/src/lib/domain-publication.ts +102 -0
- package/src/lib/idp.ts +5 -1
- package/src/lib/instance.ts +6 -1
- package/src/lib/login-flow.ts +13 -3
- package/src/lib/meta.ts +2 -2
- package/src/lib/update.ts +4 -4
- package/src/program/__tests__/program.test.ts +1 -1
- package/studio/client/dist/assets/{elk-api-D2xgMJvi.js → elk-api-lwhFo7vB.js} +1 -1
- package/studio/client/dist/assets/index-B-c-smo9.js +8 -0
- package/studio/client/dist/assets/index-BckHuAWk.js +81 -0
- package/studio/client/dist/assets/index-C4aNQ6dz.css +1 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/package.json +3 -1
- package/studio/server/agent/bridge/stdio.ts +8 -1
- package/studio/server/agent/conversation.test.ts +10 -5
- package/studio/server/agent/conversation.ts +56 -8
- package/studio/server/agent/harness/codex/loadout.ts +18 -21
- package/studio/server/agent/harness/gateway/config.ts +20 -18
- package/studio/server/agent/harness/selection.ts +9 -1
- package/studio/server/agent/prompts/system.ts +3 -2
- package/studio/server/agent/prompts/turn.ts +13 -4
- package/studio/server/agent/run/completion.ts +2 -2
- package/studio/server/agent/run/preparation.ts +6 -5
- package/studio/server/agent/run/transcript.test.ts +35 -1
- package/studio/server/agent/run/transcript.ts +169 -3
- package/studio/server/agent/run/usage.test.ts +24 -0
- package/studio/server/agent/run/usage.ts +26 -1
- package/studio/server/api/canvas.ts +44 -0
- package/studio/server/api/comments.ts +85 -0
- package/studio/server/api/context.ts +61 -0
- package/studio/server/api/deployment.ts +39 -0
- package/studio/server/api/documents.ts +55 -0
- package/studio/server/api/domain.ts +59 -0
- package/studio/server/api/http.ts +45 -0
- package/studio/server/api/project.ts +57 -0
- package/studio/server/api/schema.ts +12 -0
- package/studio/server/api/views.ts +57 -0
- package/studio/server/api/workspace.ts +60 -0
- package/studio/server/api.test.ts +147 -0
- package/studio/server/api.ts +20 -422
- package/studio/server/cache.test.ts +111 -0
- package/studio/server/cache.ts +163 -9
- package/studio/server/cli-consumers.test.ts +494 -0
- package/studio/server/cli.test.ts +88 -0
- package/studio/server/cli.ts +240 -0
- package/studio/server/client-package.ts +25 -1
- package/studio/server/domain.test.ts +30 -2
- package/studio/server/domain.ts +14 -2
- package/studio/server/environment/dotenv-preview.test.ts +39 -0
- package/studio/server/environment/dotenv-preview.ts +27 -0
- package/studio/server/environment/files.test.ts +53 -0
- package/studio/server/{state/env.ts → environment/files.ts} +7 -48
- package/studio/server/{state → handoff}/copy.ts +21 -6
- package/studio/server/handoff/service.test.ts +22 -0
- package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
- package/studio/server/instances/active.ts +72 -0
- package/studio/server/instances/deploy-record.test.ts +31 -0
- package/studio/server/instances/deploy-record.ts +37 -0
- package/studio/server/instances/deploy.ts +56 -0
- package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
- package/studio/server/instances/probe.ts +67 -0
- package/studio/server/instances/status.test.ts +12 -0
- package/studio/server/instances/status.ts +49 -0
- package/studio/server/introspect/anatomy/client-tree.ts +57 -0
- package/studio/server/introspect/anatomy/env-fields.ts +66 -0
- package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
- package/studio/server/introspect/anatomy/source.ts +181 -0
- package/studio/server/introspect/anatomy/views/legacy.ts +232 -0
- package/studio/server/introspect/anatomy/views/routes.ts +105 -0
- package/studio/server/introspect/anatomy/views.ts +58 -0
- package/studio/server/introspect/anatomy-extras.test.ts +91 -4
- package/studio/server/introspect/anatomy-extras.ts +7 -726
- package/studio/server/introspect/anatomy.ts +46 -19
- package/studio/server/introspect/bundle.ts +15 -6
- package/studio/server/introspect/canonical-schema.test.ts +79 -0
- package/studio/server/introspect/canonical-schema.ts +80 -18
- package/studio/server/introspect/config-preview.test.ts +32 -0
- package/studio/server/introspect/config-preview.ts +119 -0
- package/studio/server/introspect/diff.test.ts +7 -11
- package/studio/server/introspect/diff.ts +28 -37
- package/studio/server/introspect/extractor.ts +8 -6
- package/studio/server/introspect/hash.ts +5 -2
- package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
- package/studio/server/introspect/revision.test.ts +54 -0
- package/studio/server/introspect/revision.ts +49 -0
- package/studio/server/introspect/runtime.test.ts +63 -5
- package/studio/server/introspect/runtime.ts +49 -3
- package/studio/server/introspect/schema-ir-json.ts +181 -0
- package/studio/server/introspect/source-overlay/annotations.ts +14 -0
- package/studio/server/introspect/source-overlay/handlers.ts +681 -0
- package/studio/server/introspect/source-overlay/kernel-calls.ts +49 -0
- package/studio/server/introspect/source-overlay/project.ts +248 -0
- package/studio/server/introspect/source-overlay/spans.ts +360 -0
- package/studio/server/json.ts +46 -0
- package/studio/server/lifecycle.ts +5 -1
- package/studio/server/sse.test.ts +26 -0
- package/studio/server/sse.ts +4 -1
- package/studio/server/state/baseline.test.ts +212 -1
- package/studio/server/state/baseline.ts +145 -29
- package/studio/server/state/comments.test.ts +66 -1
- package/studio/server/state/comments.ts +173 -10
- package/studio/server/state/context.ts +34 -2
- package/studio/server/state/documents.ts +42 -1
- package/studio/server/state/integrations.ts +27 -1
- package/studio/server/state/layout.test.ts +34 -0
- package/studio/server/state/layout.ts +44 -17
- package/studio/server/state/settings.ts +29 -22
- package/studio/server/state/store.test.ts +122 -0
- package/studio/server/state/store.ts +66 -17
- package/studio/server/state/visibility.ts +22 -7
- package/studio/server/views/model.test.ts +57 -0
- package/studio/server/views/model.ts +66 -0
- package/studio/server/views/runtime.ts +40 -0
- package/studio/server/views/selection-repository.test.ts +31 -0
- package/studio/server/views/selection-repository.ts +60 -0
- package/studio/server/views/session.test.ts +73 -0
- package/studio/server/views/session.ts +166 -0
- package/studio/server/{state/views.test.ts → views/target.test.ts} +8 -159
- package/studio/server/views/target.ts +248 -0
- package/studio/server/watch.ts +1 -1
- package/studio/server/workspace/catalog.test.ts +17 -0
- package/studio/server/workspace/catalog.ts +43 -0
- package/studio/server/{state → workspace}/create.test.ts +1 -1
- package/studio/server/{state → workspace}/create.ts +1 -1
- package/studio/server/{state → workspace}/git.ts +1 -1
- package/studio/server/workspace/updates.ts +83 -0
- package/studio/server/workspace-state.ts +1 -1
- package/studio/shared/contracts/README.md +16 -0
- package/studio/shared/contracts/agent.ts +270 -0
- package/studio/shared/contracts/runtime.ts +50 -0
- package/studio/shared/contracts/schema.ts +462 -0
- package/studio/shared/contracts/surface.test.ts +319 -0
- package/studio/shared/contracts/workspace.ts +274 -0
- package/studio/shared/layout.test.ts +116 -0
- package/studio/shared/schema/identity.test.ts +58 -0
- package/studio/shared/schema/identity.ts +82 -0
- package/studio/shared/types.ts +11 -1099
- package/studio/tsconfig.json +1 -1
- package/viewer/dist/main.js +35 -35
- package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
- package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
- package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
- package/studio/server/state/catalog.ts +0 -117
- package/studio/server/state/instance.ts +0 -280
- package/studio/server/state/updates.ts +0 -63
- 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
|
-
}
|