@astrale-os/cli 0.8.0-alpha.0 → 0.8.1-alpha.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 +4 -1
- package/dist/astrale.js +62968 -41240
- package/dist/public/connect-core.js +55030 -0
- package/dist/public/keys/index.js +45631 -0
- package/dist/public/paths/index.js +43990 -0
- package/dist/types/admin/instance/model.d.ts +40 -0
- package/dist/types/connect-core.d.ts +22 -0
- package/dist/types/connection/auth.d.ts +33 -0
- package/dist/types/errors.d.ts +23 -0
- package/dist/types/identity/registry.d.ts +25 -0
- package/dist/types/keys/algorithm.d.ts +6 -0
- package/dist/types/keys/credential.d.ts +21 -0
- package/dist/types/keys/index.d.ts +4 -0
- package/dist/types/keys/pair.d.ts +34 -0
- package/dist/types/lib/admin-target.d.ts +74 -0
- package/dist/types/lib/ca-fetch.d.ts +2 -0
- package/dist/types/lib/config.d.ts +71 -0
- package/dist/types/lib/format.d.ts +1 -0
- package/dist/types/lib/idp-session.d.ts +33 -0
- package/dist/types/lib/idp.d.ts +397 -0
- package/dist/types/lib/instance-target.d.ts +36 -0
- package/dist/types/lib/instance.d.ts +186 -0
- package/dist/types/lib/log.d.ts +30 -0
- package/dist/types/lib/login-flow.d.ts +49 -0
- package/dist/types/lib/meta.d.ts +36 -0
- package/dist/types/lib/output.d.ts +83 -0
- package/dist/types/lib/table.d.ts +18 -0
- package/dist/types/lib/validation.d.ts +11 -0
- package/dist/types/paths/index.d.ts +2 -0
- package/dist/types/state/exchange-credentials.d.ts +25 -0
- package/dist/types/state/files.d.ts +9 -0
- package/dist/types/state/identities.d.ts +42 -0
- package/dist/types/state/index.d.ts +8 -0
- package/dist/types/state/paths.d.ts +32 -0
- package/package.json +30 -8
- package/src/admin/.spec/api.d.ts +3 -0
- package/src/admin/.spec/architecture.md +15 -0
- package/src/admin/.spec/icon.svg +3 -0
- package/src/admin/.spec/layout.ts +6 -0
- package/src/admin/catalog/.spec/api.d.ts +65 -0
- package/src/admin/catalog/.spec/architecture.md +6 -0
- package/src/admin/catalog/.spec/icon.svg +3 -0
- package/src/admin/catalog/.spec/layout.ts +6 -0
- package/src/admin/catalog/__tests__/client.test.ts +171 -0
- package/src/admin/catalog/client.ts +214 -0
- package/src/admin/catalog/index.ts +13 -0
- package/src/admin/catalog/model.ts +42 -0
- package/src/admin/catalog/tsconfig.json +8 -0
- package/src/admin/graph/.spec/api.d.ts +28 -0
- package/src/admin/graph/.spec/architecture.md +5 -0
- package/src/admin/graph/.spec/icon.svg +3 -0
- package/src/admin/graph/.spec/layout.ts +3 -0
- package/src/admin/graph/index.ts +6 -0
- package/src/admin/graph/nodes.ts +56 -0
- package/src/admin/index.ts +2 -0
- package/src/admin/instance/.spec/api.d.ts +66 -0
- package/src/admin/instance/.spec/architecture.md +10 -0
- package/src/admin/instance/.spec/icon.svg +3 -0
- package/src/admin/instance/.spec/layout.ts +6 -0
- package/src/admin/instance/__tests__/client.test.ts +289 -0
- package/src/admin/instance/client.ts +298 -0
- package/src/admin/instance/index.ts +16 -0
- package/src/admin/instance/model.ts +65 -0
- package/src/admin/instance/tsconfig.json +8 -0
- package/src/commands/__tests__/admin-instance.test.ts +3 -36
- package/src/commands/__tests__/call.test.ts +21 -68
- package/src/commands/__tests__/domain-install-owned.test.ts +26 -27
- package/src/commands/__tests__/domain-list.test.ts +2 -6
- package/src/commands/__tests__/logs.test.ts +50 -98
- package/src/commands/__tests__/ls.test.ts +21 -17
- package/src/commands/__tests__/read-commands.test.ts +171 -101
- package/src/commands/__tests__/view.test.ts +100 -51
- package/src/commands/admin/status.ts +3 -3
- package/src/commands/admin/use.ts +29 -6
- package/src/commands/auth/login.ts +1 -1
- package/src/commands/auth/logout.ts +4 -2
- package/src/commands/auth/status.ts +2 -2
- package/src/commands/auth/token.ts +2 -2
- package/src/commands/browser.ts +1 -1
- package/src/commands/call.ts +55 -91
- package/src/commands/describe.ts +52 -147
- package/src/commands/domain/install.ts +81 -50
- package/src/commands/domain/list.ts +4 -8
- package/src/commands/domain/publish.ts +9 -31
- package/src/commands/get.ts +32 -65
- package/src/commands/identity/__tests__/register.test.ts +93 -0
- package/src/commands/identity/create.ts +2 -2
- package/src/commands/identity/delete.ts +2 -2
- package/src/commands/identity/export.ts +7 -33
- package/src/commands/identity/import.ts +21 -70
- package/src/commands/identity/list.ts +2 -2
- package/src/commands/identity/register.ts +148 -117
- package/src/commands/identity/sync.ts +2 -2
- package/src/commands/identity/unsync.ts +2 -2
- package/src/commands/identity/use.ts +2 -2
- package/src/commands/identity/whoami.ts +2 -2
- package/src/commands/idp/add.ts +1 -1
- package/src/commands/idp/list.ts +1 -1
- package/src/commands/idp/refresh.ts +1 -1
- package/src/commands/idp/remove.ts +2 -2
- package/src/commands/idp/show.ts +1 -1
- package/src/commands/instance/active.ts +2 -2
- package/src/commands/instance/bookmark.ts +8 -2
- package/src/commands/instance/create.ts +1 -1
- package/src/commands/instance/delete.ts +4 -10
- package/src/commands/instance/forget.ts +1 -1
- package/src/commands/instance/list.ts +3 -3
- package/src/commands/instance/status.ts +5 -7
- package/src/commands/instance/use.ts +3 -3
- package/src/commands/logs.ts +152 -279
- package/src/commands/ls.ts +78 -204
- package/src/commands/mutate.ts +70 -139
- package/src/commands/query.ts +100 -278
- package/src/commands/session/analyze.ts +1 -1
- package/src/commands/session/list.ts +1 -1
- package/src/commands/setup.ts +1 -1
- package/src/commands/status.ts +1 -1
- package/src/commands/studio.ts +1 -1
- package/src/commands/token.ts +26 -21
- package/src/commands/update.ts +1 -1
- package/src/commands/use.ts +2 -3
- package/src/commands/view-serve.ts +1 -1
- package/src/commands/view.ts +67 -109
- package/src/connect-core.test.ts +38 -0
- package/src/connect-core.ts +42 -0
- package/src/connection/.spec/api.d.ts +111 -0
- package/src/connection/.spec/architecture.md +41 -0
- package/src/connection/.spec/capabilities/connection.ts +13 -0
- package/src/connection/.spec/examples/call.ts +7 -0
- package/src/connection/.spec/flows/command.ts +20 -0
- package/src/connection/.spec/flows/session.ts +61 -0
- package/src/connection/.spec/laws/connection.ts +181 -0
- package/src/connection/.spec/layout.ts +19 -0
- package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
- package/src/connection/__tests__/ca-fetch.test.ts +53 -0
- package/src/connection/__tests__/call.test.ts +17 -0
- package/src/connection/__tests__/credential.test.ts +109 -0
- package/src/connection/__tests__/errors.test.ts +96 -0
- package/src/connection/__tests__/exchange.test.ts +224 -0
- package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
- package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
- package/src/connection/__tests__/managed.test.ts +39 -0
- package/src/connection/__tests__/self.test.ts +51 -0
- package/src/connection/__tests__/session.test.ts +140 -0
- package/src/connection/__tests__/target.test.ts +93 -0
- package/src/{kernel → connection}/auth.ts +8 -8
- package/src/connection/call.ts +7 -0
- package/src/{kernel/run.ts → connection/command.ts} +22 -13
- package/src/connection/credential.ts +114 -0
- package/src/{kernel → connection}/errors.ts +32 -9
- package/src/connection/exchange.ts +274 -0
- package/src/connection/index.ts +8 -0
- package/src/connection/self.ts +120 -0
- package/src/connection/session.ts +176 -0
- package/src/connection/target.ts +111 -0
- package/src/connection/tsconfig.json +6 -0
- package/src/graph/.spec/api.d.ts +34 -0
- package/src/graph/.spec/laws/documents.ts +24 -0
- package/src/graph/.spec/laws/projection.ts +13 -0
- package/src/graph/.spec/layout.ts +6 -0
- package/src/graph/__tests__/mutation.test.ts +43 -0
- package/src/graph/__tests__/projection.test.ts +22 -0
- package/src/graph/__tests__/query.test.ts +154 -0
- package/src/graph/index.ts +3 -0
- package/src/graph/mutation.ts +18 -0
- package/src/graph/projection.ts +21 -0
- package/src/graph/query.ts +108 -0
- package/src/graph/tsconfig.json +6 -0
- package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
- package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
- package/src/identity/.spec/api.d.ts +124 -0
- package/src/identity/.spec/architecture.md +33 -0
- package/src/identity/.spec/capabilities/identity.ts +7 -0
- package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
- package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
- package/src/identity/.spec/flows/import.ts +29 -0
- package/src/identity/.spec/laws/identity.ts +56 -0
- package/src/identity/.spec/layout.ts +13 -0
- package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
- package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
- package/src/identity/__tests__/registration.test.ts +64 -0
- package/src/identity/__tests__/registry.test.ts +70 -0
- package/src/identity/__tests__/transfer.test.ts +198 -0
- package/src/identity/index.ts +23 -0
- package/src/identity/registration.ts +72 -0
- package/src/identity/registry.ts +172 -0
- package/src/identity/transfer.ts +187 -0
- package/src/identity/tsconfig.json +6 -0
- package/src/index.ts +1 -0
- package/src/keys/.spec/api.d.ts +74 -0
- package/src/keys/.spec/architecture.md +13 -0
- package/src/keys/.spec/laws/keys.ts +43 -0
- package/src/keys/.spec/layout.ts +6 -0
- package/src/keys/__tests__/algorithm.test.ts +39 -0
- package/src/keys/__tests__/keys.test.ts +168 -0
- package/src/keys/algorithm.ts +33 -0
- package/src/keys/credential.ts +112 -0
- package/src/keys/index.ts +14 -0
- package/src/keys/pair.ts +219 -0
- package/src/keys/tsconfig.json +6 -0
- package/src/lib/__tests__/admin-target.test.ts +67 -7
- package/src/lib/__tests__/binary.test.ts +2 -3
- package/src/lib/__tests__/command-dx.test.ts +2 -2
- package/src/lib/__tests__/config.test.ts +3 -2
- package/src/lib/__tests__/idp.test.ts +4 -6
- package/src/lib/__tests__/instance-target.test.ts +7 -4
- package/src/lib/__tests__/local-status.test.ts +1 -1
- package/src/lib/__tests__/log.test.ts +31 -0
- package/src/lib/__tests__/meta.test.ts +55 -0
- package/src/lib/__tests__/sdk-deps.test.ts +1 -1
- package/src/lib/__tests__/self.test.ts +8 -230
- package/src/lib/__tests__/use-target.test.ts +1 -1
- package/src/lib/__tests__/view-open-intent.test.ts +78 -104
- package/src/lib/__tests__/view-session.test.ts +99 -0
- package/src/lib/admin-domain.ts +39 -25
- package/src/lib/admin-instance.ts +38 -43
- package/src/lib/admin-target.ts +67 -13
- package/src/lib/binary.ts +13 -34
- package/src/lib/browser.ts +1 -1
- package/src/{kernel → lib}/ca-fetch.ts +35 -27
- package/src/lib/config.ts +1 -1
- package/src/lib/idp-session.ts +1 -1
- package/src/lib/idp.ts +3 -8
- package/src/lib/instance-target.ts +12 -8
- package/src/lib/instance.ts +34 -2
- package/src/lib/local-status.ts +2 -2
- package/src/lib/log.ts +3 -1
- package/src/lib/login-flow.ts +32 -5
- package/src/lib/meta.ts +15 -11
- package/src/lib/provision-instance.ts +4 -12
- package/src/lib/self.ts +5 -147
- package/src/lib/update.ts +1 -1
- package/src/lib/use-target.ts +1 -1
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/open-intent.ts +7 -37
- package/src/lib/view/port-allocation.ts +1 -1
- package/src/lib/view/resolve.ts +52 -45
- package/src/lib/view/server.ts +16 -33
- package/src/lib/view/session.ts +51 -22
- package/src/paths/.spec/api.d.ts +13 -0
- package/src/paths/.spec/architecture.md +5 -0
- package/src/paths/.spec/layout.ts +3 -0
- package/src/paths/index.ts +13 -0
- package/src/program/.spec/api.d.ts +42 -0
- package/src/program/.spec/architecture.md +18 -0
- package/src/program/.spec/laws/program.ts +29 -0
- package/src/program/.spec/layout.ts +14 -0
- package/src/program/__tests__/program.test.ts +359 -0
- package/src/program/build.ts +190 -0
- package/src/program/command.ts +42 -0
- package/src/program/index.ts +2 -0
- package/src/program/options.ts +40 -0
- package/src/{registry.ts → program/registry.ts} +5 -1
- package/src/program/tsconfig.json +6 -0
- package/src/setup/steps/admin.ts +18 -3
- package/src/setup/steps/instance.ts +1 -1
- package/src/state/.spec/api.d.ts +133 -0
- package/src/state/.spec/architecture.md +31 -0
- package/src/state/.spec/capabilities/state.ts +7 -0
- package/src/state/.spec/flows/identity-update.ts +22 -0
- package/src/state/.spec/laws/state.ts +73 -0
- package/src/state/.spec/layout.ts +14 -0
- package/src/state/__tests__/exchange-credentials.test.ts +188 -0
- package/src/state/__tests__/files.test.ts +113 -0
- package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
- package/src/state/__tests__/identities.test.ts +142 -0
- package/src/state/__tests__/paths.test.ts +27 -0
- package/src/state/exchange-credentials.ts +219 -0
- package/src/state/files.ts +129 -0
- package/src/state/identities.ts +236 -0
- package/src/state/index.ts +29 -0
- package/src/state/paths.ts +61 -0
- package/src/state/tsconfig.json +6 -0
- package/src/telemetry/__tests__/redact.test.ts +3 -3
- package/src/telemetry/__tests__/trigger.test.ts +1 -1
- package/src/telemetry/settings.ts +1 -1
- package/src/telemetry/store.ts +1 -1
- package/src/test-utils.ts +1 -1
- package/studio/client/dist/assets/elk-api-D0cBetPW.js +1 -0
- package/studio/client/dist/assets/elk-worker.min-C9JGDOE-.js +6312 -0
- package/studio/client/dist/assets/index-BQnJ5sgd.css +1 -0
- package/studio/client/dist/assets/index-ChOr3yP0.css +1 -0
- package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
- package/studio/client/dist/assets/index-bVD2KJgz.js +8 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/package.json +10 -1
- package/studio/server/introspect/extractor.ts +1 -1
- package/studio/server/state/views.test.ts +3 -7
- package/studio/server/state/views.ts +23 -30
- package/studio/tsconfig.json +23 -0
- package/tsconfig.json +1 -1
- package/viewer/dist/main.js +65 -39
- package/viewer/tsconfig.json +13 -0
- package/src/command.ts +0 -42
- package/src/commands/__tests__/help-contract.test.ts +0 -164
- package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
- package/src/kernel/__tests__/errors.test.ts +0 -43
- package/src/kernel/__tests__/expand.test.ts +0 -123
- package/src/kernel/client.ts +0 -214
- package/src/kernel/expand.ts +0 -192
- package/src/kernel/graph.ts +0 -96
- package/src/kernel/index.ts +0 -29
- package/src/kernel/options.ts +0 -22
- package/src/kernel/types.ts +0 -14
- package/src/lib/__tests__/domain-identity.test.ts +0 -60
- package/src/lib/__tests__/fs-atomic.test.ts +0 -104
- package/src/lib/__tests__/identity.test.ts +0 -79
- package/src/lib/__tests__/keys.test.ts +0 -129
- package/src/lib/domain-identity.ts +0 -49
- package/src/lib/env.ts +0 -49
- package/src/lib/fs-atomic.ts +0 -126
- package/src/lib/identity.ts +0 -256
- package/src/lib/keys.ts +0 -294
- package/src/lib/paths.ts +0 -11
- package/src/program.ts +0 -213
- package/studio/client/dist/assets/index-BaqEuIQJ.js +0 -109
- package/studio/client/dist/assets/index-jRdExahh.css +0 -1
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import type { Command } from 'commander'
|
|
2
|
+
|
|
3
|
+
import { describe, expect, test } from 'bun:test'
|
|
4
|
+
import { createHash } from 'node:crypto'
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
|
|
8
|
+
import { buildProgram } from '../index'
|
|
9
|
+
|
|
10
|
+
// Help output is the public CLI contract: version, spec anchors, and skill mirror stay in sync.
|
|
11
|
+
|
|
12
|
+
const cliRoot = join(import.meta.dir, '../../..')
|
|
13
|
+
|
|
14
|
+
/** Every command in the tree (root + groups + nested subgroups), depth-first. */
|
|
15
|
+
function allCommands(cmd: Command): Command[] {
|
|
16
|
+
return [cmd, ...cmd.commands.flatMap(allCommands)]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('help contract — version is single-sourced', () => {
|
|
20
|
+
/** @evidence TEST-CLI-PROGRAM-VERSION-SINGLE-SOURCE */
|
|
21
|
+
test('program version === package.json === release-please manifest', async () => {
|
|
22
|
+
const program = await buildProgram()
|
|
23
|
+
const pkg = JSON.parse(readFileSync(join(cliRoot, 'package.json'), 'utf8')) as {
|
|
24
|
+
version: string
|
|
25
|
+
}
|
|
26
|
+
const manifest = JSON.parse(
|
|
27
|
+
readFileSync(join(cliRoot, '.release-please-manifest.json'), 'utf8'),
|
|
28
|
+
) as Record<string, string>
|
|
29
|
+
|
|
30
|
+
// Program version must follow both package.json and release-please metadata.
|
|
31
|
+
expect(program.version()).toBe(pkg.version)
|
|
32
|
+
expect(manifest['.']).toBe(pkg.version)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
type InspectedCommand = Command & {
|
|
37
|
+
readonly _allowExcessArguments?: boolean
|
|
38
|
+
readonly _allowUnknownOption?: boolean
|
|
39
|
+
readonly _combineFlagAndOptionalValue?: boolean
|
|
40
|
+
readonly _enablePositionalOptions?: boolean
|
|
41
|
+
readonly _hidden?: boolean
|
|
42
|
+
readonly _passThroughOptions?: boolean
|
|
43
|
+
readonly _showHelpAfterError?: boolean
|
|
44
|
+
readonly _showSuggestionAfterError?: boolean
|
|
45
|
+
readonly _storeOptionsAsProperties?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type InspectedArgument = Command['registeredArguments'][number] & {
|
|
49
|
+
readonly parseArg?: unknown
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type InspectedOption = Command['options'][number] & {
|
|
53
|
+
readonly conflictsWith?: readonly string[]
|
|
54
|
+
readonly implied?: Readonly<Record<string, unknown>>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ledgeredSurface(root: Command): readonly object[] {
|
|
58
|
+
const rows: object[] = []
|
|
59
|
+
const visit = (command: Command, parents: readonly string[]): void => {
|
|
60
|
+
const inspected = command as InspectedCommand
|
|
61
|
+
rows.push({
|
|
62
|
+
command: parents.join(' '),
|
|
63
|
+
aliases: command.aliases(),
|
|
64
|
+
description: command.description(),
|
|
65
|
+
summary: command.summary(),
|
|
66
|
+
arguments: command.registeredArguments.map((argument) => {
|
|
67
|
+
const inspectedArgument = argument as InspectedArgument
|
|
68
|
+
return {
|
|
69
|
+
name: argument.name(),
|
|
70
|
+
description: argument.description,
|
|
71
|
+
required: Boolean(argument.required),
|
|
72
|
+
variadic: Boolean(argument.variadic),
|
|
73
|
+
defaultValue: argument.defaultValue ?? null,
|
|
74
|
+
choices: argument.argChoices ?? null,
|
|
75
|
+
hasParser: typeof inspectedArgument.parseArg === 'function',
|
|
76
|
+
}
|
|
77
|
+
}),
|
|
78
|
+
options: command.options.map((option) => {
|
|
79
|
+
const inspectedOption = option as InspectedOption
|
|
80
|
+
return {
|
|
81
|
+
flags: option.flags,
|
|
82
|
+
attribute: option.attributeName(),
|
|
83
|
+
description: option.description,
|
|
84
|
+
required: Boolean(option.required),
|
|
85
|
+
optional: Boolean(option.optional),
|
|
86
|
+
variadic: Boolean(option.variadic),
|
|
87
|
+
mandatory: Boolean(option.mandatory),
|
|
88
|
+
negate: Boolean(option.negate),
|
|
89
|
+
hidden: Boolean(option.hidden),
|
|
90
|
+
defaultValue: option.defaultValue ?? null,
|
|
91
|
+
presetArg: option.presetArg ?? null,
|
|
92
|
+
envVar: option.envVar ?? null,
|
|
93
|
+
choices: option.argChoices ?? null,
|
|
94
|
+
conflictsWith: inspectedOption.conflictsWith ?? [],
|
|
95
|
+
implied: inspectedOption.implied ?? null,
|
|
96
|
+
hasParser: typeof option.parseArg === 'function',
|
|
97
|
+
}
|
|
98
|
+
}),
|
|
99
|
+
behavior: {
|
|
100
|
+
allowExcessArguments: Boolean(inspected._allowExcessArguments),
|
|
101
|
+
allowUnknownOption: Boolean(inspected._allowUnknownOption),
|
|
102
|
+
combineFlagAndOptionalValue: Boolean(inspected._combineFlagAndOptionalValue),
|
|
103
|
+
enablePositionalOptions: Boolean(inspected._enablePositionalOptions),
|
|
104
|
+
passThroughOptions: Boolean(inspected._passThroughOptions),
|
|
105
|
+
showHelpAfterError: Boolean(inspected._showHelpAfterError),
|
|
106
|
+
showSuggestionAfterError: Boolean(inspected._showSuggestionAfterError),
|
|
107
|
+
storeOptionsAsProperties: Boolean(inspected._storeOptionsAsProperties),
|
|
108
|
+
},
|
|
109
|
+
hidden: Boolean(inspected._hidden),
|
|
110
|
+
help: command.helpInformation(),
|
|
111
|
+
})
|
|
112
|
+
for (const child of command.commands) visit(child, [...parents, child.name()])
|
|
113
|
+
}
|
|
114
|
+
visit(root, [])
|
|
115
|
+
return rows.sort((left, right) => {
|
|
116
|
+
const leftName = (left as { command: string }).command
|
|
117
|
+
const rightName = (right as { command: string }).command
|
|
118
|
+
return leftName.localeCompare(rightName)
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
describe('program composition', () => {
|
|
123
|
+
/** @evidence TEST-CLI-PROGRAM-MATCHES-LEDGERED-SURFACE */
|
|
124
|
+
test('matches the complete ledgered command and help surface', async () => {
|
|
125
|
+
const surface = ledgeredSurface(await buildProgram())
|
|
126
|
+
const paths = surface.map((entry) => (entry as { command: string }).command)
|
|
127
|
+
|
|
128
|
+
expect(paths).toEqual([
|
|
129
|
+
'',
|
|
130
|
+
'__view-serve',
|
|
131
|
+
'admin',
|
|
132
|
+
'admin status',
|
|
133
|
+
'admin use',
|
|
134
|
+
'auth',
|
|
135
|
+
'auth login',
|
|
136
|
+
'auth logout',
|
|
137
|
+
'auth status',
|
|
138
|
+
'auth token',
|
|
139
|
+
'browser',
|
|
140
|
+
'call',
|
|
141
|
+
'describe',
|
|
142
|
+
'domain',
|
|
143
|
+
'domain install',
|
|
144
|
+
'domain list',
|
|
145
|
+
'domain publish',
|
|
146
|
+
'get',
|
|
147
|
+
'identity',
|
|
148
|
+
'identity create',
|
|
149
|
+
'identity delete',
|
|
150
|
+
'identity export',
|
|
151
|
+
'identity import',
|
|
152
|
+
'identity list',
|
|
153
|
+
'identity register',
|
|
154
|
+
'identity sync',
|
|
155
|
+
'identity unsync',
|
|
156
|
+
'identity use',
|
|
157
|
+
'identity whoami',
|
|
158
|
+
'idp',
|
|
159
|
+
'idp add',
|
|
160
|
+
'idp list',
|
|
161
|
+
'idp refresh',
|
|
162
|
+
'idp remove',
|
|
163
|
+
'idp show',
|
|
164
|
+
'instance',
|
|
165
|
+
'instance active',
|
|
166
|
+
'instance bookmark',
|
|
167
|
+
'instance create',
|
|
168
|
+
'instance delete',
|
|
169
|
+
'instance forget',
|
|
170
|
+
'instance list',
|
|
171
|
+
'instance status',
|
|
172
|
+
'instance use',
|
|
173
|
+
'logs',
|
|
174
|
+
'ls',
|
|
175
|
+
'mutate',
|
|
176
|
+
'query',
|
|
177
|
+
'session',
|
|
178
|
+
'session analyze',
|
|
179
|
+
'session list',
|
|
180
|
+
'setup',
|
|
181
|
+
'status',
|
|
182
|
+
'studio',
|
|
183
|
+
'token',
|
|
184
|
+
'update',
|
|
185
|
+
'use',
|
|
186
|
+
'view',
|
|
187
|
+
'whoami',
|
|
188
|
+
])
|
|
189
|
+
expect(createHash('sha256').update(JSON.stringify(surface)).digest('hex')).toBe(
|
|
190
|
+
'e030c6f672dea850a90bef16cf4222ea6af45e60f50cfc829c85bd90d9543632',
|
|
191
|
+
)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
/** @evidence TEST-CLI-PROGRAM-BUILDS-ISOLATED-ROOTS */
|
|
195
|
+
test('builds a fresh Commander root for each consumer', async () => {
|
|
196
|
+
const first = await buildProgram()
|
|
197
|
+
first.command('__temporary-test-command')
|
|
198
|
+
|
|
199
|
+
const second = await buildProgram()
|
|
200
|
+
expect(second).not.toBe(first)
|
|
201
|
+
expect(second.commands.some((command) => command.name() === '__temporary-test-command')).toBe(
|
|
202
|
+
false,
|
|
203
|
+
)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe('help contract — no internal SPEC anchors leak to users', () => {
|
|
208
|
+
test('no rendered --help text contains a § section anchor', async () => {
|
|
209
|
+
const program = await buildProgram()
|
|
210
|
+
const offenders = allCommands(program)
|
|
211
|
+
.filter((c) => c.helpInformation().includes('§'))
|
|
212
|
+
.map((c) => c.name() || '<root>')
|
|
213
|
+
|
|
214
|
+
expect(offenders).toEqual([])
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
describe('help contract — IdP/auth surface is registered', () => {
|
|
219
|
+
test('idp group and auth commands are visible in --help tree', async () => {
|
|
220
|
+
const program = await buildProgram()
|
|
221
|
+
const names = allCommands(program).map((command) => command.name())
|
|
222
|
+
|
|
223
|
+
expect(names).toContain('idp')
|
|
224
|
+
expect(names).toContain('add')
|
|
225
|
+
expect(names).toContain('login')
|
|
226
|
+
expect(names).toContain('token')
|
|
227
|
+
expect(names).toContain('update')
|
|
228
|
+
expect(program.helpInformation()).toContain('idp')
|
|
229
|
+
expect(program.helpInformation()).toContain('update')
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
describe('help contract — admin target surface is registered', () => {
|
|
234
|
+
test('admin group and admin-target flags are visible', async () => {
|
|
235
|
+
const program = await buildProgram()
|
|
236
|
+
const names = allCommands(program).map((command) => command.name())
|
|
237
|
+
const instanceCreate = allCommands(program).find((command) => command.name() === 'create')
|
|
238
|
+
|
|
239
|
+
expect(names).toContain('admin')
|
|
240
|
+
expect(names).toContain('status')
|
|
241
|
+
expect(names).toContain('use')
|
|
242
|
+
expect(program.helpInformation()).toContain('admin')
|
|
243
|
+
expect(instanceCreate?.helpInformation()).toContain('--admin <name>')
|
|
244
|
+
expect(instanceCreate?.helpInformation()).toContain('--admin-url <url>')
|
|
245
|
+
|
|
246
|
+
const adminUse = program.commands
|
|
247
|
+
.find((command) => command.name() === 'admin')
|
|
248
|
+
?.commands.find((command) => command.name() === 'use')
|
|
249
|
+
expect(adminUse?.helpInformation()).toContain('--kernel-issuer <url>')
|
|
250
|
+
expect(adminUse?.helpInformation()).toContain('--domain-issuer <url>')
|
|
251
|
+
expect(adminUse?.helpInformation()).not.toContain('--issuer <url>')
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
test('instance create is the alphaCreate flow, not legacy Instance.init DX', async () => {
|
|
255
|
+
const program = await buildProgram()
|
|
256
|
+
const instanceCreate = allCommands(program).find((command) => command.name() === 'create')
|
|
257
|
+
const help = instanceCreate?.helpInformation() ?? ''
|
|
258
|
+
|
|
259
|
+
expect(help).toContain('Instance.alphaCreate')
|
|
260
|
+
expect(help).not.toContain('--no-use')
|
|
261
|
+
expect(help).not.toContain('Instance.init requires --host-id')
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
describe('help contract — connect-only command surface', () => {
|
|
266
|
+
test('runtime management commands are not registered', async () => {
|
|
267
|
+
const program = await buildProgram()
|
|
268
|
+
const names = allCommands(program).map((command) => command.name())
|
|
269
|
+
|
|
270
|
+
// `logs` and `domain` have managed/admin meanings, so only retired local-runtime verbs are absent.
|
|
271
|
+
for (const removed of [
|
|
272
|
+
'init',
|
|
273
|
+
'start',
|
|
274
|
+
'stop',
|
|
275
|
+
'restart',
|
|
276
|
+
'reset',
|
|
277
|
+
'bootstrap',
|
|
278
|
+
'tunnel',
|
|
279
|
+
'graph',
|
|
280
|
+
'server',
|
|
281
|
+
'env',
|
|
282
|
+
]) {
|
|
283
|
+
expect(names).not.toContain(removed)
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
describe('help contract — read command split', () => {
|
|
289
|
+
test('get is point-read only and query owns structured read flags', async () => {
|
|
290
|
+
const program = await buildProgram()
|
|
291
|
+
const get = allCommands(program).find((command) => command.name() === 'get')
|
|
292
|
+
const query = allCommands(program).find((command) => command.name() === 'query')
|
|
293
|
+
const getHelp = get?.helpInformation() ?? ''
|
|
294
|
+
const queryHelp = query?.helpInformation() ?? ''
|
|
295
|
+
|
|
296
|
+
expect(getHelp).toContain('Usage: astrale get [options] <target>')
|
|
297
|
+
expect(getHelp).not.toContain('-l, --long')
|
|
298
|
+
expect(getHelp).not.toContain('--depth')
|
|
299
|
+
expect(getHelp).not.toContain('--children')
|
|
300
|
+
expect(getHelp).not.toContain('--edges')
|
|
301
|
+
expect(getHelp).not.toContain('--graph')
|
|
302
|
+
|
|
303
|
+
expect(queryHelp).toContain('Usage: astrale query [options] [sources...]')
|
|
304
|
+
expect(queryHelp).not.toContain('--depth <n>')
|
|
305
|
+
expect(queryHelp).not.toContain('--children <json>')
|
|
306
|
+
expect(queryHelp).not.toContain('--edges <json>')
|
|
307
|
+
expect(queryHelp).toContain('--ast <json>')
|
|
308
|
+
expect(queryHelp).toContain('--definition <path>')
|
|
309
|
+
expect(queryHelp).toContain('--edge <class>')
|
|
310
|
+
expect(queryHelp).toContain('--direction <direction>')
|
|
311
|
+
expect(queryHelp).toContain('--limit <n>')
|
|
312
|
+
expect(queryHelp).toContain('--cursor <token>')
|
|
313
|
+
expect(queryHelp).not.toContain('--cypher <query>')
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
describe('help contract — explicit anonymous authentication', () => {
|
|
318
|
+
test('kernel commands expose the credential-less session flag', async () => {
|
|
319
|
+
const program = await buildProgram()
|
|
320
|
+
const call = allCommands(program).find((command) => command.name() === 'call')
|
|
321
|
+
|
|
322
|
+
expect(call?.helpInformation()).toContain('--anonymous')
|
|
323
|
+
expect(call?.helpInformation()).toContain('Send no credential')
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
describe('help contract — payload sources', () => {
|
|
328
|
+
test('call excludes --file while mutate supports it', async () => {
|
|
329
|
+
const program = await buildProgram()
|
|
330
|
+
const call = allCommands(program).find((command) => command.name() === 'call')
|
|
331
|
+
const mutate = allCommands(program).find((command) => command.name() === 'mutate')
|
|
332
|
+
const callHelp = call?.helpInformation() ?? ''
|
|
333
|
+
const mutateHelp = mutate?.helpInformation() ?? ''
|
|
334
|
+
|
|
335
|
+
expect(callHelp).toContain('--data <json>')
|
|
336
|
+
expect(callHelp).not.toContain('--file <path>')
|
|
337
|
+
expect(mutateHelp).toContain('--data <json>')
|
|
338
|
+
expect(mutateHelp).toContain('--file <path>')
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
describe('help contract — skill is single-source, not duplicated', () => {
|
|
343
|
+
const canonical = join(cliRoot, 'skills/astrale-cli/SKILL.md')
|
|
344
|
+
// Workspace mirror lives in the superrepo, outside this submodule. Absent
|
|
345
|
+
// when the CLI repo is tested standalone — only assert parity when present.
|
|
346
|
+
const mirror = join(cliRoot, '../.agents/skills/astrale-cli/SKILL.md')
|
|
347
|
+
|
|
348
|
+
test('canonical skill file exists and is non-empty', () => {
|
|
349
|
+
expect(existsSync(canonical)).toBe(true)
|
|
350
|
+
expect(readFileSync(canonical, 'utf8').length).toBeGreaterThan(0)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
test.skipIf(!existsSync(mirror))(
|
|
354
|
+
'workspace mirror is byte-identical to the canonical skill',
|
|
355
|
+
() => {
|
|
356
|
+
expect(readFileSync(mirror, 'utf8')).toBe(readFileSync(canonical, 'utf8'))
|
|
357
|
+
},
|
|
358
|
+
)
|
|
359
|
+
})
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { Command, Option } from 'commander'
|
|
2
|
+
|
|
3
|
+
import pkg from '../../package.json' with { type: 'json' }
|
|
4
|
+
import { withKernelOptions } from './options'
|
|
5
|
+
import { registerCommand, registerGroup } from './registry'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build the fully-wired Commander program (every command + group registered)
|
|
9
|
+
* WITHOUT parsing argv. `bin/astrale.ts` is a thin shim that calls this and
|
|
10
|
+
* `.parse()`; tests import it to walk the command tree (the `--help` surface
|
|
11
|
+
* is asserted to be the source of truth it claims to be — see
|
|
12
|
+
* `program/__tests__/program.test.ts`).
|
|
13
|
+
*/
|
|
14
|
+
export async function buildProgram(): Promise<Command> {
|
|
15
|
+
const program = new Command()
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.name('astrale')
|
|
19
|
+
.description('Astrale CLI — connect to existing Astrale kernels')
|
|
20
|
+
// Single source of truth = package.json (bumped by release-please together
|
|
21
|
+
// with .release-please-manifest.json). Never hand-write a version literal.
|
|
22
|
+
.version(pkg.version)
|
|
23
|
+
.showSuggestionAfterError(true)
|
|
24
|
+
.addOption(new Option('--ci', 'Machine mode: no prompts, structured errors on stderr'))
|
|
25
|
+
.addOption(new Option('--no-prompt', 'Disable interactive prompts'))
|
|
26
|
+
.addOption(
|
|
27
|
+
new Option('--offline-ok', 'Tolerate offline state for commands that can operate locally'),
|
|
28
|
+
)
|
|
29
|
+
.addOption(
|
|
30
|
+
new Option('--log-level <level>', 'Log level').choices(['debug', 'info', 'warn', 'error']),
|
|
31
|
+
)
|
|
32
|
+
.addOption(new Option('--log-format <format>', 'Log output format').choices(['text', 'json']))
|
|
33
|
+
.action(async () => {
|
|
34
|
+
// Bare `astrale` in an interactive terminal with nothing connected yet →
|
|
35
|
+
// launch the guided setup. Otherwise (configured, piped, or CI) show help.
|
|
36
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
37
|
+
const { shouldAutostartSetup } = await import('../setup/engine')
|
|
38
|
+
if (await shouldAutostartSetup()) {
|
|
39
|
+
await (await import('../commands/setup')).default.action(undefined, {})
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
program.help()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// Verbatim alias of `identity whoami` — defer to the command-definition
|
|
47
|
+
// module so options stay in sync.
|
|
48
|
+
const whoamiMod = await import('../commands/identity/whoami')
|
|
49
|
+
registerCommand(program, {
|
|
50
|
+
name: 'whoami',
|
|
51
|
+
description: 'Show the current default identity (alias for identity whoami)',
|
|
52
|
+
options: whoamiMod.default.options,
|
|
53
|
+
action: whoamiMod.default.action,
|
|
54
|
+
})
|
|
55
|
+
registerCommand(program, (await import('../commands/setup')).default)
|
|
56
|
+
registerCommand(program, (await import('../commands/use')).default)
|
|
57
|
+
registerCommand(program, (await import('../commands/update')).default)
|
|
58
|
+
|
|
59
|
+
// ── Graph / kernel ─────────────────────────────────────────────
|
|
60
|
+
registerCommand(program, withKernelOptions((await import('../commands/call')).default))
|
|
61
|
+
registerCommand(program, withKernelOptions((await import('../commands/token')).default))
|
|
62
|
+
registerCommand(program, withKernelOptions((await import('../commands/get')).default))
|
|
63
|
+
registerCommand(program, withKernelOptions((await import('../commands/mutate')).default))
|
|
64
|
+
registerCommand(program, withKernelOptions((await import('../commands/ls')).default))
|
|
65
|
+
registerCommand(program, withKernelOptions((await import('../commands/describe')).default))
|
|
66
|
+
registerCommand(program, withKernelOptions((await import('../commands/query')).default))
|
|
67
|
+
registerCommand(program, withKernelOptions((await import('../commands/logs')).default))
|
|
68
|
+
registerCommand(program, withKernelOptions((await import('../commands/view')).default))
|
|
69
|
+
registerCommand(program, (await import('../commands/status')).default)
|
|
70
|
+
registerCommand(program, (await import('../commands/browser')).default)
|
|
71
|
+
registerCommand(program, (await import('../commands/view-serve')).default)
|
|
72
|
+
registerCommand(program, (await import('../commands/studio')).default)
|
|
73
|
+
|
|
74
|
+
registerGroup(program, {
|
|
75
|
+
name: 'instance',
|
|
76
|
+
description: 'Manage admin-provisioned instances and local bookmarks',
|
|
77
|
+
commands: [
|
|
78
|
+
withKernelOptions((await import('../commands/instance/list')).default),
|
|
79
|
+
(await import('../commands/instance/bookmark')).default,
|
|
80
|
+
(await import('../commands/instance/forget')).default,
|
|
81
|
+
withKernelOptions((await import('../commands/instance/create')).default),
|
|
82
|
+
withKernelOptions((await import('../commands/instance/delete')).default),
|
|
83
|
+
withKernelOptions((await import('../commands/instance/status')).default),
|
|
84
|
+
(await import('../commands/instance/active')).default,
|
|
85
|
+
(await import('../commands/instance/use')).default,
|
|
86
|
+
],
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
registerGroup(program, {
|
|
90
|
+
name: 'domain',
|
|
91
|
+
description: 'List, publish, and install domains (admin catalog + per-instance install)',
|
|
92
|
+
commands: [
|
|
93
|
+
withKernelOptions((await import('../commands/domain/list')).default),
|
|
94
|
+
withKernelOptions((await import('../commands/domain/publish')).default),
|
|
95
|
+
withKernelOptions((await import('../commands/domain/install')).default),
|
|
96
|
+
],
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
registerGroup(program, {
|
|
100
|
+
name: 'admin',
|
|
101
|
+
description: 'Configure the admin kernel',
|
|
102
|
+
commands: [
|
|
103
|
+
(await import('../commands/admin/status')).default,
|
|
104
|
+
(await import('../commands/admin/use')).default,
|
|
105
|
+
],
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
registerGroup(program, {
|
|
109
|
+
name: 'identity',
|
|
110
|
+
description: 'Manage CLI identities & delegation keypairs',
|
|
111
|
+
commands: [
|
|
112
|
+
(await import('../commands/identity/create')).default,
|
|
113
|
+
withKernelOptions((await import('../commands/identity/register')).default),
|
|
114
|
+
(await import('../commands/identity/list')).default,
|
|
115
|
+
(await import('../commands/identity/use')).default,
|
|
116
|
+
(await import('../commands/identity/whoami')).default,
|
|
117
|
+
(await import('../commands/identity/delete')).default,
|
|
118
|
+
(await import('../commands/identity/sync')).default,
|
|
119
|
+
(await import('../commands/identity/unsync')).default,
|
|
120
|
+
(await import('../commands/identity/export')).default,
|
|
121
|
+
(await import('../commands/identity/import')).default,
|
|
122
|
+
],
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
registerGroup(program, {
|
|
126
|
+
name: 'auth',
|
|
127
|
+
description: 'Authenticate with configured identity providers',
|
|
128
|
+
commands: [
|
|
129
|
+
(await import('../commands/auth/login')).default,
|
|
130
|
+
(await import('../commands/auth/token')).default,
|
|
131
|
+
(await import('../commands/auth/logout')).default,
|
|
132
|
+
(await import('../commands/auth/status')).default,
|
|
133
|
+
],
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
registerGroup(program, {
|
|
137
|
+
name: 'session',
|
|
138
|
+
description: 'Locally recorded work sessions: list and DX-analyze them',
|
|
139
|
+
commands: [
|
|
140
|
+
(await import('../commands/session/list')).default,
|
|
141
|
+
(await import('../commands/session/analyze')).default,
|
|
142
|
+
],
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
registerGroup(program, {
|
|
146
|
+
name: 'idp',
|
|
147
|
+
description: 'Manage OpenID Connect identity providers',
|
|
148
|
+
commands: [
|
|
149
|
+
(await import('../commands/idp/add')).default,
|
|
150
|
+
(await import('../commands/idp/list')).default,
|
|
151
|
+
(await import('../commands/idp/show')).default,
|
|
152
|
+
(await import('../commands/idp/refresh')).default,
|
|
153
|
+
(await import('../commands/idp/remove')).default,
|
|
154
|
+
],
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
program.addHelpText(
|
|
158
|
+
'after',
|
|
159
|
+
`
|
|
160
|
+
Command groups:
|
|
161
|
+
Getting started setup (sign in, pick an instance, equip your workspace)
|
|
162
|
+
Kernel ls, get, mutate, call, query, describe, token
|
|
163
|
+
Management admin, instance, domain, identity, auth, idp, update
|
|
164
|
+
Agent browser (drive the GUI via agent-browser)
|
|
165
|
+
Studio studio (launch the local Domain Studio GUI for a workspace)
|
|
166
|
+
|
|
167
|
+
Path syntax:
|
|
168
|
+
/:origin Domain root
|
|
169
|
+
/:origin:class.Name Class node (or /:origin:interface.Name)
|
|
170
|
+
/:origin:class.Name:method Static callable
|
|
171
|
+
<nodePath>::method Instance method dispatch — double colon ::
|
|
172
|
+
@nodeId Reference a node by its UID
|
|
173
|
+
@nodeId::method Instance method on a node by UID
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
$ astrale ls @note --edge /:notes.example.dev:class.references
|
|
177
|
+
$ astrale studio
|
|
178
|
+
$ astrale admin status
|
|
179
|
+
$ astrale update --check
|
|
180
|
+
$ astrale instance bookmark staging --url https://kernel.example.com
|
|
181
|
+
$ astrale instance create my-app
|
|
182
|
+
$ astrale instance status staging
|
|
183
|
+
$ astrale token --audience shell.astrale.ai --ttl 3600
|
|
184
|
+
$ astrale query /:notes.example.dev:class.Note --limit 50
|
|
185
|
+
$ astrale query --file query.v6.json --cursor "$CURSOR"
|
|
186
|
+
`,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return program
|
|
190
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface CommandArgument {
|
|
2
|
+
readonly name: string
|
|
3
|
+
readonly description: string
|
|
4
|
+
readonly required?: boolean
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CommandOption {
|
|
8
|
+
readonly flags: string
|
|
9
|
+
readonly description: string
|
|
10
|
+
readonly default?: string
|
|
11
|
+
readonly choices?: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CommandDefinition {
|
|
15
|
+
readonly name: string
|
|
16
|
+
readonly description: string
|
|
17
|
+
/** One-line summary shown in the parent's command list (Commander `.summary()`). */
|
|
18
|
+
readonly summary?: string
|
|
19
|
+
/** Hide from help listings (internal entries, e.g. `__view-serve`). */
|
|
20
|
+
readonly hidden?: boolean
|
|
21
|
+
readonly aliases?: string[]
|
|
22
|
+
readonly arguments?: CommandArgument[]
|
|
23
|
+
readonly options?: CommandOption[]
|
|
24
|
+
/**
|
|
25
|
+
* Free-form prose appended to `<cmd> --help` (Commander `addHelpText('after')`).
|
|
26
|
+
* Use for command-local behavior the flag list cannot express + canonical
|
|
27
|
+
* examples. Pre-wrap lines to ~78 cols — Commander prints it verbatim.
|
|
28
|
+
*/
|
|
29
|
+
readonly afterHelpText?: string
|
|
30
|
+
/** Opaque Commander callback; each command module owns its concrete argument tuple. */
|
|
31
|
+
readonly action: (...args: never[]) => Promise<void>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CommandGroup {
|
|
35
|
+
readonly name: string
|
|
36
|
+
readonly description: string
|
|
37
|
+
/** One-line summary shown in the program's command list (Commander `.summary()`). */
|
|
38
|
+
readonly summary?: string
|
|
39
|
+
readonly commands: CommandDefinition[]
|
|
40
|
+
/** Nested subgroups (one extra level of `astrale foo bar baz` nesting). */
|
|
41
|
+
readonly subgroups?: CommandGroup[]
|
|
42
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CommandDefinition, CommandOption } from './command'
|
|
2
|
+
|
|
3
|
+
import { RAW_OUTPUT_OPTIONS } from '../lib/output'
|
|
4
|
+
|
|
5
|
+
const KERNEL_PASSTHROUGH_OPTIONS: readonly CommandOption[] = Object.freeze([
|
|
6
|
+
{
|
|
7
|
+
flags: '--url <url>',
|
|
8
|
+
description: 'Target a kernel URL directly (overrides instance resolution)',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
flags: '-i, --instance <name>',
|
|
12
|
+
description: 'Target a specific instance (overrides active)',
|
|
13
|
+
},
|
|
14
|
+
{ flags: '--timeout <ms>', description: 'Request timeout in ms (default: 30000)' },
|
|
15
|
+
{ flags: '--as <identity>', description: 'Call as a specific identity' },
|
|
16
|
+
{ flags: '--creds <token>', description: 'Use a pre-signed credential (e.g. delegation token)' },
|
|
17
|
+
{
|
|
18
|
+
flags: '--anonymous',
|
|
19
|
+
description: 'Send no credential (cannot be combined with --as or --creds)',
|
|
20
|
+
},
|
|
21
|
+
{ flags: '--debug', description: 'Print full error diagnostics on failure' },
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
const KERNEL_OPTIONS: readonly CommandOption[] = Object.freeze([
|
|
25
|
+
{
|
|
26
|
+
flags: '--format <type>',
|
|
27
|
+
description: 'Output format (default: yaml in TTY, json when piped)',
|
|
28
|
+
choices: ['yaml', 'json'],
|
|
29
|
+
},
|
|
30
|
+
...RAW_OUTPUT_OPTIONS,
|
|
31
|
+
...KERNEL_PASSTHROUGH_OPTIONS,
|
|
32
|
+
])
|
|
33
|
+
|
|
34
|
+
/** Attach the single canonical option set to one Kernel-touching command. */
|
|
35
|
+
export function withKernelOptions(definition: CommandDefinition): CommandDefinition {
|
|
36
|
+
return {
|
|
37
|
+
...definition,
|
|
38
|
+
options: [...(definition.options ?? []), ...KERNEL_OPTIONS],
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -2,6 +2,8 @@ import { type Command, Option } from 'commander'
|
|
|
2
2
|
|
|
3
3
|
import type { CommandDefinition, CommandGroup } from './command'
|
|
4
4
|
|
|
5
|
+
type CommanderAction = Parameters<Command['action']>[0]
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Register a single command on a Commander program or subcommand.
|
|
7
9
|
*/
|
|
@@ -36,7 +38,9 @@ export function registerCommand(parent: Command, def: CommandDefinition): void {
|
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
// CommandDefinition keeps each callback tuple opaque; Commander materializes
|
|
42
|
+
// that tuple only after this definition has registered its arguments/options.
|
|
43
|
+
cmd.action(def.action as CommanderAction)
|
|
40
44
|
|
|
41
45
|
if (def.afterHelpText) cmd.addHelpText('after', def.afterHelpText)
|
|
42
46
|
}
|