@astrale-os/cli 1.0.0-beta.2 → 1.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/astrale.js +51105 -58057
- package/dist/public/connect-core.js +2085 -30702
- package/dist/public/keys/index.js +884 -29476
- package/dist/public/paths/index.js +13101 -41452
- 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 +7 -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/package.json +15 -20
- 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 +46 -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 +7 -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 +3 -14
- 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 +62 -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 +49 -0
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +71 -59
- package/src/commands/domain/install.ts +54 -35
- 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 +14 -15
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +5 -4
- 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 +48 -35
- package/src/commands/logs.ts +123 -33
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +35 -32
- 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 +13 -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 +31 -11
- package/src/commands/view.ts +82 -37
- package/src/connection/.spec/api.d.ts +7 -8
- package/src/connection/.spec/architecture.md +5 -4
- 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__/credential.test.ts +20 -1
- package/src/connection/__tests__/errors.test.ts +27 -20
- package/src/connection/__tests__/exchange.test.ts +30 -1
- 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 +3 -3
- package/src/connection/__tests__/target.test.ts +1 -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 +47 -23
- 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 +19 -8
- 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__/registration.test.ts +1 -1
- package/src/identity/registration.ts +3 -3
- 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__/command-dx.test.ts +1 -1
- 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__/studio-server-deps.test.ts +1 -1
- package/src/lib/__tests__/update.test.ts +264 -11
- package/src/lib/__tests__/view-assets.test.ts +14 -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/command-dx.ts +9 -9
- 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/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/telemetry/__tests__/recorder.test.ts +1 -0
- package/src/telemetry/recorder.ts +9 -1
- package/src/ui/.spec/api.d.ts +14 -0
- package/src/ui/.spec/architecture.md +10 -0
- package/src/ui/.spec/laws.ts +36 -0
- package/src/ui/.spec/layout.ts +16 -0
- package/src/ui/__tests__/ui.test.ts +981 -0
- package/src/ui/index.ts +13 -0
- package/src/ui/lock.ts +87 -0
- package/src/ui/model.ts +83 -0
- package/src/ui/operations.ts +710 -0
- package/src/ui/project.ts +219 -0
- package/src/ui/release.ts +321 -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-7YHdeCxp.js +81 -0
- package/studio/client/dist/assets/index-C0FAnwNw.css +2 -0
- package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +1 -0
- package/studio/client/dist/assets/schema-studio-CABpTfvH.js +8 -0
- package/studio/client/dist/index.html +3 -2
- package/studio/package.json +7 -13
- 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 +32 -36
- package/studio/server/agent/prompts/anchors.ts +20 -49
- package/studio/server/agent/prompts/system.test.ts +3 -2
- package/studio/server/agent/prompts/system.ts +3 -3
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/api/canvas.ts +0 -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 +23 -12
- package/studio/server/cache.ts +12 -15
- package/studio/server/domain.test.ts +50 -68
- package/studio/server/domain.ts +82 -63
- package/studio/server/handoff/copy.ts +1 -1
- 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 +9 -14
- package/studio/server/introspect/anatomy-extras.test.ts +26 -42
- 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 +94 -389
- package/studio/server/introspect/canonical-schema.ts +227 -457
- package/studio/server/introspect/core-extractor.ts +18 -127
- package/studio/server/introspect/core.ts +1 -2
- package/studio/server/introspect/diff.test.ts +11 -7
- package/studio/server/introspect/diff.ts +18 -55
- package/studio/server/introspect/extractor.ts +22 -117
- package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
- package/studio/server/introspect/overlay.test.ts +26 -54
- package/studio/server/introspect/overlay.ts +10 -37
- package/studio/server/introspect/revision.test.ts +1 -1
- package/studio/server/introspect/revision.ts +1 -1
- package/studio/server/introspect/runtime.test.ts +65 -183
- package/studio/server/introspect/runtime.ts +8 -25
- package/studio/server/introspect/schema-ir-json.test.ts +70 -0
- package/studio/server/introspect/schema-ir-json.ts +38 -68
- 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/state/baseline.test.ts +14 -9
- package/studio/server/state/baseline.ts +3 -5
- package/studio/server/state/visibility.ts +1 -5
- package/studio/server/views/model.ts +3 -3
- package/studio/server/views/target.test.ts +17 -74
- 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-watch.ts +2 -2
- package/studio/shared/contracts/schema.ts +38 -107
- package/studio/shared/contracts/surface.test.ts +13 -15
- package/studio/shared/contracts/workspace.ts +2 -11
- package/studio/shared/schema/identity.test.ts +20 -44
- package/studio/shared/schema/identity.ts +9 -23
- package/viewer/dist/main.js +30 -51
- 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/src/commands/query.ts
CHANGED
|
@@ -6,8 +6,9 @@ import type { KernelCommandOpts, SelfExpansionMeta } from '../connection'
|
|
|
6
6
|
import type { CommandDefinition } from '../program/index'
|
|
7
7
|
|
|
8
8
|
import { expandSelfInPath, runKernelCommand, withSelfHint } from '../connection'
|
|
9
|
+
import { AstraleError } from '../errors'
|
|
9
10
|
import { prepareQuery, type QueryCommandInput } from '../graph/index'
|
|
10
|
-
import {
|
|
11
|
+
import { failInput } from '../lib/log'
|
|
11
12
|
import { isMachine, output } from '../lib/output'
|
|
12
13
|
|
|
13
14
|
type QueryOpts = KernelCommandOpts & {
|
|
@@ -22,48 +23,49 @@ type QueryOpts = KernelCommandOpts & {
|
|
|
22
23
|
|
|
23
24
|
export async function queryCommand(sources: string[], opts: QueryOpts): Promise<void> {
|
|
24
25
|
let input: QueryCommandInput
|
|
25
|
-
let meta: SelfExpansionMeta | undefined
|
|
26
26
|
try {
|
|
27
27
|
const ast = await readAst(opts)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
cursor: opts.cursor,
|
|
37
|
-
}
|
|
38
|
-
meta = expanded.find((entry) => entry.meta !== undefined)?.meta
|
|
39
|
-
} else {
|
|
40
|
-
input = {
|
|
41
|
-
sources,
|
|
42
|
-
ast,
|
|
43
|
-
definition: opts.definition,
|
|
44
|
-
edge: opts.edge,
|
|
45
|
-
direction: opts.direction,
|
|
46
|
-
limit: opts.limit,
|
|
47
|
-
cursor: opts.cursor,
|
|
48
|
-
}
|
|
28
|
+
input = {
|
|
29
|
+
sources,
|
|
30
|
+
...(ast === undefined ? {} : { ast }),
|
|
31
|
+
definition: opts.definition,
|
|
32
|
+
edge: opts.edge,
|
|
33
|
+
direction: opts.direction,
|
|
34
|
+
limit: opts.limit,
|
|
35
|
+
cursor: opts.cursor,
|
|
49
36
|
}
|
|
50
37
|
} catch (error) {
|
|
51
|
-
|
|
38
|
+
failInput(error, opts)
|
|
52
39
|
}
|
|
53
40
|
|
|
54
41
|
let prepared
|
|
55
42
|
try {
|
|
56
43
|
prepared = prepareQuery(input)
|
|
57
44
|
} catch (error) {
|
|
58
|
-
|
|
45
|
+
failInput(error, opts)
|
|
59
46
|
}
|
|
60
47
|
|
|
61
48
|
await runKernelCommand({
|
|
62
49
|
opts,
|
|
63
50
|
label: 'Query',
|
|
64
|
-
fn:
|
|
51
|
+
fn: async (context) => {
|
|
52
|
+
const expanded = await Promise.all(sources.map((source) => expandSelfInPath(source, context)))
|
|
53
|
+
const meta: SelfExpansionMeta | undefined = expanded.find(
|
|
54
|
+
(entry) => entry.meta !== undefined,
|
|
55
|
+
)?.meta
|
|
56
|
+
const resolved =
|
|
57
|
+
meta === undefined
|
|
58
|
+
? prepared
|
|
59
|
+
: prepareQuery({ ...input, sources: expanded.map(({ path }) => path) })
|
|
60
|
+
return withSelfHint(() => context.graph.query(resolved.ast, { page: resolved.page }), meta)
|
|
61
|
+
},
|
|
65
62
|
format: (response, format) => {
|
|
66
|
-
output(
|
|
63
|
+
output(
|
|
64
|
+
response.page.next === undefined
|
|
65
|
+
? response.result
|
|
66
|
+
: { ...response.result, page: { next: response.page.next } },
|
|
67
|
+
format,
|
|
68
|
+
)
|
|
67
69
|
if (response.page.next && !isMachine(format)) {
|
|
68
70
|
process.stderr.write(` cursor: ${response.page.next}\n`)
|
|
69
71
|
}
|
|
@@ -81,9 +83,9 @@ async function readAst(opts: QueryOpts): Promise<unknown | undefined> {
|
|
|
81
83
|
try {
|
|
82
84
|
raw = await readFile(opts.file, 'utf8')
|
|
83
85
|
} catch (error) {
|
|
84
|
-
throw new
|
|
85
|
-
|
|
86
|
-
)
|
|
86
|
+
throw new AstraleError('FILE_READ_FAILED', `Cannot read --file ${opts.file}.`, undefined, {
|
|
87
|
+
cause: error,
|
|
88
|
+
})
|
|
87
89
|
}
|
|
88
90
|
return parseJson(raw, opts.file)
|
|
89
91
|
}
|
|
@@ -105,7 +107,8 @@ Behavior:
|
|
|
105
107
|
one exact Edge-Class expansion; --direction defaults to outgoing. --ast and
|
|
106
108
|
--file accept a complete canonical astrale.graph.query/v6 document, including
|
|
107
109
|
Property ordering and Node or Edge reference/value projections. --cursor resumes
|
|
108
|
-
one caller-bound query scope.
|
|
110
|
+
one caller-bound query scope. Machine output adds page.next only when another
|
|
111
|
+
page exists; pass that opaque value back through --cursor.
|
|
109
112
|
|
|
110
113
|
Legacy depth/children selector JSON and raw Cypher are not portable Kernel
|
|
111
114
|
V2 query contracts and are not accepted.
|
|
@@ -122,7 +125,7 @@ Examples:
|
|
|
122
125
|
{ flags: '-f, --file <path>', description: 'Read a canonical Query V6 document from a file' },
|
|
123
126
|
{
|
|
124
127
|
flags: '--definition <path>',
|
|
125
|
-
description: 'Select Nodes
|
|
128
|
+
description: 'Select Nodes satisfying one exact Class',
|
|
126
129
|
},
|
|
127
130
|
{ flags: '--edge <class>', description: 'Expand one exact Edge Class' },
|
|
128
131
|
{
|
package/src/commands/token.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { issuer } from '@astrale-os/sdk/auth'
|
|
1
|
+
import { issuer, type AuthApi, type IssuerId, type MintedCredential } from '@astrale-os/sdk/auth'
|
|
2
2
|
|
|
3
3
|
import type { KernelCommandOpts } from '../connection'
|
|
4
4
|
import type { CommandDefinition } from '../program/index'
|
|
@@ -6,12 +6,12 @@ import type { CommandDefinition } from '../program/index'
|
|
|
6
6
|
import { runKernelCommand } from '../connection'
|
|
7
7
|
import { AstraleError } from '../errors'
|
|
8
8
|
import { decodeJwtExpiration } from '../lib/local-status'
|
|
9
|
-
import {
|
|
9
|
+
import { failInput, log } from '../lib/log'
|
|
10
10
|
import { output } from '../lib/output'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* `astrale token` — mint a fresh
|
|
14
|
-
*
|
|
13
|
+
* `astrale token` — mint a fresh audience-bound token for the active instance
|
|
14
|
+
* and selected identity through the bound AuthApi.
|
|
15
15
|
*/
|
|
16
16
|
export type TokenOpts = KernelCommandOpts & {
|
|
17
17
|
audience?: string
|
|
@@ -21,23 +21,25 @@ export type TokenOpts = KernelCommandOpts & {
|
|
|
21
21
|
for?: string
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
const DEFAULT_TOKEN_TTL_SECONDS = 4 * 60
|
|
25
|
+
|
|
24
26
|
export async function tokenCommand(opts: TokenOpts): Promise<void> {
|
|
25
27
|
const commandOpts: TokenOpts = opts.for && !opts.as ? { ...opts, as: opts.for } : opts
|
|
26
|
-
|
|
28
|
+
let ttl: number
|
|
29
|
+
try {
|
|
30
|
+
ttl = parseTtl(commandOpts.ttl)
|
|
31
|
+
} catch (error) {
|
|
32
|
+
failInput(error, opts)
|
|
33
|
+
}
|
|
27
34
|
await runKernelCommand<string>({
|
|
28
35
|
opts: commandOpts,
|
|
29
|
-
label: 'Minting
|
|
36
|
+
label: 'Minting token',
|
|
30
37
|
fn: async (ctx) => {
|
|
31
38
|
const audience =
|
|
32
39
|
commandOpts.audience === undefined
|
|
33
40
|
? ctx.target.kernelIssuer
|
|
34
41
|
: issuer.accept(commandOpts.audience)
|
|
35
|
-
|
|
36
|
-
return ctx.auth.delegate(self.id, {
|
|
37
|
-
audience,
|
|
38
|
-
ttlSeconds: ttl,
|
|
39
|
-
attenuation: { kind: 'identity', self: true },
|
|
40
|
-
})
|
|
42
|
+
return issueToken(ctx.auth, ctx.target.kernelIssuer, audience, ttl)
|
|
41
43
|
},
|
|
42
44
|
format: (token, fmtOpts) => {
|
|
43
45
|
if (fmtOpts.json || fmtOpts.format !== undefined) {
|
|
@@ -45,15 +47,31 @@ export async function tokenCommand(opts: TokenOpts): Promise<void> {
|
|
|
45
47
|
return
|
|
46
48
|
}
|
|
47
49
|
if (!fmtOpts.raw && (process.stdout.isTTY ?? false)) {
|
|
48
|
-
log.dim(' (
|
|
50
|
+
log.dim(' (audience-bound token — ES256, self-identity)')
|
|
49
51
|
}
|
|
50
52
|
process.stdout.write(`${token}\n`)
|
|
51
53
|
},
|
|
52
54
|
})
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
/** Issue either a top-level Kernel credential or an external-audience delegation. */
|
|
58
|
+
export async function issueToken(
|
|
59
|
+
auth: Pick<AuthApi, 'delegate' | 'mint' | 'whoami'>,
|
|
60
|
+
kernel: IssuerId,
|
|
61
|
+
audience: IssuerId,
|
|
62
|
+
ttlSeconds: number,
|
|
63
|
+
): Promise<MintedCredential> {
|
|
64
|
+
if (audience === kernel) return auth.mint({ ttlSeconds })
|
|
65
|
+
const self = await auth.whoami()
|
|
66
|
+
return auth.delegate(self.id, {
|
|
67
|
+
audience,
|
|
68
|
+
ttlSeconds,
|
|
69
|
+
attenuation: { kind: 'identity', self: true },
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
55
73
|
export function parseTtl(raw: string | undefined): number {
|
|
56
|
-
if (raw === undefined) return
|
|
74
|
+
if (raw === undefined) return DEFAULT_TOKEN_TTL_SECONDS
|
|
57
75
|
if (!/^\d+$/.test(raw)) {
|
|
58
76
|
throw new AstraleError(
|
|
59
77
|
'INVALID_FLAG',
|
|
@@ -72,16 +90,17 @@ export function parseTtl(raw: string | undefined): number {
|
|
|
72
90
|
|
|
73
91
|
export default {
|
|
74
92
|
name: 'token',
|
|
75
|
-
description: 'Mint a fresh
|
|
93
|
+
description: 'Mint a fresh audience-bound credential for the active instance + identity',
|
|
76
94
|
afterHelpText: `
|
|
77
95
|
Behavior:
|
|
78
|
-
|
|
79
|
-
The default audience
|
|
80
|
-
|
|
96
|
+
Mints a token for the selected authenticated identity for 240 seconds by default.
|
|
97
|
+
The default Kernel audience produces a top-level Grant credential reusable with
|
|
98
|
+
--creds. A different --audience produces a delegated service envelope. --for is
|
|
99
|
+
an alias of --as.
|
|
81
100
|
|
|
82
|
-
|
|
83
|
-
be
|
|
84
|
-
|
|
101
|
+
Every receiver must admit the exact token audience. A service-audience token can
|
|
102
|
+
be sent as a Bearer token only to that service's authenticated endpoint. The
|
|
103
|
+
requested lifetime cannot exceed the selected source credential's remaining life.
|
|
85
104
|
|
|
86
105
|
Examples:
|
|
87
106
|
$ export TOKEN=$(astrale token --audience shell.astrale.ai --raw)
|
|
@@ -93,14 +112,10 @@ Examples:
|
|
|
93
112
|
flags: '--audience <aud>',
|
|
94
113
|
description: 'Token audience (default: target Kernel issuer)',
|
|
95
114
|
},
|
|
96
|
-
{ flags: '--ttl <sec>', description: 'TTL in seconds (default:
|
|
115
|
+
{ flags: '--ttl <sec>', description: 'TTL in seconds (default: 240)' },
|
|
97
116
|
{ flags: '--for <identity>', description: 'Mint the token for this identity (alias of --as)' },
|
|
98
117
|
],
|
|
99
118
|
action: async (opts) => {
|
|
100
|
-
|
|
101
|
-
await tokenCommand(opts as Parameters<typeof tokenCommand>[0])
|
|
102
|
-
} catch (error) {
|
|
103
|
-
failClosed(error, opts as TokenOpts)
|
|
104
|
-
}
|
|
119
|
+
await tokenCommand(opts as Parameters<typeof tokenCommand>[0])
|
|
105
120
|
},
|
|
106
121
|
} satisfies CommandDefinition
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import addCommand from '../add'
|
|
4
|
+
import listCommand from '../list'
|
|
5
|
+
|
|
6
|
+
class ExitError extends Error {}
|
|
7
|
+
|
|
8
|
+
const commit = 'a'.repeat(40)
|
|
9
|
+
const item = {
|
|
10
|
+
name: 'pattern-chart-line-basic',
|
|
11
|
+
type: 'registry:block',
|
|
12
|
+
title: 'Line chart',
|
|
13
|
+
description: 'A controlled chart.',
|
|
14
|
+
dependencies: ['@astrale-os/ui@^0.3.0-beta.0'],
|
|
15
|
+
files: [
|
|
16
|
+
{
|
|
17
|
+
path: 'registry/patterns/chart/line-basic.tsx',
|
|
18
|
+
type: 'registry:component',
|
|
19
|
+
target: 'components/astrale/pattern/chart/line-basic.tsx',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
meta: { canonicalAddress: 'pattern/chart/line/basic' },
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let stdout = ''
|
|
26
|
+
let stderr = ''
|
|
27
|
+
let originalArgv: string[]
|
|
28
|
+
let originalExit: typeof process.exit
|
|
29
|
+
let originalFetch: typeof globalThis.fetch
|
|
30
|
+
let originalStdout: typeof process.stdout.write
|
|
31
|
+
let originalStderr: typeof process.stderr.write
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
stdout = ''
|
|
35
|
+
stderr = ''
|
|
36
|
+
originalArgv = process.argv
|
|
37
|
+
originalExit = process.exit
|
|
38
|
+
originalFetch = globalThis.fetch
|
|
39
|
+
originalStdout = process.stdout.write
|
|
40
|
+
originalStderr = process.stderr.write
|
|
41
|
+
process.stdout.write = ((chunk: string | Uint8Array) => {
|
|
42
|
+
stdout += String(chunk)
|
|
43
|
+
return true
|
|
44
|
+
}) as typeof process.stdout.write
|
|
45
|
+
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
46
|
+
stderr += String(chunk)
|
|
47
|
+
return true
|
|
48
|
+
}) as typeof process.stderr.write
|
|
49
|
+
process.exit = (() => {
|
|
50
|
+
throw new ExitError()
|
|
51
|
+
}) as typeof process.exit
|
|
52
|
+
globalThis.fetch = mockFetch()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
process.argv = originalArgv
|
|
57
|
+
process.exit = originalExit
|
|
58
|
+
globalThis.fetch = originalFetch
|
|
59
|
+
process.stdout.write = originalStdout
|
|
60
|
+
process.stderr.write = originalStderr
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('UI command machine contracts', () => {
|
|
64
|
+
test('list emits exactly one parseable JSON value', async () => {
|
|
65
|
+
const action = listCommand.action as (
|
|
66
|
+
query: string | undefined,
|
|
67
|
+
options: { json?: boolean; limit?: string },
|
|
68
|
+
) => Promise<void>
|
|
69
|
+
await action('line-basic', { json: true, limit: '100' })
|
|
70
|
+
|
|
71
|
+
expect(stderr).toBe('')
|
|
72
|
+
expect(JSON.parse(stdout)).toEqual([item])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('list forwards an explicit release without consulting the beta channel', async () => {
|
|
76
|
+
const seen: string[] = []
|
|
77
|
+
globalThis.fetch = mockFetch(seen)
|
|
78
|
+
const action = listCommand.action as (
|
|
79
|
+
query: string | undefined,
|
|
80
|
+
options: { json?: boolean; limit?: string; version?: string },
|
|
81
|
+
) => Promise<void>
|
|
82
|
+
|
|
83
|
+
await action('line-basic', { json: true, limit: '100', version: '0.3.0-beta.1' })
|
|
84
|
+
|
|
85
|
+
expect(JSON.parse(stdout)).toEqual([item])
|
|
86
|
+
expect(seen.some((url) => url.endsWith('/@astrale-os/ui/beta'))).toBe(false)
|
|
87
|
+
expect(seen.some((url) => url.includes('/git/ref/tags/v0.3.0-beta.1'))).toBe(true)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('add rejects missing items without prompting in machine mode', async () => {
|
|
91
|
+
process.argv = ['node', 'astrale', '--no-prompt', 'ui', 'add']
|
|
92
|
+
const add = addCommand.action as (items: string[], options: { json?: boolean }) => Promise<void>
|
|
93
|
+
await expect(add([], { json: true })).rejects.toBeInstanceOf(ExitError)
|
|
94
|
+
expect(JSON.parse(stderr)).toMatchObject({ error: 'UI_ITEM_NOT_FOUND' })
|
|
95
|
+
expect(stdout).toBe('')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('registry failures retain a stable code without leaking transport secrets', async () => {
|
|
99
|
+
globalThis.fetch = (async () => {
|
|
100
|
+
throw new Error('Authorization: Bearer npm_super_secret_value_that_must_not_escape')
|
|
101
|
+
}) as unknown as typeof fetch
|
|
102
|
+
const action = listCommand.action as (
|
|
103
|
+
query: string | undefined,
|
|
104
|
+
options: { json?: boolean; limit?: string },
|
|
105
|
+
) => Promise<void>
|
|
106
|
+
await expect(action('line-basic', { json: true, limit: '100' })).rejects.toBeInstanceOf(
|
|
107
|
+
ExitError,
|
|
108
|
+
)
|
|
109
|
+
expect(JSON.parse(stderr)).toEqual({
|
|
110
|
+
error: 'UI_REGISTRY_UNAVAILABLE',
|
|
111
|
+
message: 'Unable to reach npm UI release.',
|
|
112
|
+
})
|
|
113
|
+
expect(stderr).not.toContain('super_secret')
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
function mockFetch(seen: string[] = []): typeof fetch {
|
|
118
|
+
return (async (input: string | URL | Request) => {
|
|
119
|
+
const url = String(input)
|
|
120
|
+
seen.push(url)
|
|
121
|
+
if (url.endsWith('/@astrale-os/ui/beta')) return Response.json({ version: '0.3.0-beta.1' })
|
|
122
|
+
if (url.includes('/git/ref/tags/')) {
|
|
123
|
+
return Response.json({ object: { type: 'commit', sha: commit, url: '' } })
|
|
124
|
+
}
|
|
125
|
+
if (url.endsWith('/tooling/compatibility.json')) {
|
|
126
|
+
return Response.json({
|
|
127
|
+
version: 1,
|
|
128
|
+
shadcn: '4.18.0',
|
|
129
|
+
base: 'base',
|
|
130
|
+
style: 'nova',
|
|
131
|
+
baseUi: '1.7.0',
|
|
132
|
+
react: '^18.3.1 || ^19.0.0',
|
|
133
|
+
tailwind: '^4.3.3',
|
|
134
|
+
presets: ['astrale', 'compact', 'expressive'],
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
if (url.endsWith('/registry/patterns/chart/registry.json')) {
|
|
138
|
+
return Response.json({
|
|
139
|
+
items: [
|
|
140
|
+
{
|
|
141
|
+
...item,
|
|
142
|
+
files: item.files.map((file) => ({ ...file, path: 'line-basic.tsx' })),
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
if (url.endsWith('/registry.json')) {
|
|
148
|
+
return Response.json({ include: ['registry/patterns/chart/registry.json'] })
|
|
149
|
+
}
|
|
150
|
+
return new Response('not found', { status: 404 })
|
|
151
|
+
}) as typeof fetch
|
|
152
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../program'
|
|
2
|
+
|
|
3
|
+
import { promptMultiSelect } from '../../lib/prompt'
|
|
4
|
+
import { addUi, listLockedUi, UiError } from '../../ui'
|
|
5
|
+
import { UI_JSON_OPTION, UI_PROJECT_OPTION, runUiCommand, type UiCommandOptions } from './shared'
|
|
6
|
+
|
|
7
|
+
type Options = UiCommandOptions & { dryRun?: boolean; overwrite?: boolean; yes?: boolean }
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
name: 'add',
|
|
11
|
+
description: 'Install consumer-owned Astrale pattern or block source',
|
|
12
|
+
arguments: [
|
|
13
|
+
{
|
|
14
|
+
name: 'items',
|
|
15
|
+
description: 'Canonical pattern or block addresses',
|
|
16
|
+
required: false,
|
|
17
|
+
variadic: true,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
options: [
|
|
21
|
+
UI_PROJECT_OPTION,
|
|
22
|
+
{ flags: '--dry-run', description: 'Show exact files and dependencies without writing' },
|
|
23
|
+
{ flags: '--overwrite', description: 'Allow replacing locally changed installed source' },
|
|
24
|
+
{ flags: '--yes', description: 'Confirm the planned operation non-interactively' },
|
|
25
|
+
UI_JSON_OPTION,
|
|
26
|
+
],
|
|
27
|
+
afterHelpText:
|
|
28
|
+
'\nInstalled source belongs to the application. Ordinary add never overwrites local edits.\nRun astrale ui doctor before intentionally replacing local source with --overwrite --yes.\n',
|
|
29
|
+
action: async (items: string[], options: Options) =>
|
|
30
|
+
runUiCommand(options, async () => {
|
|
31
|
+
let selected = items
|
|
32
|
+
if (selected.length === 0) {
|
|
33
|
+
if (process.argv.includes('--ci') || process.argv.includes('--no-prompt')) {
|
|
34
|
+
throw new UiError('UI_ITEM_NOT_FOUND', 'No UI item was provided in non-interactive mode.')
|
|
35
|
+
}
|
|
36
|
+
const available = await listLockedUi(options.project)
|
|
37
|
+
selected =
|
|
38
|
+
(await promptMultiSelect(
|
|
39
|
+
'Choose Astrale UI source to install',
|
|
40
|
+
available.map((item) => ({
|
|
41
|
+
name: item.title ?? item.meta.canonicalAddress,
|
|
42
|
+
value: item.meta.canonicalAddress,
|
|
43
|
+
description: item.description,
|
|
44
|
+
})),
|
|
45
|
+
)) ?? []
|
|
46
|
+
if (selected.length === 0)
|
|
47
|
+
throw new UiError('UI_ITEM_NOT_FOUND', 'No UI item was selected.')
|
|
48
|
+
}
|
|
49
|
+
return addUi(selected, options)
|
|
50
|
+
}),
|
|
51
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../program'
|
|
2
|
+
|
|
3
|
+
import { doctorUi } from '../../ui'
|
|
4
|
+
import { UI_JSON_OPTION, runUiCommand, type UiCommandOptions } from './shared'
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
name: 'doctor',
|
|
8
|
+
description: 'Verify Astrale UI package, CSS, config, lock, and installed source integrity',
|
|
9
|
+
arguments: [{ name: 'path', description: 'Application directory', required: false }],
|
|
10
|
+
options: [UI_JSON_OPTION],
|
|
11
|
+
action: async (path: string | undefined, options: UiCommandOptions) =>
|
|
12
|
+
runUiCommand(options, () => doctorUi(path)),
|
|
13
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../program'
|
|
2
|
+
|
|
3
|
+
import { initUi, type UiPreset } from '../../ui'
|
|
4
|
+
import { UI_JSON_OPTION, runUiCommand, type UiCommandOptions } from './shared'
|
|
5
|
+
|
|
6
|
+
type Options = UiCommandOptions & {
|
|
7
|
+
preset?: UiPreset
|
|
8
|
+
version?: string
|
|
9
|
+
dryRun?: boolean
|
|
10
|
+
force?: boolean
|
|
11
|
+
install?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
name: 'init',
|
|
16
|
+
description: 'Initialize Astrale UI in an existing React and Tailwind v4 application',
|
|
17
|
+
arguments: [{ name: 'path', description: 'Application directory', required: false }],
|
|
18
|
+
options: [
|
|
19
|
+
{
|
|
20
|
+
flags: '--preset <name>',
|
|
21
|
+
description: 'Visual preset',
|
|
22
|
+
choices: ['astrale', 'compact', 'expressive'],
|
|
23
|
+
default: 'astrale',
|
|
24
|
+
},
|
|
25
|
+
{ flags: '--version <version>', description: 'Exact @astrale-os/ui version or release tag' },
|
|
26
|
+
{ flags: '--dry-run', description: 'Print the complete plan without writing or installing' },
|
|
27
|
+
{ flags: '--force', description: 'Replace an existing Astrale UI lock intentionally' },
|
|
28
|
+
{
|
|
29
|
+
flags: '--no-install',
|
|
30
|
+
description: 'Write configuration without running the package manager',
|
|
31
|
+
},
|
|
32
|
+
UI_JSON_OPTION,
|
|
33
|
+
],
|
|
34
|
+
afterHelpText:
|
|
35
|
+
'\nExamples:\n $ astrale ui init --preset astrale\n $ astrale ui init ./app --dry-run --json\n',
|
|
36
|
+
action: async (path: string | undefined, options: Options) =>
|
|
37
|
+
runUiCommand(options, () => initUi({ path, ...options })),
|
|
38
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../program'
|
|
2
|
+
|
|
3
|
+
import { listUi } from '../../ui'
|
|
4
|
+
import { UI_JSON_OPTION, runUiCommand, type UiCommandOptions } from './shared'
|
|
5
|
+
|
|
6
|
+
type Options = UiCommandOptions & { type?: string; limit?: string; version?: string }
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: 'list',
|
|
10
|
+
description: 'List or search Astrale patterns and blocks',
|
|
11
|
+
arguments: [{ name: 'query', description: 'Optional search text', required: false }],
|
|
12
|
+
options: [
|
|
13
|
+
{
|
|
14
|
+
flags: '--type <type>',
|
|
15
|
+
description: 'Restrict to pattern or block',
|
|
16
|
+
choices: ['pattern', 'block'],
|
|
17
|
+
},
|
|
18
|
+
{ flags: '--limit <n>', description: 'Maximum results', default: '100' },
|
|
19
|
+
{ flags: '--version <version>', description: 'Exact release to inspect' },
|
|
20
|
+
UI_JSON_OPTION,
|
|
21
|
+
],
|
|
22
|
+
action: async (query: string | undefined, options: Options) =>
|
|
23
|
+
runUiCommand(options, () =>
|
|
24
|
+
listUi(query, { type: options.type, limit: Number(options.limit), version: options.version }),
|
|
25
|
+
),
|
|
26
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../program'
|
|
2
|
+
|
|
3
|
+
import { applyPreset, type UiPreset } from '../../ui'
|
|
4
|
+
import { UI_JSON_OPTION, UI_PROJECT_OPTION, runUiCommand, type UiCommandOptions } from './shared'
|
|
5
|
+
|
|
6
|
+
type Options = UiCommandOptions & { dryRun?: boolean }
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: 'apply',
|
|
10
|
+
description: 'Change the preset CSS import without rewriting component source',
|
|
11
|
+
arguments: [{ name: 'name', description: 'Preset name' }],
|
|
12
|
+
options: [
|
|
13
|
+
UI_PROJECT_OPTION,
|
|
14
|
+
{ flags: '--dry-run', description: 'Show the CSS and lock change without writing' },
|
|
15
|
+
UI_JSON_OPTION,
|
|
16
|
+
],
|
|
17
|
+
action: async (name: UiPreset, options: Options) =>
|
|
18
|
+
runUiCommand(options, () => applyPreset(name, options)),
|
|
19
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../program'
|
|
2
|
+
|
|
3
|
+
import { UI_PRESETS } from '../../ui'
|
|
4
|
+
import { UI_JSON_OPTION, runUiCommand, type UiCommandOptions } from './shared'
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
name: 'list',
|
|
8
|
+
description: 'List qualified Astrale UI visual presets',
|
|
9
|
+
options: [UI_JSON_OPTION],
|
|
10
|
+
action: async (options: UiCommandOptions) =>
|
|
11
|
+
runUiCommand(options, async () => UI_PRESETS.map((name) => ({ name }))),
|
|
12
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { fatal } from '../../lib/log'
|
|
2
|
+
import { output } from '../../lib/output'
|
|
3
|
+
|
|
4
|
+
export type UiCommandOptions = { json?: boolean; project?: string }
|
|
5
|
+
|
|
6
|
+
export async function runUiCommand(
|
|
7
|
+
options: UiCommandOptions,
|
|
8
|
+
operation: () => Promise<unknown>,
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
try {
|
|
11
|
+
output(await operation(), { json: options.json })
|
|
12
|
+
} catch (error) {
|
|
13
|
+
fatal(error, { json: options.json })
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const UI_PROJECT_OPTION = {
|
|
18
|
+
flags: '--project <path>',
|
|
19
|
+
description: 'Existing application root (defaults to the current project)',
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
export const UI_JSON_OPTION = {
|
|
23
|
+
flags: '--json',
|
|
24
|
+
description: 'Emit one machine-readable JSON value',
|
|
25
|
+
} as const
|
package/src/commands/update.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type SdkOutdated,
|
|
13
13
|
} from '../lib/sdk-deps'
|
|
14
14
|
import { ASTRALE_CLI_SKILL, detectSkill, installSkills, SKILL_INSTALL_HINT } from '../lib/skills'
|
|
15
|
-
import { DEFAULT_UPDATE_CHANNEL, updateAstrale } from '../lib/update'
|
|
15
|
+
import { DEFAULT_UPDATE_CHANNEL, packageManagedUpdateError, updateAstrale } from '../lib/update'
|
|
16
16
|
|
|
17
17
|
type UpdateOpts = RawOutputOpts & {
|
|
18
18
|
check?: boolean
|
|
@@ -104,14 +104,22 @@ async function refreshSdkDeps(check: boolean, assumeYes = false): Promise<boolea
|
|
|
104
104
|
/**
|
|
105
105
|
* Read-only staleness report for tooling — `astrale update --check --json`.
|
|
106
106
|
* domain-studio polls this on load to drive its "update available" badge. It is
|
|
107
|
-
* unified and NON-THROWING:
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* update`, so a stale CLI or
|
|
107
|
+
* unified and NON-THROWING: an explicit package-managed result uses npm release
|
|
108
|
+
* identity, while script-install failures remain script failures in `error`
|
|
109
|
+
* instead of being recategorized. The SDK axis is already best-effort. Skills are
|
|
110
|
+
* intentionally absent — they ride along with `astrale update`, so a stale CLI or
|
|
111
|
+
* SDK is the only signal worth surfacing.
|
|
111
112
|
*/
|
|
112
113
|
export type StaleReport = {
|
|
113
114
|
stale: boolean
|
|
114
|
-
cli: {
|
|
115
|
+
cli: {
|
|
116
|
+
stale: boolean
|
|
117
|
+
managed: boolean
|
|
118
|
+
current?: string
|
|
119
|
+
latest?: string
|
|
120
|
+
channel?: string
|
|
121
|
+
error?: string
|
|
122
|
+
}
|
|
115
123
|
sdk: { stale: boolean; inProject: boolean; outdated: SdkOutdated[] }
|
|
116
124
|
}
|
|
117
125
|
|
|
@@ -138,6 +146,15 @@ export async function cliStale(
|
|
|
138
146
|
version: target.version,
|
|
139
147
|
currentVersion: running,
|
|
140
148
|
})
|
|
149
|
+
if (r.status === 'managed') {
|
|
150
|
+
const latest = await dependencies.fetchPackageVersion(target).catch(() => undefined)
|
|
151
|
+
return {
|
|
152
|
+
stale: latest !== undefined && latest !== running,
|
|
153
|
+
managed: true,
|
|
154
|
+
current: running,
|
|
155
|
+
...(latest === undefined ? {} : { latest, channel: 'npm' }),
|
|
156
|
+
}
|
|
157
|
+
}
|
|
141
158
|
if (r.status === 'updated') {
|
|
142
159
|
return {
|
|
143
160
|
stale: false,
|
|
@@ -154,13 +171,12 @@ export async function cliStale(
|
|
|
154
171
|
latest: r.latestVersion,
|
|
155
172
|
channel: r.channel,
|
|
156
173
|
}
|
|
157
|
-
} catch {
|
|
158
|
-
const latest = await dependencies.fetchPackageVersion(target).catch(() => undefined)
|
|
174
|
+
} catch (error) {
|
|
159
175
|
return {
|
|
160
|
-
stale:
|
|
161
|
-
managed:
|
|
176
|
+
stale: false,
|
|
177
|
+
managed: false,
|
|
162
178
|
current: running,
|
|
163
|
-
|
|
179
|
+
error: error instanceof Error ? error.message : String(error),
|
|
164
180
|
}
|
|
165
181
|
}
|
|
166
182
|
}
|
|
@@ -294,6 +310,10 @@ Examples:
|
|
|
294
310
|
log.success(`Updated astrale ${result.previousVersion} -> ${result.currentVersion}`)
|
|
295
311
|
log.dim(` channel: ${result.channel}`)
|
|
296
312
|
log.dim(` binary: ${result.bin}`)
|
|
313
|
+
} else if (result?.status === 'managed') {
|
|
314
|
+
const error = packageManagedUpdateError(result.executable)
|
|
315
|
+
if (!opts.yes) throw error
|
|
316
|
+
log.warn(`CLI self-update skipped: ${error.message}`)
|
|
297
317
|
}
|
|
298
318
|
|
|
299
319
|
// Axis B — agent skills. Keep an existing install current with the CLI.
|