@astrale-os/cli 0.8.1-alpha.5 → 0.8.1-alpha.7
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 -3
- package/dist/astrale.js +3391 -3163
- package/dist/public/connect-core.js +2041 -3066
- package/dist/public/keys/index.js +1854 -2895
- package/dist/public/paths/index.js +1833 -2882
- package/dist/types/connection/auth.d.ts +3 -0
- package/dist/types/lib/instance-target.d.ts +2 -0
- package/dist/types/lib/instance.d.ts +10 -0
- package/dist/types/lib/invocation.d.ts +3 -0
- package/dist/types/lib/log.d.ts +6 -5
- package/dist/types/lib/output.d.ts +6 -2
- package/package.json +8 -8
- package/src/commands/__tests__/domain-uninstall.test.ts +53 -0
- package/src/commands/__tests__/install-identity-override.test.ts +14 -3
- package/src/commands/__tests__/instance-bookmark.test.ts +66 -1
- package/src/commands/__tests__/instance-list-rows.test.ts +1 -0
- package/src/commands/__tests__/instance-use.test.ts +67 -0
- package/src/commands/__tests__/introspect-parse.test.ts +21 -0
- package/src/commands/__tests__/logs.test.ts +5 -0
- package/src/commands/__tests__/read-commands.test.ts +11 -1
- package/src/commands/__tests__/token-ttl.test.ts +21 -0
- package/src/commands/__tests__/view-build.test.ts +58 -0
- package/src/commands/auth/token.ts +19 -7
- package/src/commands/call.ts +18 -43
- package/src/commands/domain/install.ts +6 -5
- package/src/commands/domain/uninstall.ts +128 -0
- package/src/commands/get.ts +23 -5
- package/src/commands/identity/create.ts +11 -2
- package/src/commands/identity/delete.ts +8 -2
- package/src/commands/identity/export.ts +8 -2
- package/src/commands/identity/import.ts +11 -2
- package/src/commands/identity/sync.ts +8 -2
- package/src/commands/identity/unsync.ts +12 -2
- package/src/commands/identity/use.ts +8 -2
- package/src/commands/identity/whoami.ts +1 -1
- package/src/commands/instance/active.ts +13 -1
- package/src/commands/instance/bookmark.ts +26 -3
- package/src/commands/instance/list.ts +18 -4
- package/src/commands/instance/use.ts +54 -7
- package/src/commands/introspect.ts +117 -0
- package/src/commands/logs.ts +50 -6
- package/src/commands/mutate.ts +2 -3
- package/src/commands/query.ts +3 -5
- package/src/commands/token.ts +35 -8
- package/src/commands/update.ts +30 -9
- package/src/commands/view.ts +33 -19
- package/src/connection/.spec/architecture.md +5 -0
- package/src/connection/.spec/laws/connection.ts +20 -0
- package/src/connection/.spec/layout.ts +1 -0
- package/src/connection/__tests__/auth.test.ts +27 -1
- package/src/connection/__tests__/ca-fetch.test.ts +8 -1
- package/src/connection/__tests__/credential.test.ts +10 -0
- package/src/connection/__tests__/errors.test.ts +430 -36
- package/src/connection/__tests__/exchange.test.ts +46 -5
- package/src/connection/__tests__/reasons.test.ts +78 -0
- package/src/connection/auth.ts +11 -9
- package/src/connection/command.ts +1 -1
- package/src/connection/credential.ts +3 -0
- package/src/connection/errors.ts +195 -154
- package/src/connection/exchange.ts +14 -2
- package/src/connection/reasons.ts +179 -0
- package/src/connection/session.ts +9 -0
- package/src/connection/target.ts +1 -0
- package/src/graph/__tests__/mutation.test.ts +6 -0
- package/src/graph/mutation.ts +13 -0
- package/src/identity/__tests__/registry.test.ts +1 -1
- package/src/identity/registry.ts +12 -5
- package/src/lib/__tests__/command-dx.test.ts +35 -20
- package/src/lib/__tests__/instance-target.test.ts +17 -0
- package/src/lib/__tests__/instance.test.ts +51 -1
- package/src/lib/__tests__/output.test.ts +12 -0
- package/src/lib/__tests__/view-assets.test.ts +33 -1
- package/src/lib/__tests__/view-server.test.ts +68 -0
- package/src/lib/ca-fetch.ts +9 -3
- package/src/lib/command-dx.ts +40 -23
- package/src/lib/instance-target.ts +18 -2
- package/src/lib/instance.ts +31 -0
- package/src/lib/invocation.ts +11 -0
- package/src/lib/log.ts +17 -9
- package/src/lib/output.ts +20 -4
- package/src/lib/view/assets.ts +16 -2
- package/src/program/__tests__/program.test.ts +4 -1
- package/src/program/build.ts +5 -9
- package/src/program/options.ts +2 -1
- package/src/state/__tests__/identities.test.ts +2 -2
- package/src/state/identities.ts +3 -10
- package/studio/package.json +7 -8
- package/viewer/dist/main.js +57 -57
package/src/lib/command-dx.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { Command, CommanderError } from 'commander'
|
|
|
2
2
|
|
|
3
3
|
import chalk from 'chalk'
|
|
4
4
|
|
|
5
|
+
import { isMachine } from './output'
|
|
6
|
+
|
|
5
7
|
export type CommandCatalogEntry = {
|
|
6
8
|
path: string[]
|
|
7
9
|
usage: string
|
|
@@ -34,42 +36,48 @@ export function renderCommanderError(
|
|
|
34
36
|
const matched = matchRegisteredPrefix(program, tokens)
|
|
35
37
|
|
|
36
38
|
if (matched.path.length === 0 && tokens.length > 0) {
|
|
37
|
-
return renderUnknownCommand(tokens, catalog)
|
|
39
|
+
return maybeMachine(renderUnknownCommand(tokens, catalog))
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
if (error.code === 'commander.missingArgument') {
|
|
41
43
|
const usage = usageFor(matched.path, matched.command)
|
|
42
44
|
const argName = error.message.match(/'([^']+)'/)?.[1]
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
`
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
return maybeMachine(
|
|
46
|
+
[
|
|
47
|
+
`Missing required argument${argName ? ` ${chalk.bold(`<${argName}>`)}` : ''} for ${chalk.bold(
|
|
48
|
+
`astrale ${matched.path.join(' ')}`,
|
|
49
|
+
)}`,
|
|
50
|
+
'',
|
|
51
|
+
'Usage:',
|
|
52
|
+
` astrale ${usage}`,
|
|
53
|
+
].join('\n'),
|
|
54
|
+
)
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
if (error.code === 'commander.excessArguments') {
|
|
54
58
|
const usage = usageFor(matched.path, matched.command)
|
|
55
59
|
const extra = tokens.slice(matched.path.length).join(' ')
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
`
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
return maybeMachine(
|
|
61
|
+
[
|
|
62
|
+
`Unexpected argument${extra.includes(' ') ? 's' : ''} for ${chalk.bold(
|
|
63
|
+
`astrale ${matched.path.join(' ')}`,
|
|
64
|
+
)}${extra ? `: ${extra}` : ''}`,
|
|
65
|
+
'',
|
|
66
|
+
'Usage:',
|
|
67
|
+
` astrale ${usage}`,
|
|
68
|
+
].join('\n'),
|
|
69
|
+
)
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
const suggestions = nearestCommands(tokens.join(' '), catalog)
|
|
67
|
-
return
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
return maybeMachine(
|
|
74
|
+
[
|
|
75
|
+
error.message,
|
|
76
|
+
...(suggestions.length > 0
|
|
77
|
+
? ['', 'Did you mean:', ...suggestions.map((s) => ` astrale ${s}`)]
|
|
78
|
+
: []),
|
|
79
|
+
].join('\n'),
|
|
80
|
+
)
|
|
73
81
|
}
|
|
74
82
|
|
|
75
83
|
const RETIRED_COMMANDS: Record<string, string> = {
|
|
@@ -133,6 +141,15 @@ function usageFor(path: string[], command: Command): string {
|
|
|
133
141
|
return [path.join(' '), suffix].filter(Boolean).join(' ')
|
|
134
142
|
}
|
|
135
143
|
|
|
144
|
+
const ANSI_RE = new RegExp(String.fromCharCode(27) + '\\[[0-9;]*m', 'g')
|
|
145
|
+
|
|
146
|
+
function maybeMachine(text: string): string {
|
|
147
|
+
if (!isMachine()) return text
|
|
148
|
+
const plain = text.replace(ANSI_RE, '')
|
|
149
|
+
const first = plain.split('\n').find((line) => line.trim().length > 0) ?? plain
|
|
150
|
+
return JSON.stringify({ error: 'USAGE_ERROR', message: first, detail: plain })
|
|
151
|
+
}
|
|
152
|
+
|
|
136
153
|
function stripOptions(argv: string[]): string[] {
|
|
137
154
|
const out: string[] = []
|
|
138
155
|
for (const token of argv) {
|
|
@@ -94,8 +94,8 @@ async function resolveNamedInstanceTarget(
|
|
|
94
94
|
try {
|
|
95
95
|
managed = await opts.managed(identifier)
|
|
96
96
|
} catch (e) {
|
|
97
|
-
if (
|
|
98
|
-
throw
|
|
97
|
+
if (isManagedInstanceNotFound(e) || isAdminDiscoveryFailure(e)) throw notFound
|
|
98
|
+
throw e
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
const url = normalizeInstanceKernelUrl(managed.url)
|
|
@@ -173,6 +173,22 @@ export function adminTargetToInstance(target: ResolvedAdminTarget): ResolvedInst
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
const ADMIN_DISCOVERY_CODES = new Set([
|
|
177
|
+
'TOKEN_EXCHANGE_SOURCE_INVALID',
|
|
178
|
+
'TOKEN_EXCHANGE_SOURCE_EXPIRED',
|
|
179
|
+
'TOKEN_EXCHANGE_UNSUPPORTED',
|
|
180
|
+
'TOKEN_EXCHANGE_DISCOVERY_FAILED',
|
|
181
|
+
'TOKEN_EXCHANGE_PROTOCOL_ERROR',
|
|
182
|
+
'TOKEN_EXCHANGE_INSECURE',
|
|
183
|
+
'ADMIN_DOMAIN_ISSUER_MISSING',
|
|
184
|
+
'ADMIN_INVENTORY_UNAVAILABLE',
|
|
185
|
+
])
|
|
186
|
+
|
|
187
|
+
/** Admin lookup failed before it could say whether the slug exists. */
|
|
188
|
+
export function isAdminDiscoveryFailure(error: unknown): boolean {
|
|
189
|
+
return error instanceof AstraleError && ADMIN_DISCOVERY_CODES.has(error.code)
|
|
190
|
+
}
|
|
191
|
+
|
|
176
192
|
export function isManagedInstanceNotFound(error: unknown): boolean {
|
|
177
193
|
if (error instanceof AstraleError && error.code === 'INSTANCE_NOT_FOUND') return true
|
|
178
194
|
if (!(error instanceof Error)) return false
|
package/src/lib/instance.ts
CHANGED
|
@@ -68,6 +68,11 @@ export type ResolvedInstance = {
|
|
|
68
68
|
status?: string
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
export type BookmarkTrustConflict = {
|
|
72
|
+
readonly name: string
|
|
73
|
+
readonly caFile: string | null
|
|
74
|
+
}
|
|
75
|
+
|
|
71
76
|
function seed(): InstanceStore {
|
|
72
77
|
return { active: '', instances: {} }
|
|
73
78
|
}
|
|
@@ -198,6 +203,32 @@ export function resolveInstanceKey(store: InstanceStore, identifier: string): st
|
|
|
198
203
|
return null
|
|
199
204
|
}
|
|
200
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Find other bookmarks for the same normalized Kernel URL whose TLS trust
|
|
208
|
+
* configuration differs. System trust (`undefined`) is a configuration too:
|
|
209
|
+
* mixing it with a custom CA is exactly as significant as mixing two CA files.
|
|
210
|
+
*/
|
|
211
|
+
export function findBookmarkTrustConflicts(
|
|
212
|
+
store: InstanceStore,
|
|
213
|
+
name: string,
|
|
214
|
+
url: string,
|
|
215
|
+
caFile?: string,
|
|
216
|
+
): BookmarkTrustConflict[] {
|
|
217
|
+
const normalizedUrl = normalizeInstanceKernelUrl(url)
|
|
218
|
+
const configuredCa = caFile ?? null
|
|
219
|
+
return Object.entries(store.instances).flatMap(([candidateName, entry]) => {
|
|
220
|
+
if (
|
|
221
|
+
candidateName === name ||
|
|
222
|
+
entry.url === undefined ||
|
|
223
|
+
normalizeInstanceKernelUrl(entry.url) !== normalizedUrl ||
|
|
224
|
+
(entry.caFile ?? null) === configuredCa
|
|
225
|
+
) {
|
|
226
|
+
return []
|
|
227
|
+
}
|
|
228
|
+
return [{ name: candidateName, caFile: entry.caFile ?? null }]
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
201
232
|
export async function addInstance(key: string, opts: AddInstanceOpts = {}): Promise<InstanceEntry> {
|
|
202
233
|
validateName(key, 'Instance')
|
|
203
234
|
if (RESERVED_SLUGS.has(key)) throw new ReservedSlugError(key)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Process-wide machine mode from argv (`--json` / `--raw` / `--ci`). */
|
|
2
|
+
|
|
3
|
+
let argvMachine = false
|
|
4
|
+
|
|
5
|
+
export function configureInvocation(argv: readonly string[]): void {
|
|
6
|
+
argvMachine = argv.some((token) => token === '--json' || token === '--raw' || token === '--ci')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function invocationWantsMachine(): boolean {
|
|
10
|
+
return argvMachine
|
|
11
|
+
}
|
package/src/lib/log.ts
CHANGED
|
@@ -3,7 +3,7 @@ import ora, { type Ora } from 'ora'
|
|
|
3
3
|
|
|
4
4
|
import { AstraleError, NotImplementedError } from '../errors'
|
|
5
5
|
import { formatElapsed } from './format'
|
|
6
|
-
import { isMachine, type
|
|
6
|
+
import { isMachine, type MachineOpts } from './output'
|
|
7
7
|
|
|
8
8
|
export const log = {
|
|
9
9
|
info: (msg: string) => console.log(chalk.blue('ℹ'), msg),
|
|
@@ -16,26 +16,34 @@ export const log = {
|
|
|
16
16
|
dim: (msg: string) => console.log(chalk.dim(msg)),
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
/** Report an error with hint (when present) and exit.
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
export function fatal(e: unknown, opts?: RawOutputOpts): never {
|
|
19
|
+
/** Report an error with hint (when present) and exit. `--json` / `--ci` / a
|
|
20
|
+
* non-TTY stdout always get one structured JSON line on stderr. */
|
|
21
|
+
export function fatal(e: unknown, opts?: MachineOpts): never {
|
|
23
22
|
// Ctrl-C at an interactive (@inquirer/prompts) prompt — exit quietly with the
|
|
24
23
|
// SIGINT convention, not a red error line.
|
|
25
24
|
if (e instanceof Error && e.name === 'ExitPromptError') process.exit(130)
|
|
26
25
|
const msg = e instanceof Error ? e.message : String(e)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const payload: Record<string, unknown> = { error, message: msg }
|
|
26
|
+
const code = e instanceof AstraleError ? e.code : e instanceof Error ? e.name : 'Error'
|
|
27
|
+
if (isMachine(opts)) {
|
|
28
|
+
const payload: Record<string, unknown> = { error: code, message: msg }
|
|
30
29
|
if (e instanceof AstraleError && e.hint) payload.hint = e.hint
|
|
31
30
|
process.stderr.write(JSON.stringify(payload) + '\n')
|
|
32
31
|
process.exit(1)
|
|
33
32
|
}
|
|
34
|
-
log.error(msg)
|
|
33
|
+
log.error(e instanceof AstraleError ? `${code}: ${msg}` : msg)
|
|
35
34
|
if (e instanceof AstraleError && e.hint) log.dim(` hint: ${e.hint}`)
|
|
36
35
|
process.exit(1)
|
|
37
36
|
}
|
|
38
37
|
|
|
38
|
+
/** Admit expected invalid input and exit through {@link fatal}. */
|
|
39
|
+
export function failClosed(error: unknown, opts?: MachineOpts): never {
|
|
40
|
+
if (error instanceof AstraleError) fatal(error, opts)
|
|
41
|
+
fatal(
|
|
42
|
+
new AstraleError('INVALID_INPUT', error instanceof Error ? error.message : String(error)),
|
|
43
|
+
opts,
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
/** Shortcut for stub commands that aren't wired in v1 (§15). */
|
|
40
48
|
export function fatalNotImplemented(feature: string, hint?: string): never {
|
|
41
49
|
fatal(new NotImplementedError(feature, hint))
|
package/src/lib/output.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
import { stringify as yamlStringify } from 'yaml'
|
|
3
3
|
|
|
4
|
+
import { invocationWantsMachine } from './invocation'
|
|
4
5
|
import { renderTable, type Column } from './table'
|
|
5
6
|
|
|
6
7
|
export type { Column } from './table'
|
|
@@ -13,6 +14,8 @@ export type OutputOpts = {
|
|
|
13
14
|
|
|
14
15
|
export type RawOutputOpts = Pick<OutputOpts, 'raw' | 'json'>
|
|
15
16
|
|
|
17
|
+
export type MachineOpts = RawOutputOpts & { readonly ci?: boolean }
|
|
18
|
+
|
|
16
19
|
export const RAW_OUTPUT_OPTIONS = [
|
|
17
20
|
{ flags: '--json', description: 'Always-valid JSON (for jq)' },
|
|
18
21
|
{ flags: '--raw', description: 'Unwrapped: bare scalar / raw bytes / JSON for objects' },
|
|
@@ -20,10 +23,15 @@ export const RAW_OUTPUT_OPTIONS = [
|
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* Is the consumer a machine (emit structured data, not a pretty view)?
|
|
23
|
-
* True for `--json`, `--raw`,
|
|
26
|
+
* True for `--json`, `--raw`, `--ci`, a process-wide `--ci/--json/--raw` on argv,
|
|
27
|
+
* or any non-TTY stdout (pipe, redirect, agent).
|
|
24
28
|
*/
|
|
25
|
-
export function isMachine(opts?:
|
|
26
|
-
return
|
|
29
|
+
export function isMachine(opts?: MachineOpts): boolean {
|
|
30
|
+
return (
|
|
31
|
+
!!(opts?.raw || opts?.json || opts?.ci) ||
|
|
32
|
+
invocationWantsMachine() ||
|
|
33
|
+
!(process.stdout.isTTY ?? false)
|
|
34
|
+
)
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -132,6 +140,14 @@ export type ListOpts = OutputOpts & {
|
|
|
132
140
|
|
|
133
141
|
const NOISE_KEYS = new Set(['schema', 'icon', 'code', 'inputSchema', 'outputSchema'])
|
|
134
142
|
|
|
143
|
+
function isNoiseKey(key: string): boolean {
|
|
144
|
+
if (NOISE_KEYS.has(key)) return true
|
|
145
|
+
const dot = key.lastIndexOf('.')
|
|
146
|
+
const colon = key.lastIndexOf(':')
|
|
147
|
+
const split = Math.max(dot, colon)
|
|
148
|
+
return split >= 0 && NOISE_KEYS.has(key.slice(split + 1))
|
|
149
|
+
}
|
|
150
|
+
|
|
135
151
|
/**
|
|
136
152
|
* Strip heavy, low-signal keys (serialized schema blobs, SVG icons, code) at any
|
|
137
153
|
* depth — so machine output is the kernel's data minus the noise, never a wall.
|
|
@@ -141,7 +157,7 @@ export function denoise(value: unknown): unknown {
|
|
|
141
157
|
if (value && typeof value === 'object') {
|
|
142
158
|
const out: Record<string, unknown> = {}
|
|
143
159
|
for (const [k, v] of Object.entries(value)) {
|
|
144
|
-
if (
|
|
160
|
+
if (isNoiseKey(k)) continue
|
|
145
161
|
out[k] = v && typeof v === 'object' ? denoise(v) : v
|
|
146
162
|
}
|
|
147
163
|
return out
|
package/src/lib/view/assets.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
1
|
+
import { existsSync, statSync } from 'node:fs'
|
|
2
2
|
import { copyFile } from 'node:fs/promises'
|
|
3
3
|
import { dirname, join } from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
@@ -34,8 +34,8 @@ export async function ensureViewerAssets(
|
|
|
34
34
|
entry = process.argv[1] ?? '.',
|
|
35
35
|
): Promise<string> {
|
|
36
36
|
const dist = viewerDistDir(moduleUrl, entry)
|
|
37
|
-
if (hasViewerBundle(dist)) return dist
|
|
38
37
|
const srcDir = join(dist, '..')
|
|
38
|
+
if (hasViewerBundle(dist) && !viewerSourceIsNewer(srcDir, dist)) return dist
|
|
39
39
|
const bun = (
|
|
40
40
|
globalThis as { Bun?: { build: (o: object) => Promise<{ success: boolean; logs: unknown[] }> } }
|
|
41
41
|
).Bun
|
|
@@ -62,3 +62,17 @@ function hasViewerBundle(directory: string): boolean {
|
|
|
62
62
|
function hasViewerSource(directory: string): boolean {
|
|
63
63
|
return existsSync(join(directory, 'main.ts')) && existsSync(join(directory, 'index.html'))
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
function viewerSourceIsNewer(source: string, dist: string): boolean {
|
|
67
|
+
if (!hasViewerSource(source)) return false
|
|
68
|
+
if (!hasViewerBundle(dist)) return true
|
|
69
|
+
const newestSource = Math.max(
|
|
70
|
+
statSync(join(source, 'main.ts')).mtimeMs,
|
|
71
|
+
statSync(join(source, 'index.html')).mtimeMs,
|
|
72
|
+
)
|
|
73
|
+
const oldestOutput = Math.min(
|
|
74
|
+
statSync(join(dist, 'main.js')).mtimeMs,
|
|
75
|
+
statSync(join(dist, 'index.html')).mtimeMs,
|
|
76
|
+
)
|
|
77
|
+
return newestSource > oldestOutput
|
|
78
|
+
}
|
|
@@ -142,6 +142,7 @@ describe('program composition', () => {
|
|
|
142
142
|
'domain install',
|
|
143
143
|
'domain list',
|
|
144
144
|
'domain publish',
|
|
145
|
+
'domain uninstall',
|
|
145
146
|
'get',
|
|
146
147
|
'identity',
|
|
147
148
|
'identity create',
|
|
@@ -169,6 +170,7 @@ describe('program composition', () => {
|
|
|
169
170
|
'instance list',
|
|
170
171
|
'instance status',
|
|
171
172
|
'instance use',
|
|
173
|
+
'introspect',
|
|
172
174
|
'logs',
|
|
173
175
|
'mutate',
|
|
174
176
|
'query',
|
|
@@ -185,7 +187,7 @@ describe('program composition', () => {
|
|
|
185
187
|
'whoami',
|
|
186
188
|
])
|
|
187
189
|
expect(createHash('sha256').update(JSON.stringify(surface)).digest('hex')).toBe(
|
|
188
|
-
'
|
|
190
|
+
'73c4dc12159039257a1847701b16ef963f5c96a188f3656604dd2770e74c116e',
|
|
189
191
|
)
|
|
190
192
|
})
|
|
191
193
|
|
|
@@ -334,6 +336,7 @@ describe('help contract — payload sources', () => {
|
|
|
334
336
|
|
|
335
337
|
expect(callHelp).toContain('--data <json>')
|
|
336
338
|
expect(callHelp).not.toContain('--file <path>')
|
|
339
|
+
expect(callHelp).not.toContain('--describe')
|
|
337
340
|
expect(mutateHelp).toContain('--data <json>')
|
|
338
341
|
expect(mutateHelp).toContain('--file <path>')
|
|
339
342
|
})
|
package/src/program/build.ts
CHANGED
|
@@ -23,13 +23,6 @@ export async function buildProgram(): Promise<Command> {
|
|
|
23
23
|
.showSuggestionAfterError(true)
|
|
24
24
|
.addOption(new Option('--ci', 'Machine mode: no prompts, structured errors on stderr'))
|
|
25
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
26
|
.action(async () => {
|
|
34
27
|
// Bare `astrale` in an interactive terminal with nothing connected yet →
|
|
35
28
|
// launch the guided setup. Otherwise (configured, piped, or CI) show help.
|
|
@@ -62,6 +55,7 @@ export async function buildProgram(): Promise<Command> {
|
|
|
62
55
|
registerCommand(program, withKernelOptions((await import('../commands/get')).default))
|
|
63
56
|
registerCommand(program, withKernelOptions((await import('../commands/mutate')).default))
|
|
64
57
|
registerCommand(program, withKernelOptions((await import('../commands/query')).default))
|
|
58
|
+
registerCommand(program, withKernelOptions((await import('../commands/introspect')).default))
|
|
65
59
|
registerCommand(program, withKernelOptions((await import('../commands/logs')).default))
|
|
66
60
|
registerCommand(program, withKernelOptions((await import('../commands/view')).default))
|
|
67
61
|
registerCommand(program, (await import('../commands/status')).default)
|
|
@@ -86,11 +80,12 @@ export async function buildProgram(): Promise<Command> {
|
|
|
86
80
|
|
|
87
81
|
registerGroup(program, {
|
|
88
82
|
name: 'domain',
|
|
89
|
-
description: 'List, publish, and
|
|
83
|
+
description: 'List, publish, install, and uninstall domains',
|
|
90
84
|
commands: [
|
|
91
85
|
withKernelOptions((await import('../commands/domain/list')).default),
|
|
92
86
|
withKernelOptions((await import('../commands/domain/publish')).default),
|
|
93
87
|
withKernelOptions((await import('../commands/domain/install')).default),
|
|
88
|
+
withKernelOptions((await import('../commands/domain/uninstall')).default),
|
|
94
89
|
],
|
|
95
90
|
})
|
|
96
91
|
|
|
@@ -157,7 +152,7 @@ export async function buildProgram(): Promise<Command> {
|
|
|
157
152
|
`
|
|
158
153
|
Command groups:
|
|
159
154
|
Getting started setup (sign in, pick an instance, equip your workspace)
|
|
160
|
-
Kernel get, mutate, call, query, token
|
|
155
|
+
Kernel get, mutate, call, query, introspect, logs, view, token
|
|
161
156
|
Management admin, instance, domain, identity, auth, idp, update
|
|
162
157
|
Agent browser (drive the GUI via agent-browser)
|
|
163
158
|
Studio studio (launch the local Domain Studio GUI for a workspace)
|
|
@@ -179,6 +174,7 @@ Examples:
|
|
|
179
174
|
$ astrale instance status staging
|
|
180
175
|
$ astrale token --audience shell.astrale.ai --ttl 3600
|
|
181
176
|
$ astrale query /:notes.example.dev:class.Note --limit 50
|
|
177
|
+
$ astrale introspect /:host.astrale.ai:class.Manager:createInstance
|
|
182
178
|
$ astrale query --file query.v6.json --cursor "$CURSOR"
|
|
183
179
|
`,
|
|
184
180
|
)
|
package/src/program/options.ts
CHANGED
|
@@ -16,7 +16,8 @@ const KERNEL_PASSTHROUGH_OPTIONS: readonly CommandOption[] = Object.freeze([
|
|
|
16
16
|
{ flags: '--creds <token>', description: 'Use a pre-signed credential (e.g. delegation token)' },
|
|
17
17
|
{
|
|
18
18
|
flags: '--anonymous',
|
|
19
|
-
description:
|
|
19
|
+
description:
|
|
20
|
+
'Send no credential (cannot be combined with --as or --creds; --as and --creds are mutually exclusive)',
|
|
20
21
|
},
|
|
21
22
|
{ flags: '--debug', description: 'Print full error diagnostics on failure' },
|
|
22
23
|
])
|
|
@@ -46,7 +46,7 @@ describe('identity state', () => {
|
|
|
46
46
|
path,
|
|
47
47
|
now: () => new Date('2025-01-01T00:00:00.000Z'),
|
|
48
48
|
})
|
|
49
|
-
expect(missing
|
|
49
|
+
expect(missing).toEqual({ default: '', identities: {} })
|
|
50
50
|
await expect(readFile(path, 'utf-8')).rejects.toThrow()
|
|
51
51
|
|
|
52
52
|
await writeFile(path, legacy)
|
|
@@ -56,7 +56,7 @@ describe('identity state', () => {
|
|
|
56
56
|
|
|
57
57
|
const current = `${JSON.stringify({ version: IDENTITY_STORE_VERSION, ...missing })}\n`
|
|
58
58
|
await writeFile(path, current)
|
|
59
|
-
expect((await readIdentityStore({ path })).identities
|
|
59
|
+
expect((await readIdentityStore({ path })).identities).toEqual({})
|
|
60
60
|
expect(await readFile(path, 'utf-8')).toBe(current)
|
|
61
61
|
})
|
|
62
62
|
|
package/src/state/identities.ts
CHANGED
|
@@ -183,17 +183,10 @@ function hasVersion(input: unknown): input is { readonly version: unknown } {
|
|
|
183
183
|
return typeof input === 'object' && input !== null && Object.hasOwn(input, 'version')
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
function seed(
|
|
186
|
+
function seed(_now: Date): IdentityStore {
|
|
187
187
|
return {
|
|
188
|
-
default: '
|
|
189
|
-
identities: {
|
|
190
|
-
manager: {
|
|
191
|
-
subject: 'manager',
|
|
192
|
-
createdAt: now.toISOString(),
|
|
193
|
-
source: 'key',
|
|
194
|
-
mode: 'local',
|
|
195
|
-
},
|
|
196
|
-
},
|
|
188
|
+
default: '',
|
|
189
|
+
identities: {},
|
|
197
190
|
}
|
|
198
191
|
}
|
|
199
192
|
|
package/studio/package.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"typecheck": "tsgo --noEmit"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@astrale-os/shell": "file:../vendor/astrale-os-shell-0.3.8-beta.
|
|
22
|
+
"@astrale-os/shell": "file:../vendor/astrale-os-shell-0.3.8-beta.2.tgz",
|
|
23
23
|
"@dagrejs/dagre": "^3.0.0",
|
|
24
24
|
"@radix-ui/react-collapsible": "^1.1.0",
|
|
25
25
|
"@radix-ui/react-dialog": "^1.1.6",
|
|
@@ -60,12 +60,11 @@
|
|
|
60
60
|
"vite": "^7.0.0"
|
|
61
61
|
},
|
|
62
62
|
"overrides": {
|
|
63
|
-
"@astrale-os/kernel-client": "file:../vendor/kernel/astrale-os-kernel-client-0.6.0-beta.
|
|
64
|
-
"@astrale-os/kernel-core": "file:../vendor/kernel/astrale-os-kernel-core-0.9.0-beta.
|
|
65
|
-
"@astrale-os/kernel-dsl": "file:../vendor/kernel/astrale-os-kernel-dsl-0.2.0-beta.
|
|
66
|
-
"@astrale-os/kernel-
|
|
67
|
-
"@astrale-os/kernel-
|
|
68
|
-
"@astrale-os/
|
|
69
|
-
"@astrale-os/sdk": "file:../vendor/astrale-os-sdk-0.5.0-beta.1.tgz"
|
|
63
|
+
"@astrale-os/kernel-client": "file:../vendor/kernel/astrale-os-kernel-client-0.6.0-beta.3.tgz",
|
|
64
|
+
"@astrale-os/kernel-core": "file:../vendor/kernel/astrale-os-kernel-core-0.9.0-beta.2.tgz",
|
|
65
|
+
"@astrale-os/kernel-dsl": "file:../vendor/kernel/astrale-os-kernel-dsl-0.2.0-beta.2.tgz",
|
|
66
|
+
"@astrale-os/kernel-protocol": "file:../vendor/kernel/astrale-os-kernel-protocol-0.5.0-beta.2.tgz",
|
|
67
|
+
"@astrale-os/kernel-server": "file:../vendor/kernel/astrale-os-kernel-server-0.5.0-beta.3.tgz",
|
|
68
|
+
"@astrale-os/sdk": "file:../vendor/astrale-os-sdk-0.5.0-beta.3.tgz"
|
|
70
69
|
}
|
|
71
70
|
}
|