@astrale-os/cli 0.8.1-alpha.4 → 0.8.1-alpha.6
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 +1 -1
- package/dist/astrale.js +827 -575
- package/dist/public/connect-core.js +22 -16
- package/dist/public/keys/index.js +3 -10
- package/dist/public/paths/index.js +3 -10
- package/dist/types/lib/instance-target.d.ts +2 -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 +1 -1
- 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/auth/token.ts +19 -7
- package/src/commands/call.ts +18 -43
- 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/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 +5 -3
- package/src/connection/__tests__/credential.test.ts +10 -0
- package/src/connection/__tests__/errors.test.ts +20 -0
- package/src/connection/credential.ts +3 -0
- package/src/connection/errors.ts +74 -12
- 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 +47 -15
- package/src/lib/__tests__/instance-target.test.ts +17 -0
- package/src/lib/__tests__/output.test.ts +12 -0
- package/src/lib/command-dx.ts +41 -23
- package/src/lib/instance-target.ts +18 -2
- package/src/lib/invocation.ts +11 -0
- package/src/lib/log.ts +17 -9
- package/src/lib/output.ts +20 -4
- package/src/program/__tests__/program.test.ts +4 -2
- package/src/program/build.ts +3 -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/src/commands/describe.ts +0 -86
|
@@ -52281,17 +52281,10 @@ async function readDecoded(options) {
|
|
|
52281
52281
|
function hasVersion(input) {
|
|
52282
52282
|
return typeof input === "object" && input !== null && Object.hasOwn(input, "version");
|
|
52283
52283
|
}
|
|
52284
|
-
function seed(
|
|
52284
|
+
function seed(_now) {
|
|
52285
52285
|
return {
|
|
52286
|
-
default: "
|
|
52287
|
-
identities: {
|
|
52288
|
-
manager: {
|
|
52289
|
-
subject: "manager",
|
|
52290
|
-
createdAt: now.toISOString(),
|
|
52291
|
-
source: "key",
|
|
52292
|
-
mode: "local"
|
|
52293
|
-
}
|
|
52294
|
-
}
|
|
52286
|
+
default: "",
|
|
52287
|
+
identities: {}
|
|
52295
52288
|
};
|
|
52296
52289
|
}
|
|
52297
52290
|
function encodeV1(store) {
|
|
@@ -54096,10 +54089,10 @@ async function readIdentities() {
|
|
|
54096
54089
|
}
|
|
54097
54090
|
async function getDefault() {
|
|
54098
54091
|
const store = await readIdentities();
|
|
54099
|
-
|
|
54100
|
-
|
|
54101
|
-
throw new Error(`Default identity "${store.default}" not found. Run: astrale identity create ${store.default}`);
|
|
54092
|
+
if (store.default === "" || store.identities[store.default] === undefined) {
|
|
54093
|
+
throw new AstraleError("NO_IDENTITY", "No default identity.", "Run: astrale identity create <name>");
|
|
54102
54094
|
}
|
|
54095
|
+
const identity3 = store.identities[store.default];
|
|
54103
54096
|
return { ...identity3, name: store.default };
|
|
54104
54097
|
}
|
|
54105
54098
|
async function getIdentity(name) {
|
|
@@ -54492,9 +54485,9 @@ async function resolveNamedInstanceTarget(identifier, opts) {
|
|
|
54492
54485
|
try {
|
|
54493
54486
|
managed = await opts.managed(identifier);
|
|
54494
54487
|
} catch (e) {
|
|
54495
|
-
if (
|
|
54496
|
-
throw
|
|
54497
|
-
throw
|
|
54488
|
+
if (isManagedInstanceNotFound(e) || isAdminDiscoveryFailure(e))
|
|
54489
|
+
throw notFound;
|
|
54490
|
+
throw e;
|
|
54498
54491
|
}
|
|
54499
54492
|
const url3 = normalizeInstanceKernelUrl(managed.url);
|
|
54500
54493
|
return {
|
|
@@ -54560,6 +54553,19 @@ function adminTargetToInstance(target) {
|
|
|
54560
54553
|
...target.caFile ? { caFile: target.caFile } : {}
|
|
54561
54554
|
};
|
|
54562
54555
|
}
|
|
54556
|
+
var ADMIN_DISCOVERY_CODES = new Set([
|
|
54557
|
+
"TOKEN_EXCHANGE_SOURCE_INVALID",
|
|
54558
|
+
"TOKEN_EXCHANGE_SOURCE_EXPIRED",
|
|
54559
|
+
"TOKEN_EXCHANGE_UNSUPPORTED",
|
|
54560
|
+
"TOKEN_EXCHANGE_DISCOVERY_FAILED",
|
|
54561
|
+
"TOKEN_EXCHANGE_PROTOCOL_ERROR",
|
|
54562
|
+
"TOKEN_EXCHANGE_INSECURE",
|
|
54563
|
+
"ADMIN_DOMAIN_ISSUER_MISSING",
|
|
54564
|
+
"ADMIN_INVENTORY_UNAVAILABLE"
|
|
54565
|
+
]);
|
|
54566
|
+
function isAdminDiscoveryFailure(error51) {
|
|
54567
|
+
return error51 instanceof AstraleError && ADMIN_DISCOVERY_CODES.has(error51.code);
|
|
54568
|
+
}
|
|
54563
54569
|
function isManagedInstanceNotFound(error51) {
|
|
54564
54570
|
if (error51 instanceof AstraleError && error51.code === "INSTANCE_NOT_FOUND")
|
|
54565
54571
|
return true;
|
|
@@ -45340,17 +45340,10 @@ async function readDecoded(options) {
|
|
|
45340
45340
|
function hasVersion(input) {
|
|
45341
45341
|
return typeof input === "object" && input !== null && Object.hasOwn(input, "version");
|
|
45342
45342
|
}
|
|
45343
|
-
function seed(
|
|
45343
|
+
function seed(_now) {
|
|
45344
45344
|
return {
|
|
45345
|
-
default: "
|
|
45346
|
-
identities: {
|
|
45347
|
-
manager: {
|
|
45348
|
-
subject: "manager",
|
|
45349
|
-
createdAt: now.toISOString(),
|
|
45350
|
-
source: "key",
|
|
45351
|
-
mode: "local"
|
|
45352
|
-
}
|
|
45353
|
-
}
|
|
45345
|
+
default: "",
|
|
45346
|
+
identities: {}
|
|
45354
45347
|
};
|
|
45355
45348
|
}
|
|
45356
45349
|
function encodeV1(store) {
|
|
@@ -43940,17 +43940,10 @@ async function readDecoded(options) {
|
|
|
43940
43940
|
function hasVersion(input) {
|
|
43941
43941
|
return typeof input === "object" && input !== null && Object.hasOwn(input, "version");
|
|
43942
43942
|
}
|
|
43943
|
-
function seed(
|
|
43943
|
+
function seed(_now) {
|
|
43944
43944
|
return {
|
|
43945
|
-
default: "
|
|
43946
|
-
identities: {
|
|
43947
|
-
manager: {
|
|
43948
|
-
subject: "manager",
|
|
43949
|
-
createdAt: now.toISOString(),
|
|
43950
|
-
source: "key",
|
|
43951
|
-
mode: "local"
|
|
43952
|
-
}
|
|
43953
|
-
}
|
|
43945
|
+
default: "",
|
|
43946
|
+
identities: {}
|
|
43954
43947
|
};
|
|
43955
43948
|
}
|
|
43956
43949
|
function encodeV1(store) {
|
|
@@ -33,4 +33,6 @@ export type ResolveInstanceTargetOpts = {
|
|
|
33
33
|
export declare function resolveInstanceTarget(request: InstanceTargetRequest, opts: ResolveInstanceTargetOpts): Promise<ResolvedInstanceTarget>;
|
|
34
34
|
export declare function couldBeConfiguredAdminInstance(identifier: string, config: AstraleConfig): boolean;
|
|
35
35
|
export declare function adminTargetToInstance(target: ResolvedAdminTarget): ResolvedInstanceTarget;
|
|
36
|
+
/** Admin lookup failed before it could say whether the slug exists. */
|
|
37
|
+
export declare function isAdminDiscoveryFailure(error: unknown): boolean;
|
|
36
38
|
export declare function isManagedInstanceNotFound(error: unknown): boolean;
|
package/dist/types/lib/log.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Ora } from 'ora';
|
|
2
|
-
import { type
|
|
2
|
+
import { type MachineOpts } from './output';
|
|
3
3
|
export declare const log: {
|
|
4
4
|
info: (msg: string) => void;
|
|
5
5
|
success: (msg: string) => void;
|
|
@@ -8,10 +8,11 @@ export declare const log: {
|
|
|
8
8
|
step: (msg: string) => void;
|
|
9
9
|
dim: (msg: string) => void;
|
|
10
10
|
};
|
|
11
|
-
/** Report an error with hint (when present) and exit.
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
/** Report an error with hint (when present) and exit. `--json` / `--ci` / a
|
|
12
|
+
* non-TTY stdout always get one structured JSON line on stderr. */
|
|
13
|
+
export declare function fatal(e: unknown, opts?: MachineOpts): never;
|
|
14
|
+
/** Admit expected invalid input and exit through {@link fatal}. */
|
|
15
|
+
export declare function failClosed(error: unknown, opts?: MachineOpts): never;
|
|
15
16
|
/** Shortcut for stub commands that aren't wired in v1 (§15). */
|
|
16
17
|
export declare function fatalNotImplemented(feature: string, hint?: string): never;
|
|
17
18
|
/**
|
|
@@ -6,6 +6,9 @@ export type OutputOpts = {
|
|
|
6
6
|
format?: 'yaml' | 'json';
|
|
7
7
|
};
|
|
8
8
|
export type RawOutputOpts = Pick<OutputOpts, 'raw' | 'json'>;
|
|
9
|
+
export type MachineOpts = RawOutputOpts & {
|
|
10
|
+
readonly ci?: boolean;
|
|
11
|
+
};
|
|
9
12
|
export declare const RAW_OUTPUT_OPTIONS: readonly [{
|
|
10
13
|
readonly flags: '--json';
|
|
11
14
|
readonly description: 'Always-valid JSON (for jq)';
|
|
@@ -15,9 +18,10 @@ export declare const RAW_OUTPUT_OPTIONS: readonly [{
|
|
|
15
18
|
}];
|
|
16
19
|
/**
|
|
17
20
|
* Is the consumer a machine (emit structured data, not a pretty view)?
|
|
18
|
-
* True for `--json`, `--raw`,
|
|
21
|
+
* True for `--json`, `--raw`, `--ci`, a process-wide `--ci/--json/--raw` on argv,
|
|
22
|
+
* or any non-TTY stdout (pipe, redirect, agent).
|
|
19
23
|
*/
|
|
20
|
-
export declare function isMachine(opts?:
|
|
24
|
+
export declare function isMachine(opts?: MachineOpts): boolean;
|
|
21
25
|
/**
|
|
22
26
|
* `--raw` = the *unwrapped* value (bare scalar, raw bytes). The raw-vs-json
|
|
23
27
|
* distinction only manifests for scalars and binary; objects/arrays fall back
|
package/package.json
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { parseIntrospectTarget } from '../introspect'
|
|
4
|
+
|
|
5
|
+
describe('parseIntrospectTarget', () => {
|
|
6
|
+
test('accepts a bare origin', () => {
|
|
7
|
+
const parsed = parseIntrospectTarget('host.astrale.ai')
|
|
8
|
+
expect(parsed.origin).toBe('host.astrale.ai')
|
|
9
|
+
expect(parsed.path.ast.steps).toEqual([])
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('accepts a Domain-rooted method Path', () => {
|
|
13
|
+
const parsed = parseIntrospectTarget('/:host.astrale.ai:class.Manager:createInstance')
|
|
14
|
+
expect(parsed.origin).toBe('host.astrale.ai')
|
|
15
|
+
expect(parsed.path.ast.steps.at(-1)?.kind).toBe('method')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('rejects an @id', () => {
|
|
19
|
+
expect(() => parseIntrospectTarget('@abc')).toThrow('not an @id')
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -29,6 +29,11 @@ describe('buildJournalInput', () => {
|
|
|
29
29
|
expect(buildJournalInput({})).toEqual({ limit: 200 })
|
|
30
30
|
expect(() => buildJournalInput({ limit: '0' })).toThrow('--limit')
|
|
31
31
|
expect(() => buildJournalInput({ limit: 'all' })).toThrow('--limit')
|
|
32
|
+
expect(() => buildJournalInput({ since: 'not-a-date' })).toThrow('--since')
|
|
33
|
+
expect(() =>
|
|
34
|
+
buildJournalInput({ since: '2026-01-01T00:00:00Z', until: '1999-01-01T00:00:00Z' }),
|
|
35
|
+
).toThrow('--since')
|
|
36
|
+
expect(() => buildJournalInput({ cursor: 'junk' })).toThrow('--cursor')
|
|
32
37
|
})
|
|
33
38
|
})
|
|
34
39
|
|
|
@@ -27,6 +27,7 @@ class ExitError extends Error {
|
|
|
27
27
|
let stdout = ''
|
|
28
28
|
let errors: string[] = []
|
|
29
29
|
let originalStdoutWrite: typeof process.stdout.write
|
|
30
|
+
let originalStderrWrite: typeof process.stderr.write
|
|
30
31
|
let originalConsoleError: typeof console.error
|
|
31
32
|
let originalExit: typeof process.exit
|
|
32
33
|
let queryCalls: Array<{ ast: unknown; options: unknown }> = []
|
|
@@ -95,12 +96,17 @@ beforeEach(() => {
|
|
|
95
96
|
runKernelCommandMock.mockClear()
|
|
96
97
|
|
|
97
98
|
originalStdoutWrite = process.stdout.write
|
|
99
|
+
originalStderrWrite = process.stderr.write
|
|
98
100
|
originalConsoleError = console.error
|
|
99
101
|
originalExit = process.exit
|
|
100
102
|
process.stdout.write = ((chunk: string | Uint8Array) => {
|
|
101
103
|
stdout += typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8')
|
|
102
104
|
return true
|
|
103
105
|
}) as typeof process.stdout.write
|
|
106
|
+
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
107
|
+
errors.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8'))
|
|
108
|
+
return true
|
|
109
|
+
}) as typeof process.stderr.write
|
|
104
110
|
console.error = ((...values: unknown[]) => {
|
|
105
111
|
errors.push(values.map(String).join(' '))
|
|
106
112
|
}) as typeof console.error
|
|
@@ -111,6 +117,7 @@ beforeEach(() => {
|
|
|
111
117
|
|
|
112
118
|
afterEach(() => {
|
|
113
119
|
process.stdout.write = originalStdoutWrite
|
|
120
|
+
process.stderr.write = originalStderrWrite
|
|
114
121
|
console.error = originalConsoleError
|
|
115
122
|
process.exit = originalExit
|
|
116
123
|
})
|
|
@@ -195,7 +202,10 @@ describe('query command', () => {
|
|
|
195
202
|
).rejects.toEqual(new ExitError(1))
|
|
196
203
|
|
|
197
204
|
expect(runKernelCommandMock).not.toHaveBeenCalled()
|
|
198
|
-
expect(errors.join('\n')).
|
|
205
|
+
expect(JSON.parse(errors.join('\n'))).toMatchObject({
|
|
206
|
+
error: 'INVALID_INPUT',
|
|
207
|
+
message: 'Invalid input: expected "astrale.graph.query" at /format.',
|
|
208
|
+
})
|
|
199
209
|
})
|
|
200
210
|
})
|
|
201
211
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { AstraleError } from '../../errors'
|
|
4
|
+
import { parseTtl } from '../token'
|
|
5
|
+
|
|
6
|
+
describe('parseTtl', () => {
|
|
7
|
+
test('defaults to 3600 seconds', () => {
|
|
8
|
+
expect(parseTtl(undefined)).toBe(3600)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('rejects non-positive and non-integer values', () => {
|
|
12
|
+
expect(() => parseTtl('abc')).toThrow(AstraleError)
|
|
13
|
+
expect(() => parseTtl('0')).toThrow(AstraleError)
|
|
14
|
+
expect(() => parseTtl('-5')).toThrow(AstraleError)
|
|
15
|
+
expect(() => parseTtl('1.5')).toThrow(AstraleError)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('admits a positive integer', () => {
|
|
19
|
+
expect(parseTtl('90')).toBe(90)
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { CommandDefinition } from '../../program/index'
|
|
2
2
|
|
|
3
|
+
import { AstraleError } from '../../errors'
|
|
3
4
|
import { getDefault, getIdentity, readIdentities } from '../../identity/index'
|
|
4
5
|
import { isSessionExpired, readIdpSession, type IdpSession } from '../../lib/idp'
|
|
5
6
|
import { ensureFreshSession } from '../../lib/idp-session'
|
|
6
|
-
import { log } from '../../lib/log'
|
|
7
|
+
import { fatal, log } from '../../lib/log'
|
|
7
8
|
import { output } from '../../lib/output'
|
|
8
9
|
|
|
9
10
|
type AuthTokenType = 'access' | 'id'
|
|
@@ -66,7 +67,12 @@ Examples:
|
|
|
66
67
|
$ astrale auth token --name alice --type id --json
|
|
67
68
|
`,
|
|
68
69
|
action: async (opts: AuthTokenOpts) => {
|
|
69
|
-
|
|
70
|
+
let result: AuthTokenResult
|
|
71
|
+
try {
|
|
72
|
+
result = await resolveAuthToken(opts)
|
|
73
|
+
} catch (error) {
|
|
74
|
+
fatal(error, opts)
|
|
75
|
+
}
|
|
70
76
|
|
|
71
77
|
if (opts.raw) {
|
|
72
78
|
process.stdout.write(result.token + '\n')
|
|
@@ -118,10 +124,11 @@ async function resolveIdentityName(opts: AuthTokenOpts): Promise<string> {
|
|
|
118
124
|
if (opts.name) {
|
|
119
125
|
const identity = await getIdentity(opts.name)
|
|
120
126
|
if ((identity.source ?? 'key') !== 'idp') {
|
|
121
|
-
throw new
|
|
127
|
+
throw new AstraleError('AUTH_ERROR', `Identity "${opts.name}" is not IdP-backed`)
|
|
122
128
|
}
|
|
123
129
|
if (opts.idp && identity.idp !== opts.idp) {
|
|
124
|
-
throw new
|
|
130
|
+
throw new AstraleError(
|
|
131
|
+
'AUTH_ERROR',
|
|
125
132
|
`Identity "${opts.name}" is backed by IdP "${identity.idp ?? '?'}", not "${opts.idp}"`,
|
|
126
133
|
)
|
|
127
134
|
}
|
|
@@ -135,20 +142,25 @@ async function resolveIdentityName(opts: AuthTokenOpts): Promise<string> {
|
|
|
135
142
|
.map(([name]) => name)
|
|
136
143
|
|
|
137
144
|
if (matches.length === 0) {
|
|
138
|
-
throw new
|
|
145
|
+
throw new AstraleError(
|
|
146
|
+
'AUTH_ERROR',
|
|
139
147
|
`No IdP-backed identities found for IdP "${opts.idp}". Run: astrale auth login --idp ${opts.idp}`,
|
|
140
148
|
)
|
|
141
149
|
}
|
|
142
150
|
if (matches.includes(store.default)) return store.default
|
|
143
151
|
if (matches.length === 1) return matches[0]
|
|
144
|
-
throw new
|
|
152
|
+
throw new AstraleError(
|
|
153
|
+
'AUTH_ERROR',
|
|
145
154
|
`Multiple IdP-backed identities found for IdP "${opts.idp}": ${matches.join(', ')}. Pass --name.`,
|
|
146
155
|
)
|
|
147
156
|
}
|
|
148
157
|
|
|
149
158
|
const identity = await getDefault()
|
|
150
159
|
if ((identity.source ?? 'key') !== 'idp') {
|
|
151
|
-
throw new
|
|
160
|
+
throw new AstraleError(
|
|
161
|
+
'AUTH_ERROR',
|
|
162
|
+
'Default identity is not IdP-backed. Pass --name or --idp.',
|
|
163
|
+
)
|
|
152
164
|
}
|
|
153
165
|
return identity.name
|
|
154
166
|
}
|
package/src/commands/call.ts
CHANGED
|
@@ -4,15 +4,12 @@ import type { ConnectionContext, KernelCommandOpts } from '../connection'
|
|
|
4
4
|
import type { CommandDefinition } from '../program/index'
|
|
5
5
|
|
|
6
6
|
import { createPathCall, expandSelfInCall, runKernelCommand, withSelfHint } from '../connection'
|
|
7
|
-
import { nodeProperty } from '../graph/index'
|
|
8
7
|
import { presentBinary } from '../lib/binary'
|
|
9
|
-
import { log } from '../lib/log'
|
|
8
|
+
import { failClosed, log } from '../lib/log'
|
|
10
9
|
import { output, present } from '../lib/output'
|
|
11
|
-
import { describeCallableFromSchema, missingCallableDescription } from './call-describe'
|
|
12
10
|
|
|
13
11
|
type CallOpts = KernelCommandOpts & {
|
|
14
12
|
data?: string
|
|
15
|
-
describe?: boolean
|
|
16
13
|
dryRun?: boolean
|
|
17
14
|
output?: string
|
|
18
15
|
}
|
|
@@ -32,15 +29,13 @@ export async function callCommand(
|
|
|
32
29
|
try {
|
|
33
30
|
expanded = await expandSelfInCall(path, rawParams, opts)
|
|
34
31
|
} catch (error) {
|
|
35
|
-
|
|
36
|
-
process.exit(1)
|
|
32
|
+
failClosed(error, opts)
|
|
37
33
|
}
|
|
38
34
|
const expandedPath = expanded.path
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return describeOperation(expandedPath, opts)
|
|
35
|
+
try {
|
|
36
|
+
Path.parse(expandedPath)
|
|
37
|
+
} catch (error) {
|
|
38
|
+
failClosed(error, opts)
|
|
44
39
|
}
|
|
45
40
|
|
|
46
41
|
// ── Parse params ────────────────────────────────────────
|
|
@@ -48,8 +43,7 @@ export async function callCommand(
|
|
|
48
43
|
try {
|
|
49
44
|
params = await parseParams([...expanded.parameters], opts.data)
|
|
50
45
|
} catch (e) {
|
|
51
|
-
|
|
52
|
-
process.exit(1)
|
|
46
|
+
failClosed(e, opts)
|
|
53
47
|
}
|
|
54
48
|
|
|
55
49
|
// ── Dry-run: show what would be sent ─────────────────────
|
|
@@ -94,24 +88,6 @@ export async function materializeCallResult(result: CallResult): Promise<Materia
|
|
|
94
88
|
return Object.freeze({ kind: 'stream', values: Object.freeze(values) })
|
|
95
89
|
}
|
|
96
90
|
|
|
97
|
-
async function describeOperation(path: string, opts: CallOpts): Promise<void> {
|
|
98
|
-
await runKernelCommand({
|
|
99
|
-
opts,
|
|
100
|
-
label: `Schema for ${path}`,
|
|
101
|
-
fn: async ({ graph }) => {
|
|
102
|
-
const parsed = Path.parse(path)
|
|
103
|
-
if (parsed.ast.anchor.kind !== 'domain') {
|
|
104
|
-
throw missingCallableDescription(path)
|
|
105
|
-
}
|
|
106
|
-
const domain = await graph.getOrThrow(Path.parse(`/:${parsed.ast.anchor.origin}`))
|
|
107
|
-
const described = describeCallableFromSchema(parsed, nodeProperty(domain, 'schema'))
|
|
108
|
-
if (described === undefined) throw missingCallableDescription(path)
|
|
109
|
-
return described
|
|
110
|
-
},
|
|
111
|
-
format: (schema, fmtOpts) => output(schema, fmtOpts),
|
|
112
|
-
})
|
|
113
|
-
}
|
|
114
|
-
|
|
115
91
|
// ── Param parsing ───────────────────────────────────────────
|
|
116
92
|
|
|
117
93
|
export async function parseParams(
|
|
@@ -129,6 +105,10 @@ export async function parseParams(
|
|
|
129
105
|
}
|
|
130
106
|
}
|
|
131
107
|
|
|
108
|
+
if (rawParams.length > 0) {
|
|
109
|
+
return parseKeyValue(rawParams)
|
|
110
|
+
}
|
|
111
|
+
|
|
132
112
|
const stdin = await readStdin()
|
|
133
113
|
if (stdin) {
|
|
134
114
|
try {
|
|
@@ -138,10 +118,6 @@ export async function parseParams(
|
|
|
138
118
|
}
|
|
139
119
|
}
|
|
140
120
|
|
|
141
|
-
if (rawParams.length > 0) {
|
|
142
|
-
return parseKeyValue(rawParams)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
121
|
return {}
|
|
146
122
|
}
|
|
147
123
|
|
|
@@ -201,11 +177,12 @@ export default {
|
|
|
201
177
|
description: 'Call a kernel operation',
|
|
202
178
|
afterHelpText: `
|
|
203
179
|
Behavior:
|
|
204
|
-
Param priority (highest wins): --data >
|
|
180
|
+
Param priority (highest wins): --data > key=value > stdin > {}. If
|
|
205
181
|
both --data and key=value are given, key=value is ignored (warned).
|
|
206
|
-
Stdin is read only when piped
|
|
207
|
-
--dry-run
|
|
208
|
-
|
|
182
|
+
Stdin is read only when piped and no --data/key=value is present
|
|
183
|
+
(ignored on a TTY). --dry-run admits the Path and prints the call
|
|
184
|
+
input without executing. Remote-bound functions auto-mint a
|
|
185
|
+
worker-scoped credential; --creds overrides it.
|
|
209
186
|
|
|
210
187
|
Self-reference:
|
|
211
188
|
@self expands to your nodeId on the active instance (path head or
|
|
@@ -214,11 +191,10 @@ Self-reference:
|
|
|
214
191
|
(e.g. via 'astrale get @self --json'). Resolution authenticates to
|
|
215
192
|
the selected Kernel and never trusts a local registration or JWT sub.
|
|
216
193
|
|
|
217
|
-
|
|
218
|
-
schema. Method Paths are not Function nodes.
|
|
194
|
+
Callable input/output lives on astrale introspect <path>.
|
|
219
195
|
|
|
220
196
|
Examples:
|
|
221
|
-
$ astrale
|
|
197
|
+
$ astrale introspect /:host.astrale.ai:class.Manager:createInstance
|
|
222
198
|
$ astrale call /:blog.acme.com:class.Author:list limit=10
|
|
223
199
|
$ astrale call '@self::deactivate'
|
|
224
200
|
$ astrale call /:kernel.astrale.ai:function.journal --data '{"limit":5}' --json
|
|
@@ -234,7 +210,6 @@ Examples:
|
|
|
234
210
|
options: [
|
|
235
211
|
{ flags: '-d, --data <json>', description: 'Params as JSON string' },
|
|
236
212
|
{ flags: '-o, --output <file>', description: 'Write binary/raw output to a file' },
|
|
237
|
-
{ flags: '--describe', description: 'Show operation schema without executing' },
|
|
238
213
|
{ flags: '--dry-run', description: 'Show what would be sent without executing' },
|
|
239
214
|
],
|
|
240
215
|
action: async (path, params, opts) => {
|
package/src/commands/get.ts
CHANGED
|
@@ -5,26 +5,40 @@ import type { CommandDefinition } from '../program/index'
|
|
|
5
5
|
|
|
6
6
|
import { expandSelfInPath, runKernelCommand, withSelfHint } from '../connection'
|
|
7
7
|
import { formatKernelError } from '../connection/errors'
|
|
8
|
-
import {
|
|
8
|
+
import { AstraleError } from '../errors'
|
|
9
|
+
import { denoise, isMachine, output } from '../lib/output'
|
|
9
10
|
|
|
10
|
-
type GetOpts = KernelCommandOpts
|
|
11
|
+
type GetOpts = KernelCommandOpts & { schema?: boolean }
|
|
11
12
|
|
|
12
13
|
/** Read one exact canonical Node. The caller's Path is not fabricated into the Node value. */
|
|
13
14
|
export async function getCommand(target: string, opts: GetOpts): Promise<void> {
|
|
14
15
|
let path: string
|
|
15
16
|
let meta
|
|
17
|
+
let parsed
|
|
16
18
|
try {
|
|
17
19
|
;({ path, meta } = await expandSelfInPath(target, opts))
|
|
20
|
+
parsed = Path.parse(path)
|
|
18
21
|
} catch (error) {
|
|
19
22
|
await formatKernelError(error, isMachine(opts), undefined, opts.debug)
|
|
20
23
|
process.exit(1)
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
const last = parsed.ast.steps.at(-1)
|
|
27
|
+
if (last?.kind === 'method') {
|
|
28
|
+
const error = new AstraleError(
|
|
29
|
+
'NOT_A_NODE',
|
|
30
|
+
`${path} is a callable Path, not a Node.`,
|
|
31
|
+
`Use \`astrale call ${path}\` or \`astrale introspect ${path}\`.`,
|
|
32
|
+
)
|
|
33
|
+
await formatKernelError(error, isMachine(opts), undefined, opts.debug)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
await runKernelCommand({
|
|
24
38
|
opts,
|
|
25
39
|
label: `Node ${path}`,
|
|
26
|
-
fn: ({ graph }) => withSelfHint(() => graph.getOrThrow(
|
|
27
|
-
format: (node, format) => output(node, format),
|
|
40
|
+
fn: ({ graph }) => withSelfHint(() => graph.getOrThrow(parsed), meta),
|
|
41
|
+
format: (node, format) => output(opts.schema === true ? node : denoise(node), format),
|
|
28
42
|
})
|
|
29
43
|
}
|
|
30
44
|
|
|
@@ -38,14 +52,18 @@ Behavior:
|
|
|
38
52
|
intentionally indistinguishable. @self is expanded before dispatch.
|
|
39
53
|
|
|
40
54
|
A Node does not contain a synthetic path, labels, or backend class ID.
|
|
41
|
-
|
|
55
|
+
Method Paths are not Nodes — use call / introspect. Schema-valued
|
|
56
|
+
properties are omitted unless --schema is passed. Use query for
|
|
57
|
+
graph-shaped reads.
|
|
42
58
|
|
|
43
59
|
Examples:
|
|
44
60
|
$ astrale get /:notes.example.dev:class.Note
|
|
45
61
|
$ astrale get @abc123 --json
|
|
46
62
|
$ astrale get @self
|
|
63
|
+
$ astrale get /:host.astrale.ai --schema
|
|
47
64
|
`,
|
|
48
65
|
arguments: [{ name: 'target', description: 'Canonical Node Path or @id' }],
|
|
66
|
+
options: [{ flags: '--schema', description: 'Include schema-valued properties' }],
|
|
49
67
|
action: async (target, opts) => {
|
|
50
68
|
await getCommand(target as string, opts as GetOpts)
|
|
51
69
|
},
|
|
@@ -2,6 +2,7 @@ import type { CommandDefinition } from '../../program/index'
|
|
|
2
2
|
|
|
3
3
|
import { createIdentity } from '../../identity/index'
|
|
4
4
|
import { fatal, log } from '../../lib/log'
|
|
5
|
+
import { isMachine, output, RAW_OUTPUT_OPTIONS, type RawOutputOpts } from '../../lib/output'
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
name: 'create',
|
|
@@ -11,16 +12,24 @@ export default {
|
|
|
11
12
|
{ flags: '--subject <sub>', description: 'Custom subject (defaults to name)' },
|
|
12
13
|
{ flags: '--local', description: 'Local-only identity (default)' },
|
|
13
14
|
{ flags: '--remote', description: 'Remote (cloud-synced) identity — requires cloud login' },
|
|
15
|
+
...RAW_OUTPUT_OPTIONS,
|
|
14
16
|
],
|
|
15
|
-
action: async (
|
|
17
|
+
action: async (
|
|
18
|
+
name: string,
|
|
19
|
+
opts: { subject?: string; local?: boolean; remote?: boolean } & RawOutputOpts,
|
|
20
|
+
) => {
|
|
16
21
|
try {
|
|
17
22
|
const mode = opts.remote ? 'remote' : 'local'
|
|
18
23
|
const identity = await createIdentity(name, { subject: opts.subject, mode })
|
|
24
|
+
if (isMachine(opts)) {
|
|
25
|
+
output({ name, ...identity }, opts)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
19
28
|
log.success(
|
|
20
29
|
`Created identity "${name}" (subject: ${identity.subject}, mode: ${identity.mode})`,
|
|
21
30
|
)
|
|
22
31
|
} catch (e) {
|
|
23
|
-
fatal(e)
|
|
32
|
+
fatal(e, opts)
|
|
24
33
|
}
|
|
25
34
|
},
|
|
26
35
|
} satisfies CommandDefinition
|
|
@@ -2,17 +2,23 @@ import type { CommandDefinition } from '../../program/index'
|
|
|
2
2
|
|
|
3
3
|
import { deleteIdentity } from '../../identity/index'
|
|
4
4
|
import { fatal, log } from '../../lib/log'
|
|
5
|
+
import { isMachine, output, RAW_OUTPUT_OPTIONS, type RawOutputOpts } from '../../lib/output'
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
name: 'delete',
|
|
8
9
|
description: 'Delete an identity',
|
|
9
10
|
arguments: [{ name: 'name', description: 'Identity name', required: true }],
|
|
10
|
-
|
|
11
|
+
options: [...RAW_OUTPUT_OPTIONS],
|
|
12
|
+
action: async (name: string, opts: RawOutputOpts) => {
|
|
11
13
|
try {
|
|
12
14
|
await deleteIdentity(name)
|
|
15
|
+
if (isMachine(opts)) {
|
|
16
|
+
output({ deleted: name }, opts)
|
|
17
|
+
return
|
|
18
|
+
}
|
|
13
19
|
log.success(`Deleted identity "${name}"`)
|
|
14
20
|
} catch (e) {
|
|
15
|
-
fatal(e)
|
|
21
|
+
fatal(e, opts)
|
|
16
22
|
}
|
|
17
23
|
},
|
|
18
24
|
} satisfies CommandDefinition
|
|
@@ -2,6 +2,7 @@ import type { CommandDefinition } from '../../program/index'
|
|
|
2
2
|
|
|
3
3
|
import { encodeIdentityExport, exportIdentity, writeIdentityExport } from '../../identity/index'
|
|
4
4
|
import { fatal, log } from '../../lib/log'
|
|
5
|
+
import { isMachine, output, RAW_OUTPUT_OPTIONS, type RawOutputOpts } from '../../lib/output'
|
|
5
6
|
import { readPassphrase } from '../../lib/prompt'
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -18,8 +19,9 @@ export default {
|
|
|
18
19
|
],
|
|
19
20
|
options: [
|
|
20
21
|
{ flags: '--encrypt', description: 'Encrypt the envelope with a passphrase (JOSE JWE)' },
|
|
22
|
+
...RAW_OUTPUT_OPTIONS,
|
|
21
23
|
],
|
|
22
|
-
action: async (name: string, path: string, opts: { encrypt?: boolean }) => {
|
|
24
|
+
action: async (name: string, path: string, opts: { encrypt?: boolean } & RawOutputOpts) => {
|
|
23
25
|
try {
|
|
24
26
|
const envelope = await exportIdentity(name)
|
|
25
27
|
const passphrase = opts.encrypt
|
|
@@ -27,6 +29,10 @@ export default {
|
|
|
27
29
|
: undefined
|
|
28
30
|
await writeIdentityExport(path, await encodeIdentityExport(envelope, passphrase))
|
|
29
31
|
|
|
32
|
+
if (isMachine(opts)) {
|
|
33
|
+
output({ name, path, encrypted: Boolean(opts.encrypt) }, opts)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
30
36
|
log.success(`Exported identity "${name}" → ${path}${opts.encrypt ? ' (encrypted)' : ''}`)
|
|
31
37
|
if (!opts.encrypt) {
|
|
32
38
|
log.warn(
|
|
@@ -34,7 +40,7 @@ export default {
|
|
|
34
40
|
)
|
|
35
41
|
}
|
|
36
42
|
} catch (e) {
|
|
37
|
-
fatal(e)
|
|
43
|
+
fatal(e, opts)
|
|
38
44
|
}
|
|
39
45
|
},
|
|
40
46
|
} satisfies CommandDefinition
|