@astrale-os/cli 1.0.0-beta.3 → 1.0.0-beta.30
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 +80 -6
- package/dist/astrale.js +55471 -58674
- package/dist/public/connect-core.js +2158 -30706
- package/dist/public/keys/index.js +1016 -29541
- package/dist/public/paths/index.js +370 -28654
- package/dist/types/admin/contract.d.ts +26 -0
- package/dist/types/admin/instance/client.d.ts +20 -0
- package/dist/types/admin/instance/index.d.ts +2 -0
- package/dist/types/admin/instance/model.d.ts +6 -10
- package/dist/types/connection/auth.d.ts +3 -0
- package/dist/types/connection/call.d.ts +3 -0
- package/dist/types/connection/command.d.ts +27 -0
- package/dist/types/connection/credential.d.ts +14 -0
- package/dist/types/connection/errors.d.ts +1 -0
- package/dist/types/connection/exchange.d.ts +9 -0
- package/dist/types/connection/failure/classify.d.ts +2 -0
- package/dist/types/connection/failure/index.d.ts +5 -0
- package/dist/types/connection/failure/model.d.ts +25 -0
- package/dist/types/connection/failure/render.d.ts +3 -0
- package/dist/types/connection/index.d.ts +8 -0
- package/dist/types/connection/reasons.d.ts +37 -0
- package/dist/types/connection/self.d.ts +37 -0
- package/dist/types/connection/session.d.ts +31 -0
- package/dist/types/connection/target.d.ts +38 -0
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/lib/admin-instance.d.ts +17 -0
- package/dist/types/lib/admin-target.d.ts +11 -36
- package/dist/types/lib/config.d.ts +13 -59
- package/dist/types/lib/failure-debug.d.ts +1 -0
- package/dist/types/lib/idp.d.ts +25 -173
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/instance.d.ts +30 -88
- package/dist/types/lib/log.d.ts +5 -3
- package/dist/types/lib/output.d.ts +1 -1
- package/dist/types/lib/proc.d.ts +43 -0
- package/dist/types/lib/self.d.ts +9 -0
- package/dist/types/lib/update.d.ts +109 -0
- package/dist/types/lib/validation.d.ts +4 -1
- package/dist/types/state/exchange-credentials.d.ts +6 -2
- package/dist/types/state/files.d.ts +2 -0
- package/dist/types/state/index.d.ts +3 -2
- package/dist/types/state/paths.d.ts +2 -0
- package/dist/types/state/session-routes.d.ts +10 -0
- package/package.json +16 -19
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/.spec/architecture.md +12 -5
- package/src/admin/__tests__/fixture.ts +25 -0
- package/src/admin/catalog/.spec/api.d.ts +1 -3
- package/src/admin/catalog/.spec/architecture.md +5 -4
- package/src/admin/catalog/__tests__/client.test.ts +143 -69
- package/src/admin/catalog/client.ts +40 -54
- package/src/admin/catalog/model.ts +3 -4
- package/src/admin/contract.ts +47 -0
- package/src/admin/graph/.spec/api.d.ts +4 -10
- package/src/admin/graph/nodes.ts +1 -1
- package/src/admin/instance/.spec/api.d.ts +5 -9
- package/src/admin/instance/.spec/architecture.md +7 -7
- package/src/admin/instance/__tests__/client.test.ts +157 -175
- package/src/admin/instance/client.ts +39 -128
- package/src/admin/instance/index.ts +1 -2
- package/src/admin/instance/model.ts +12 -18
- package/src/commands/__tests__/admin-instance.test.ts +11 -8
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/call.test.ts +34 -0
- package/src/commands/__tests__/domain-install-operation.test.ts +30 -22
- package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
- package/src/commands/__tests__/domain-list.test.ts +5 -25
- package/src/commands/__tests__/install-direct.test.ts +115 -8
- package/src/commands/__tests__/install-identity-override.test.ts +6 -17
- package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
- package/src/commands/__tests__/instance-status.test.ts +139 -0
- package/src/commands/__tests__/introspect-parse.test.ts +80 -1
- package/src/commands/__tests__/logs.test.ts +250 -2
- package/src/commands/__tests__/read-commands.test.ts +118 -10
- package/src/commands/__tests__/token-ttl.test.ts +49 -4
- package/src/commands/__tests__/update.test.ts +25 -3
- package/src/commands/__tests__/view.test.ts +50 -2
- package/src/commands/auth/logout.ts +2 -1
- package/src/commands/browser.ts +29 -0
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +71 -59
- package/src/commands/domain/install.ts +73 -46
- package/src/commands/domain/list.ts +1 -1
- package/src/commands/domain/publish.ts +14 -11
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +15 -16
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +8 -8
- package/src/commands/instance/create.ts +8 -16
- package/src/commands/instance/delete.ts +12 -9
- package/src/commands/instance/list.ts +2 -2
- package/src/commands/instance/status.ts +66 -20
- package/src/commands/instance/use.ts +16 -12
- package/src/commands/introspect.ts +53 -41
- package/src/commands/logs.ts +123 -36
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +35 -32
- package/src/commands/session/analyze.ts +26 -4
- package/src/commands/token.ts +42 -27
- package/src/commands/ui/__tests__/commands.test.ts +152 -0
- package/src/commands/ui/add.ts +51 -0
- package/src/commands/ui/doctor.ts +21 -0
- package/src/commands/ui/init.ts +38 -0
- package/src/commands/ui/list.ts +26 -0
- package/src/commands/ui/preset-apply.ts +19 -0
- package/src/commands/ui/preset-list.ts +12 -0
- package/src/commands/ui/shared.ts +25 -0
- package/src/commands/update.ts +89 -48
- package/src/commands/view.ts +82 -37
- package/src/connection/.spec/api.d.ts +7 -8
- package/src/connection/.spec/architecture.md +14 -5
- package/src/connection/.spec/flows/session.ts +2 -2
- package/src/connection/.spec/laws/connection.ts +6 -2
- package/src/connection/.spec/layout.ts +1 -0
- package/src/connection/__tests__/auth.test.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +21 -1
- package/src/connection/__tests__/errors.test.ts +27 -20
- package/src/connection/__tests__/exchange.test.ts +64 -11
- package/src/connection/__tests__/failure-fixtures.ts +39 -0
- package/src/connection/__tests__/failure-safety.test.ts +75 -0
- package/src/connection/__tests__/self.test.ts +31 -11
- package/src/connection/__tests__/session.test.ts +8 -4
- package/src/connection/__tests__/target.test.ts +2 -0
- package/src/connection/auth.ts +13 -0
- package/src/connection/call.ts +1 -1
- package/src/connection/credential.ts +2 -2
- package/src/connection/errors.ts +1 -355
- package/src/connection/exchange.ts +110 -61
- package/src/connection/failure/classify.ts +110 -0
- package/src/connection/failure/index.ts +18 -0
- package/src/connection/failure/model.ts +28 -0
- package/src/connection/failure/render.ts +116 -0
- package/src/connection/reasons.ts +16 -0
- package/src/connection/self.ts +39 -21
- package/src/connection/session.ts +27 -9
- package/src/errors.ts +3 -2
- package/src/graph/.spec/api.d.ts +12 -5
- package/src/graph/.spec/layout.ts +9 -1
- package/src/graph/__tests__/mutation.test.ts +2 -2
- package/src/graph/__tests__/query.test.ts +25 -6
- package/src/graph/class.ts +30 -0
- package/src/graph/index.ts +1 -0
- package/src/graph/query.ts +21 -32
- package/src/identity/.spec/api.d.ts +2 -2
- package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
- package/src/identity/__tests__/registration.test.ts +1 -1
- package/src/identity/__tests__/registry.test.ts +4 -0
- package/src/identity/registration.ts +3 -3
- package/src/identity/registry.ts +2 -0
- package/src/keys/__tests__/keys.test.ts +4 -0
- package/src/keys/credential.ts +1 -0
- package/src/lib/__tests__/admin-target.test.ts +12 -0
- package/src/lib/__tests__/binary.test.ts +16 -1
- package/src/lib/__tests__/browser-retention.test.ts +212 -0
- package/src/lib/__tests__/command-dx.test.ts +1 -1
- package/src/lib/__tests__/config.test.ts +27 -0
- package/src/lib/__tests__/domain-publication.test.ts +10 -16
- package/src/lib/__tests__/idp.test.ts +34 -4
- package/src/lib/__tests__/instance-candidates.test.ts +1 -3
- package/src/lib/__tests__/instance-target.test.ts +7 -39
- package/src/lib/__tests__/log-errors.test.ts +64 -0
- package/src/lib/__tests__/skills.test.ts +809 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
- package/src/lib/__tests__/update.test.ts +264 -11
- package/src/lib/__tests__/view-assets.test.ts +25 -0
- package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
- package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +26 -3
- package/src/lib/__tests__/view-session.test.ts +2 -1
- package/src/lib/admin-instance.ts +14 -8
- package/src/lib/admin-target.ts +21 -2
- package/src/lib/binary.ts +22 -5
- package/src/lib/browser-retention.ts +210 -0
- package/src/lib/command-dx.ts +9 -9
- package/src/lib/config.ts +12 -1
- package/src/lib/domain-publication.ts +9 -7
- package/src/lib/failure-debug.ts +26 -0
- package/src/lib/idp.ts +34 -3
- package/src/lib/instance-candidates.ts +2 -2
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/instance.ts +16 -11
- package/src/lib/log.ts +16 -9
- package/src/lib/output.ts +1 -1
- package/src/lib/proc.ts +7 -2
- package/src/lib/provision-instance.ts +34 -57
- package/src/lib/skills/lock.ts +117 -0
- package/src/lib/skills/sync.ts +814 -0
- package/src/lib/skills.ts +1 -52
- package/src/lib/update.ts +298 -45
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/assets.ts +11 -2
- package/src/lib/view/external-open-intent.ts +41 -0
- package/src/lib/view/external-open-origins.ts +37 -0
- package/src/lib/view/host-capabilities.ts +17 -0
- package/src/lib/view/resolve.ts +1 -1
- package/src/lib/view/server.ts +18 -3
- package/src/lib/view/session.ts +2 -0
- package/src/program/.spec/api.d.ts +1 -0
- package/src/program/__tests__/program.test.ts +163 -6
- package/src/program/argv.ts +14 -0
- package/src/program/build.ts +26 -4
- package/src/program/command.ts +1 -0
- package/src/program/index.ts +1 -0
- package/src/program/registry.ts +2 -1
- package/src/setup/__tests__/instance-step.test.ts +27 -10
- package/src/setup/steps/instance.ts +22 -15
- package/src/setup/steps/skills.ts +40 -38
- package/src/state/.spec/api.d.ts +21 -2
- package/src/state/.spec/architecture.md +18 -4
- package/src/state/.spec/layout.ts +1 -0
- package/src/state/__tests__/exchange-credentials.test.ts +88 -42
- package/src/state/__tests__/files.test.ts +24 -1
- package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
- package/src/state/__tests__/paths.test.ts +1 -0
- package/src/state/__tests__/session-routes.test.ts +138 -0
- package/src/state/exchange-credentials.ts +22 -9
- package/src/state/files.ts +41 -0
- package/src/state/index.ts +3 -1
- package/src/state/paths.ts +3 -0
- package/src/state/session-routes.ts +34 -0
- package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
- package/src/telemetry/__tests__/recorder.test.ts +1 -0
- package/src/telemetry/__tests__/retention.test.ts +267 -0
- package/src/telemetry/__tests__/settings.test.ts +102 -0
- package/src/telemetry/__tests__/store-scan.test.ts +128 -0
- package/src/telemetry/__tests__/trigger.test.ts +33 -4
- package/src/telemetry/analyze.ts +24 -2
- package/src/telemetry/recorder.ts +15 -4
- package/src/telemetry/retention.ts +176 -0
- package/src/telemetry/session.ts +18 -12
- package/src/telemetry/settings.ts +64 -11
- package/src/telemetry/store.ts +92 -9
- package/src/telemetry/trigger.ts +16 -28
- package/src/ui/.spec/api.d.ts +14 -0
- package/src/ui/.spec/architecture.md +14 -0
- package/src/ui/.spec/laws.ts +50 -0
- package/src/ui/.spec/layout.ts +16 -0
- package/src/ui/__tests__/ui.test.ts +1742 -0
- package/src/ui/index.ts +13 -0
- package/src/ui/lock.ts +89 -0
- package/src/ui/model.ts +83 -0
- package/src/ui/operations.ts +1044 -0
- package/src/ui/project.ts +333 -0
- package/src/ui/release.ts +398 -0
- package/src/ui/runner.ts +18 -0
- package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
- package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
- package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
- package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
- package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
- package/studio/client/dist/index.html +4 -3
- package/studio/package.json +8 -14
- package/studio/server/agent/ask.test.ts +1 -1
- package/studio/server/agent/bridge/grant.test.ts +1 -1
- package/studio/server/agent/bridge/routes.test.ts +1 -1
- package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
- package/studio/server/agent/harness/gateway/token.test.ts +1 -1
- package/studio/server/agent/harness/gateway/token.ts +3 -3
- package/studio/server/agent/prompts/anchors.test.ts +41 -32
- package/studio/server/agent/prompts/anchors.ts +23 -51
- package/studio/server/agent/prompts/system.test.ts +4 -4
- package/studio/server/agent/prompts/system.ts +5 -5
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/agent/run/preparation.ts +1 -1
- package/studio/server/api/canvas.ts +0 -1
- package/studio/server/api/context.ts +1 -1
- package/studio/server/api/deployment.ts +1 -1
- package/studio/server/api/views.ts +2 -2
- package/studio/server/api/workspace.ts +1 -1
- package/studio/server/api-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +1 -1
- package/studio/server/cache.test.ts +24 -18
- package/studio/server/cache.ts +10 -54
- package/studio/server/cli-consumers.test.ts +31 -3
- package/studio/server/domain.test.ts +50 -68
- package/studio/server/domain.ts +82 -63
- package/studio/server/handoff/copy.ts +2 -2
- package/studio/server/index.ts +1 -1
- package/studio/server/instances/probe.test.ts +1 -1
- package/studio/server/introspect/anatomy/views/routes.ts +46 -37
- package/studio/server/introspect/anatomy/views.ts +10 -16
- package/studio/server/introspect/anatomy-extras.test.ts +29 -56
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +6 -9
- package/studio/server/introspect/bundle.ts +0 -3
- package/studio/server/introspect/canonical-schema.test.ts +155 -424
- package/studio/server/introspect/canonical-schema.ts +248 -601
- package/studio/server/introspect/core.ts +14 -22
- package/studio/server/introspect/diff.test.ts +21 -9
- package/studio/server/introspect/diff.ts +19 -64
- package/studio/server/introspect/extractor.ts +23 -122
- package/studio/server/introspect/overlay-tsmorph.test.ts +116 -241
- package/studio/server/introspect/overlay-tsmorph.ts +0 -1
- package/studio/server/introspect/overlay.ts +3 -51
- package/studio/server/introspect/revision.test.ts +24 -28
- package/studio/server/introspect/revision.ts +11 -22
- package/studio/server/introspect/runtime.test.ts +77 -195
- package/studio/server/introspect/runtime.ts +5 -67
- package/studio/server/introspect/schema-ir-json.test.ts +81 -0
- package/studio/server/introspect/schema-ir-json.ts +39 -69
- package/studio/server/introspect/schema-refs.test.ts +43 -88
- package/studio/server/introspect/schema-refs.ts +14 -49
- package/studio/server/introspect/source-overlay/handlers.ts +116 -636
- package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
- package/studio/server/introspect/source-overlay/spans.ts +15 -45
- package/studio/server/lifecycle.ts +4 -1
- package/studio/server/state/baseline.test.ts +14 -9
- package/studio/server/state/baseline.ts +3 -5
- package/studio/server/state/documents.test.ts +69 -0
- package/studio/server/state/documents.ts +57 -10
- package/studio/server/state/visibility.ts +1 -5
- package/studio/server/views/model.ts +3 -3
- package/studio/server/views/target.test.ts +18 -76
- package/studio/server/views/target.ts +24 -50
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +8 -4
- package/studio/server/workspace/updates.ts +23 -1
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/runtime.ts +4 -0
- package/studio/shared/contracts/schema.ts +44 -131
- package/studio/shared/contracts/surface.test.ts +15 -17
- package/studio/shared/contracts/workspace.ts +5 -11
- package/studio/shared/schema/identity.test.ts +20 -44
- package/studio/shared/schema/identity.ts +9 -24
- package/studio/tsconfig.json +2 -1
- package/tsconfig.json +1 -1
- package/viewer/dist/index.html +21 -0
- package/viewer/dist/main.js +41 -62
- package/.check-workspace.cjs +0 -40
- package/src/admin/binding.ts +0 -168
- package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
- package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
- package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
- package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
- package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
- package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
- package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
- package/studio/server/introspect/core-extractor.ts +0 -139
- package/studio/server/introspect/overlay.test.ts +0 -72
- package/studio/server/introspect/source-overlay/annotations.ts +0 -14
|
@@ -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
|
|
204
|
-
|
|
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
|
}
|
package/src/lib/instance.ts
CHANGED
|
@@ -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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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(
|
|
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 =
|
|
26
|
-
|
|
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
|
-
|
|
33
|
+
} else {
|
|
34
|
+
log.error(`${code}: ${msg}`)
|
|
35
|
+
if (e instanceof AstraleError && e.hint) log.dim(` hint: ${e.hint}`)
|
|
32
36
|
}
|
|
33
|
-
|
|
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
|
-
/**
|
|
39
|
-
export function
|
|
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(
|
|
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: '
|
|
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, {
|
|
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, …)`.
|
|
@@ -9,12 +9,10 @@ import { createOwnedInstance } from './admin-instance'
|
|
|
9
9
|
import { setActive, upsertManagedBookmark } from './instance'
|
|
10
10
|
import { withSpinner } from './log'
|
|
11
11
|
import { isMachine } from './output'
|
|
12
|
-
import { promptSelect } from './prompt'
|
|
13
12
|
import { validateSlug } from './validation'
|
|
14
13
|
|
|
15
14
|
export type ProvisionOpts = KernelCommandOpts &
|
|
16
15
|
AdminTargetCommandOpts & {
|
|
17
|
-
hostId?: string
|
|
18
16
|
// Global flags (program.ts) that force non-interactive — mirrors `instance use`.
|
|
19
17
|
ci?: boolean
|
|
20
18
|
noPrompt?: boolean
|
|
@@ -35,10 +33,10 @@ export type ProvisionResult = {
|
|
|
35
33
|
const SAGA_TIMEOUT_MS = '240000'
|
|
36
34
|
|
|
37
35
|
/**
|
|
38
|
-
* Provision an
|
|
36
|
+
* Provision an instance through the admin kernel, then bookmark it and
|
|
39
37
|
* make it the active target. Extracted from `instance create` so `astrale
|
|
40
|
-
* setup` provisions through the exact same saga (auth assertion →
|
|
41
|
-
* bookmark → set-active)
|
|
38
|
+
* setup` provisions through the exact same saga (auth assertion → create →
|
|
39
|
+
* bookmark → set-active).
|
|
42
40
|
*
|
|
43
41
|
* Presentation is deliberately minimal here (a spinner + a one-line success);
|
|
44
42
|
* the caller renders anything richer — `setup` follows this with a hero panel.
|
|
@@ -48,15 +46,10 @@ export async function provisionInstance(
|
|
|
48
46
|
opts: ProvisionOpts,
|
|
49
47
|
): Promise<ProvisionResult> {
|
|
50
48
|
validateSlug(slug)
|
|
51
|
-
await
|
|
52
|
-
|
|
53
|
-
// identity pinned on the admin bookmark (that mismatch silently made a fresh
|
|
54
|
-
// user's instance unusable). `--as` still wins.
|
|
55
|
-
if (!opts.as) opts = { ...opts, as: (await readIdentities()).default }
|
|
49
|
+
const authentication = await instanceCreateAuthentication(opts)
|
|
50
|
+
opts = authentication.opts
|
|
56
51
|
|
|
57
52
|
const machine = isMachine(opts)
|
|
58
|
-
const interactive = !!process.stdin.isTTY && !(opts.ci || opts.noPrompt || process.env.CI)
|
|
59
|
-
|
|
60
53
|
let repointedFrom: string | undefined
|
|
61
54
|
let selectionError: unknown = null
|
|
62
55
|
// The global 30s default doesn't just fail the CLIENT: the disconnect kills
|
|
@@ -65,21 +58,24 @@ export async function provisionInstance(
|
|
|
65
58
|
// timeout; an explicit --timeout still wins.
|
|
66
59
|
const createOpts = { ...opts, timeout: opts.timeout ?? SAGA_TIMEOUT_MS }
|
|
67
60
|
|
|
68
|
-
const runProvision = (
|
|
61
|
+
const runProvision = () =>
|
|
69
62
|
withSpinner(
|
|
70
63
|
`Provisioning instance ${slug}`,
|
|
71
64
|
!machine,
|
|
72
65
|
async () => {
|
|
73
|
-
const created = await createOwnedInstance(createOpts, slug
|
|
66
|
+
const created = await createOwnedInstance(createOpts, slug)
|
|
74
67
|
try {
|
|
75
68
|
// Org id from the create response — authoritative for token scoping
|
|
76
69
|
// (the router's /auth/org is eventually consistent).
|
|
77
|
-
const bookmarked = await upsertManagedBookmark(
|
|
78
|
-
slug,
|
|
70
|
+
const bookmarked = await upsertManagedBookmark({
|
|
71
|
+
key: slug,
|
|
79
72
|
slug,
|
|
80
|
-
created.url,
|
|
81
|
-
created.organizationId,
|
|
82
|
-
|
|
73
|
+
url: created.url,
|
|
74
|
+
...(created.organizationId ? { organizationId: created.organizationId } : {}),
|
|
75
|
+
...(authentication.defaultIdentity
|
|
76
|
+
? { defaultIdentity: authentication.defaultIdentity }
|
|
77
|
+
: {}),
|
|
78
|
+
})
|
|
83
79
|
repointedFrom = bookmarked.repointedFrom
|
|
84
80
|
await setActive(slug)
|
|
85
81
|
} catch (e) {
|
|
@@ -93,23 +89,7 @@ export async function provisionInstance(
|
|
|
93
89
|
},
|
|
94
90
|
)
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
// hosts). We recover ONLY from its multi-host ambiguity: pop a picker and
|
|
98
|
-
// retry with the chosen host_id — the ambiguity error throws before any
|
|
99
|
-
// provisioning side effect, so the retry is clean. No host / permission /
|
|
100
|
-
// capacity / a non-interactive run all reject through to the caller's `fatal`,
|
|
101
|
-
// surfacing the server's own message plus the listed ids. An explicit
|
|
102
|
-
// `--host-id` skips all of this.
|
|
103
|
-
const created = await runProvision(opts.hostId).catch(async (e: unknown) => {
|
|
104
|
-
const hostIds = !opts.hostId && interactive ? parseEligibleHostIds(e) : null
|
|
105
|
-
if (!hostIds) throw e
|
|
106
|
-
const chosen = await promptSelect(
|
|
107
|
-
`${hostIds.length} hosts available — pick one to provision on`,
|
|
108
|
-
hostIds.map((hid) => ({ name: hid, value: hid })),
|
|
109
|
-
)
|
|
110
|
-
if (!chosen) throw e
|
|
111
|
-
return runProvision(chosen)
|
|
112
|
-
})
|
|
92
|
+
const created = await runProvision()
|
|
113
93
|
|
|
114
94
|
// Warnings go to stderr so machine-readable stdout stays clean.
|
|
115
95
|
const warn = (msg: string) => console.error(chalk.yellow('⚠'), msg)
|
|
@@ -124,31 +104,28 @@ export async function provisionInstance(
|
|
|
124
104
|
return { created, slug, repointedFrom, ...(selectionError ? { selectionError } : {}) }
|
|
125
105
|
}
|
|
126
106
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (!match) return null
|
|
139
|
-
const ids = match[1]!
|
|
140
|
-
.split(',')
|
|
141
|
-
.map((s) => s.trim())
|
|
142
|
-
.filter(Boolean)
|
|
143
|
-
return ids.length > 1 ? ids : null
|
|
107
|
+
async function instanceCreateAuthentication(opts: ProvisionOpts): Promise<{
|
|
108
|
+
readonly opts: ProvisionOpts
|
|
109
|
+
readonly defaultIdentity?: string
|
|
110
|
+
}> {
|
|
111
|
+
if (opts.creds) return { opts }
|
|
112
|
+
const store = await readIdentities()
|
|
113
|
+
const defaultIdentity = selectInstanceCreateIdentity(store, opts)
|
|
114
|
+
return {
|
|
115
|
+
opts: opts.as ? opts : { ...opts, as: defaultIdentity },
|
|
116
|
+
defaultIdentity,
|
|
117
|
+
}
|
|
144
118
|
}
|
|
145
119
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
120
|
+
export function selectInstanceCreateIdentity(
|
|
121
|
+
store: IdentityStore,
|
|
122
|
+
opts: Pick<ProvisionOpts, 'as'> = {},
|
|
123
|
+
): string {
|
|
124
|
+
assertInstanceCreateIdentity(store, opts)
|
|
125
|
+
return opts.as ?? store.default
|
|
149
126
|
}
|
|
150
127
|
|
|
151
|
-
export function
|
|
128
|
+
export function assertInstanceCreateIdentity(
|
|
152
129
|
store: IdentityStore,
|
|
153
130
|
opts: Pick<ProvisionOpts, 'as'> = {},
|
|
154
131
|
): void {
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { lstat, mkdir, readFile, readdir, rm, stat, unlink, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { dirname, join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
export type FileLockOptions = {
|
|
6
|
+
pollIntervalMs?: number
|
|
7
|
+
staleAfterMs?: number
|
|
8
|
+
timeoutMs?: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Claim = { directory: string; pid: number; ticket: number | null; choosing: boolean }
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Serialize a cross-process transition with a filesystem bakery lock.
|
|
15
|
+
*
|
|
16
|
+
* Every contender owns a UUID directory for its entire lifetime. Dead claims are
|
|
17
|
+
* removed only by that unrepeatable name, so recovery never deletes a pathname a
|
|
18
|
+
* new live owner could have replaced between an ownership check and an unlink.
|
|
19
|
+
*/
|
|
20
|
+
export async function withFileLock<T>(
|
|
21
|
+
lockPath: string,
|
|
22
|
+
fn: () => Promise<T>,
|
|
23
|
+
opts: FileLockOptions = {},
|
|
24
|
+
): Promise<T> {
|
|
25
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 100
|
|
26
|
+
const staleAfterMs = opts.staleAfterMs ?? 30_000
|
|
27
|
+
const timeoutMs = opts.timeoutMs ?? 60_000
|
|
28
|
+
const deadline = Date.now() + timeoutMs
|
|
29
|
+
const claimsRoot = `${lockPath}.claims`
|
|
30
|
+
const token = randomUUID()
|
|
31
|
+
const claimDirectory = join(claimsRoot, token)
|
|
32
|
+
|
|
33
|
+
await mkdir(dirname(lockPath), { recursive: true })
|
|
34
|
+
await mkdir(claimsRoot, { recursive: true })
|
|
35
|
+
await mkdir(claimDirectory)
|
|
36
|
+
await writeFile(join(claimDirectory, 'owner.json'), JSON.stringify({ pid: process.pid }), {
|
|
37
|
+
mode: 0o600,
|
|
38
|
+
})
|
|
39
|
+
await writeFile(join(claimDirectory, 'choosing'), '', { mode: 0o600 })
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const claims = await liveClaims(claimsRoot, staleAfterMs)
|
|
43
|
+
const ticket = Math.max(0, ...claims.flatMap((claim) => claim.ticket ?? [])) + 1
|
|
44
|
+
await writeFile(join(claimDirectory, 'ticket'), String(ticket), { mode: 0o600 })
|
|
45
|
+
await unlink(join(claimDirectory, 'choosing'))
|
|
46
|
+
|
|
47
|
+
for (;;) {
|
|
48
|
+
const contenders = await liveClaims(claimsRoot, staleAfterMs)
|
|
49
|
+
const blocked = contenders.some(
|
|
50
|
+
(claim) =>
|
|
51
|
+
claim.directory !== claimDirectory &&
|
|
52
|
+
(claim.choosing ||
|
|
53
|
+
claim.ticket === null ||
|
|
54
|
+
claim.ticket < ticket ||
|
|
55
|
+
(claim.ticket === ticket && claim.directory < claimDirectory)),
|
|
56
|
+
)
|
|
57
|
+
if (!blocked) return await fn()
|
|
58
|
+
if (Date.now() >= deadline) throw new Error('Timed out waiting for another skill update')
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs))
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
await rm(claimDirectory, { recursive: true, force: true })
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function liveClaims(root: string, staleAfterMs: number): Promise<Claim[]> {
|
|
67
|
+
const directories = (await readdir(root, { withFileTypes: true }).catch(() => []))
|
|
68
|
+
.filter((entry) => entry.isDirectory())
|
|
69
|
+
.map((entry) => join(root, entry.name))
|
|
70
|
+
const claims = await Promise.all(
|
|
71
|
+
directories.map(async (directory): Promise<Claim | null> => {
|
|
72
|
+
try {
|
|
73
|
+
const owner = JSON.parse(await readFile(join(directory, 'owner.json'), 'utf8')) as {
|
|
74
|
+
pid?: unknown
|
|
75
|
+
}
|
|
76
|
+
if (typeof owner.pid !== 'number') return await discardIfStale(directory, staleAfterMs)
|
|
77
|
+
if (!isPidAlive(owner.pid)) {
|
|
78
|
+
await rm(directory, { recursive: true, force: true })
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
const choosing = await lstat(join(directory, 'choosing'))
|
|
82
|
+
.then(() => true)
|
|
83
|
+
.catch(() => false)
|
|
84
|
+
const ticket = await readFile(join(directory, 'ticket'), 'utf8')
|
|
85
|
+
.then((value) => {
|
|
86
|
+
const parsed = Number(value)
|
|
87
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null
|
|
88
|
+
})
|
|
89
|
+
.catch(() => null)
|
|
90
|
+
return { directory, pid: owner.pid, ticket, choosing }
|
|
91
|
+
} catch {
|
|
92
|
+
return await discardIfStale(directory, staleAfterMs)
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
)
|
|
96
|
+
return claims.filter((claim): claim is Claim => claim !== null)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function discardIfStale(directory: string, staleAfterMs: number): Promise<null> {
|
|
100
|
+
try {
|
|
101
|
+
if (Date.now() - (await stat(directory)).mtimeMs > staleAfterMs) {
|
|
102
|
+
await rm(directory, { recursive: true, force: true })
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// A concurrently released unique claim is already gone.
|
|
106
|
+
}
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isPidAlive(pid: number): boolean {
|
|
111
|
+
try {
|
|
112
|
+
process.kill(pid, 0)
|
|
113
|
+
return true
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return (error as { code?: string }).code === 'EPERM'
|
|
116
|
+
}
|
|
117
|
+
}
|