@astrale-os/cli 0.8.1-alpha.3 → 0.8.1-alpha.4
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 +15 -114
- package/package.json +1 -1
- package/src/commands/__tests__/read-commands.test.ts +0 -12
- package/src/commands/domain/list.ts +1 -1
- package/src/lib/__tests__/command-dx.test.ts +13 -1
- package/src/lib/command-dx.ts +12 -0
- package/src/program/__tests__/program.test.ts +3 -3
- package/src/program/build.ts +1 -3
- package/src/telemetry/__tests__/gate.test.ts +1 -1
- package/src/commands/__tests__/ls.test.ts +0 -29
- package/src/commands/ls.ts +0 -125
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ Main command groups:
|
|
|
110
110
|
|
|
111
111
|
| Group | What it covers |
|
|
112
112
|
|-------|----------------|
|
|
113
|
-
| Kernel | `
|
|
113
|
+
| Kernel | `get`, `call`, `query`, `describe`, `token` |
|
|
114
114
|
| Context | `status`, `whoami`, `use` |
|
|
115
115
|
| Management | `admin`, `instance`, `identity`, `auth`, `idp`, `update` |
|
|
116
116
|
| Agent | `browser` |
|
package/dist/astrale.js
CHANGED
|
@@ -2576,7 +2576,7 @@ var package_default;
|
|
|
2576
2576
|
var init_package = __esm(() => {
|
|
2577
2577
|
package_default = {
|
|
2578
2578
|
name: "@astrale-os/cli",
|
|
2579
|
-
version: "0.8.1-alpha.
|
|
2579
|
+
version: "0.8.1-alpha.4",
|
|
2580
2580
|
description: "Astrale CLI — connect to existing Astrale kernels",
|
|
2581
2581
|
keywords: [
|
|
2582
2582
|
"astrale",
|
|
@@ -90852,115 +90852,6 @@ var init_class3 = __esm(() => {
|
|
|
90852
90852
|
init_class();
|
|
90853
90853
|
});
|
|
90854
90854
|
|
|
90855
|
-
// src/commands/ls.ts
|
|
90856
|
-
var exports_ls = {};
|
|
90857
|
-
__export(exports_ls, {
|
|
90858
|
-
lsCommand: () => lsCommand,
|
|
90859
|
-
listProjection: () => listProjection,
|
|
90860
|
-
displayName: () => displayName,
|
|
90861
|
-
default: () => ls_default
|
|
90862
|
-
});
|
|
90863
|
-
async function lsCommand(source2, opts) {
|
|
90864
|
-
if (opts.edge === undefined) {
|
|
90865
|
-
log.error("ls requires --edge <class>; Kernel V2 has no universal child relation");
|
|
90866
|
-
process.exit(1);
|
|
90867
|
-
}
|
|
90868
|
-
let path5;
|
|
90869
|
-
let meta3;
|
|
90870
|
-
let prepared;
|
|
90871
|
-
try {
|
|
90872
|
-
({ path: path5, meta: meta3 } = await expandSelfInPath(source2, opts));
|
|
90873
|
-
prepared = prepareQuery({
|
|
90874
|
-
sources: [path5],
|
|
90875
|
-
edge: opts.edge,
|
|
90876
|
-
direction: opts.direction,
|
|
90877
|
-
limit: opts.limit,
|
|
90878
|
-
cursor: opts.cursor
|
|
90879
|
-
});
|
|
90880
|
-
} catch (error52) {
|
|
90881
|
-
log.error(error52 instanceof Error ? error52.message : "Invalid edge neighborhood");
|
|
90882
|
-
process.exit(1);
|
|
90883
|
-
}
|
|
90884
|
-
await runKernelCommand({
|
|
90885
|
-
opts,
|
|
90886
|
-
label: `Neighbors of ${path5}`,
|
|
90887
|
-
fn: ({ graph }) => withSelfHint(() => graph.query(prepared.ast, { page: prepared.page }), meta3),
|
|
90888
|
-
format: (response2, format3) => {
|
|
90889
|
-
const graph = completeGraph(response2.result);
|
|
90890
|
-
presentList([...graph.nodes], { ...format3, long: opts.long, quiet: opts.quiet, count: opts.count }, listProjection);
|
|
90891
|
-
if (response2.page.next && !isMachine(format3) && !opts.quiet && !opts.count) {
|
|
90892
|
-
process.stderr.write(` cursor: ${response2.page.next}
|
|
90893
|
-
`);
|
|
90894
|
-
}
|
|
90895
|
-
}
|
|
90896
|
-
});
|
|
90897
|
-
}
|
|
90898
|
-
function completeGraph(result) {
|
|
90899
|
-
if (result.kind !== "graph")
|
|
90900
|
-
throw new TypeError("ls expected one complete graph Query result");
|
|
90901
|
-
return result.graph;
|
|
90902
|
-
}
|
|
90903
|
-
function listProjection(nodes) {
|
|
90904
|
-
return {
|
|
90905
|
-
columns: [
|
|
90906
|
-
{ key: "name", header: "NAME", color: source_default.cyan },
|
|
90907
|
-
{ key: "class", header: "CLASS", color: source_default.dim },
|
|
90908
|
-
{ key: "id", header: "ID", color: source_default.dim }
|
|
90909
|
-
],
|
|
90910
|
-
rows: nodes.map((node4) => ({
|
|
90911
|
-
name: displayName(node4),
|
|
90912
|
-
class: ClassPath.name(node4.class),
|
|
90913
|
-
id: node4.id
|
|
90914
|
-
})),
|
|
90915
|
-
paths: nodes.map((node4) => `@${node4.id}`)
|
|
90916
|
-
};
|
|
90917
|
-
}
|
|
90918
|
-
function displayName(node4) {
|
|
90919
|
-
const value2 = nodeProperty(node4, "name") ?? nodeProperty(node4, "title") ?? nodeProperty(node4, "slug");
|
|
90920
|
-
return typeof value2 === "string" && value2.length > 0 ? value2 : `@${node4.id}`;
|
|
90921
|
-
}
|
|
90922
|
-
var ls_default;
|
|
90923
|
-
var init_ls = __esm(() => {
|
|
90924
|
-
init_class3();
|
|
90925
|
-
init_source();
|
|
90926
|
-
init_connection2();
|
|
90927
|
-
init_graph6();
|
|
90928
|
-
init_log();
|
|
90929
|
-
init_output();
|
|
90930
|
-
ls_default = {
|
|
90931
|
-
name: "ls",
|
|
90932
|
-
description: "List one finite exact edge neighborhood",
|
|
90933
|
-
afterHelpText: `
|
|
90934
|
-
Behavior:
|
|
90935
|
-
Lists Nodes reached from one source through one exact Edge Class. Kernel V2
|
|
90936
|
-
has no universal parent/child relation, so --edge is required and recursive
|
|
90937
|
-
tree walking is intentionally absent. Direction defaults to outgoing and the
|
|
90938
|
-
finite default limit is 100. -q emits one @id per line.
|
|
90939
|
-
|
|
90940
|
-
Examples:
|
|
90941
|
-
$ astrale ls @note --edge /:notes.example.dev:class.references
|
|
90942
|
-
$ astrale ls @note --edge /:notes.example.dev:class.references --direction incoming --limit 25
|
|
90943
|
-
`,
|
|
90944
|
-
arguments: [{ name: "source", description: "Canonical source Path or @id" }],
|
|
90945
|
-
options: [
|
|
90946
|
-
{ flags: "--edge <class>", description: "Exact Edge Class to traverse" },
|
|
90947
|
-
{
|
|
90948
|
-
flags: "--direction <direction>",
|
|
90949
|
-
description: "Traversal direction (default: outgoing)",
|
|
90950
|
-
choices: ["outgoing", "incoming", "incident"]
|
|
90951
|
-
},
|
|
90952
|
-
{ flags: "--limit <n>", description: "Finite selected Node limit (default: 100)" },
|
|
90953
|
-
{ flags: "--cursor <token>", description: "Resume one live query scope" },
|
|
90954
|
-
{ flags: "-l, --long", description: "Print complete canonical Nodes" },
|
|
90955
|
-
{ flags: "-q, --quiet", description: "Print one @id per line" },
|
|
90956
|
-
{ flags: "--count", description: "Print only the number of returned Nodes" }
|
|
90957
|
-
],
|
|
90958
|
-
action: async (source2, opts) => {
|
|
90959
|
-
await lsCommand(source2, opts);
|
|
90960
|
-
}
|
|
90961
|
-
};
|
|
90962
|
-
});
|
|
90963
|
-
|
|
90964
90855
|
// src/commands/describe.ts
|
|
90965
90856
|
var exports_describe = {};
|
|
90966
90857
|
__export(exports_describe, {
|
|
@@ -93569,7 +93460,7 @@ Behavior:
|
|
|
93569
93460
|
Reads the admin catalog — every domain that has been \`publish\`ed
|
|
93570
93461
|
(origin → published worker URL). Listing only shows what is INSTALLABLE;
|
|
93571
93462
|
what is actually mounted where lives on each instance's own graph
|
|
93572
|
-
(\`astrale
|
|
93463
|
+
(\`astrale query\` against that instance).
|
|
93573
93464
|
|
|
93574
93465
|
Default output is a NAME/ORIGIN/URL/DEFAULT table on a TTY, JSON when piped
|
|
93575
93466
|
or with --json/--raw (agent-friendly — full DomainInfo objects). -q prints
|
|
@@ -95904,9 +95795,21 @@ function renderCommanderError(program2, error, argv = process.argv.slice(2)) {
|
|
|
95904
95795
|
].join(`
|
|
95905
95796
|
`);
|
|
95906
95797
|
}
|
|
95798
|
+
var RETIRED_COMMANDS = {
|
|
95799
|
+
ls: "astrale query <source> --edge <class>"
|
|
95800
|
+
};
|
|
95907
95801
|
function renderUnknownCommand(tokens, catalog) {
|
|
95908
95802
|
const command = tokens.join(" ");
|
|
95909
95803
|
const first = tokens[0];
|
|
95804
|
+
const retired = first === undefined ? undefined : RETIRED_COMMANDS[first];
|
|
95805
|
+
if (retired !== undefined) {
|
|
95806
|
+
return [
|
|
95807
|
+
`Unknown command: ${source_default.bold(`astrale ${command}`)}`,
|
|
95808
|
+
"",
|
|
95809
|
+
`\`astrale ${first}\` was removed. Use ${source_default.bold(retired)} instead.`
|
|
95810
|
+
].join(`
|
|
95811
|
+
`);
|
|
95812
|
+
}
|
|
95910
95813
|
const namespaceMatches = catalog.filter((entry) => entry.path.at(-1) === first);
|
|
95911
95814
|
if (namespaceMatches.length > 0) {
|
|
95912
95815
|
return [
|
|
@@ -96106,7 +96009,6 @@ async function buildProgram() {
|
|
|
96106
96009
|
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_token(), exports_token))).default));
|
|
96107
96010
|
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_get(), exports_get))).default));
|
|
96108
96011
|
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_mutate4(), exports_mutate))).default));
|
|
96109
|
-
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_ls(), exports_ls))).default));
|
|
96110
96012
|
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_describe(), exports_describe))).default));
|
|
96111
96013
|
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_query7(), exports_query))).default));
|
|
96112
96014
|
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_logs(), exports_logs))).default));
|
|
@@ -96194,7 +96096,7 @@ async function buildProgram() {
|
|
|
96194
96096
|
program3.addHelpText("after", `
|
|
96195
96097
|
Command groups:
|
|
96196
96098
|
Getting started setup (sign in, pick an instance, equip your workspace)
|
|
96197
|
-
Kernel
|
|
96099
|
+
Kernel get, mutate, call, query, describe, token
|
|
96198
96100
|
Management admin, instance, domain, identity, auth, idp, update
|
|
96199
96101
|
Agent browser (drive the GUI via agent-browser)
|
|
96200
96102
|
Studio studio (launch the local Domain Studio GUI for a workspace)
|
|
@@ -96208,7 +96110,6 @@ Path syntax:
|
|
|
96208
96110
|
@nodeId::method Instance method on a node by UID
|
|
96209
96111
|
|
|
96210
96112
|
Examples:
|
|
96211
|
-
$ astrale ls @note --edge /:notes.example.dev:class.references
|
|
96212
96113
|
$ astrale studio
|
|
96213
96114
|
$ astrale admin status
|
|
96214
96115
|
$ astrale update --check
|
package/package.json
CHANGED
|
@@ -218,18 +218,6 @@ describe('get command', () => {
|
|
|
218
218
|
})
|
|
219
219
|
})
|
|
220
220
|
|
|
221
|
-
describe('ls command', () => {
|
|
222
|
-
/** @evidence TEST-CLI-LS-REQUIRES-EXACT-EDGE-BEFORE-CONNECTION */
|
|
223
|
-
test('rejects a generic child listing before opening the command connection', async () => {
|
|
224
|
-
const { lsCommand } = await import('../ls')
|
|
225
|
-
|
|
226
|
-
await expect(lsCommand('@node', { json: true })).rejects.toEqual(new ExitError(1))
|
|
227
|
-
|
|
228
|
-
expect(runKernelCommandMock).not.toHaveBeenCalled()
|
|
229
|
-
expect(errors.join('\n')).toContain('Kernel V2 has no universal child relation')
|
|
230
|
-
})
|
|
231
|
-
})
|
|
232
|
-
|
|
233
221
|
describe('mutate command', () => {
|
|
234
222
|
/** @evidence TEST-CLI-MUTATE-DISPATCHES-CANONICAL-V3 */
|
|
235
223
|
test('admits authoring input and dispatches one canonical Mutation V3 document', async () => {
|
|
@@ -66,7 +66,7 @@ Behavior:
|
|
|
66
66
|
Reads the admin catalog — every domain that has been \`publish\`ed
|
|
67
67
|
(origin → published worker URL). Listing only shows what is INSTALLABLE;
|
|
68
68
|
what is actually mounted where lives on each instance's own graph
|
|
69
|
-
(\`astrale
|
|
69
|
+
(\`astrale query\` against that instance).
|
|
70
70
|
|
|
71
71
|
Default output is a NAME/ORIGIN/URL/DEFAULT table on a TTY, JSON when piped
|
|
72
72
|
or with --json/--raw (agent-friendly — full DomainInfo objects). -q prints
|
|
@@ -22,8 +22,9 @@ describe('command DX suggestions', () => {
|
|
|
22
22
|
expect(usages).toContain('admin use [bookmark]')
|
|
23
23
|
expect(usages).toContain('use <name>')
|
|
24
24
|
expect(usages).toContain('update')
|
|
25
|
-
expect(usages).toContain('
|
|
25
|
+
expect(usages).toContain('query [sources...]')
|
|
26
26
|
expect(usages).toContain('status')
|
|
27
|
+
expect(usages).not.toContain('ls <source>')
|
|
27
28
|
})
|
|
28
29
|
|
|
29
30
|
test('explains shared verbs as namespaced commands, not arity errors', async () => {
|
|
@@ -42,6 +43,17 @@ describe('command DX suggestions', () => {
|
|
|
42
43
|
expect(rendered).not.toContain('too many arguments')
|
|
43
44
|
})
|
|
44
45
|
|
|
46
|
+
test('points retired ls at query', async () => {
|
|
47
|
+
const program = await buildProgram()
|
|
48
|
+
const error = new CommanderError(1, 'commander.unknownCommand', "error: unknown command 'ls'")
|
|
49
|
+
const rendered = stripAnsi(renderCommanderError(program, error, ['ls', '@note']))
|
|
50
|
+
|
|
51
|
+
expect(rendered).toContain('Unknown command: astrale ls @note')
|
|
52
|
+
expect(rendered).toContain('astrale ls` was removed')
|
|
53
|
+
expect(rendered).toContain('astrale query <source> --edge <class>')
|
|
54
|
+
expect(rendered).not.toContain('Did you mean:')
|
|
55
|
+
})
|
|
56
|
+
|
|
45
57
|
test('suggests nearest command for typo paths', async () => {
|
|
46
58
|
const program = await buildProgram()
|
|
47
59
|
const error = new CommanderError(
|
package/src/lib/command-dx.ts
CHANGED
|
@@ -72,9 +72,21 @@ export function renderCommanderError(
|
|
|
72
72
|
].join('\n')
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
const RETIRED_COMMANDS: Record<string, string> = {
|
|
76
|
+
ls: 'astrale query <source> --edge <class>',
|
|
77
|
+
}
|
|
78
|
+
|
|
75
79
|
function renderUnknownCommand(tokens: string[], catalog: CommandCatalogEntry[]): string {
|
|
76
80
|
const command = tokens.join(' ')
|
|
77
81
|
const first = tokens[0]
|
|
82
|
+
const retired = first === undefined ? undefined : RETIRED_COMMANDS[first]
|
|
83
|
+
if (retired !== undefined) {
|
|
84
|
+
return [
|
|
85
|
+
`Unknown command: ${chalk.bold(`astrale ${command}`)}`,
|
|
86
|
+
'',
|
|
87
|
+
`\`astrale ${first}\` was removed. Use ${chalk.bold(retired)} instead.`,
|
|
88
|
+
].join('\n')
|
|
89
|
+
}
|
|
78
90
|
const namespaceMatches = catalog.filter((entry) => entry.path.at(-1) === first)
|
|
79
91
|
if (namespaceMatches.length > 0) {
|
|
80
92
|
return [
|
|
@@ -171,7 +171,6 @@ describe('program composition', () => {
|
|
|
171
171
|
'instance status',
|
|
172
172
|
'instance use',
|
|
173
173
|
'logs',
|
|
174
|
-
'ls',
|
|
175
174
|
'mutate',
|
|
176
175
|
'query',
|
|
177
176
|
'session',
|
|
@@ -187,7 +186,7 @@ describe('program composition', () => {
|
|
|
187
186
|
'whoami',
|
|
188
187
|
])
|
|
189
188
|
expect(createHash('sha256').update(JSON.stringify(surface)).digest('hex')).toBe(
|
|
190
|
-
'
|
|
189
|
+
'4b053fe80827aeb40aecc13cd17c293b8701e59cb04f5ccb8185e9e255376dd1',
|
|
191
190
|
)
|
|
192
191
|
})
|
|
193
192
|
|
|
@@ -267,7 +266,7 @@ describe('help contract — connect-only command surface', () => {
|
|
|
267
266
|
const program = await buildProgram()
|
|
268
267
|
const names = allCommands(program).map((command) => command.name())
|
|
269
268
|
|
|
270
|
-
// `logs` and `domain` have managed/admin meanings, so only retired
|
|
269
|
+
// `logs` and `domain` have managed/admin meanings, so only retired verbs are absent.
|
|
271
270
|
for (const removed of [
|
|
272
271
|
'init',
|
|
273
272
|
'start',
|
|
@@ -279,6 +278,7 @@ describe('help contract — connect-only command surface', () => {
|
|
|
279
278
|
'graph',
|
|
280
279
|
'server',
|
|
281
280
|
'env',
|
|
281
|
+
'ls',
|
|
282
282
|
]) {
|
|
283
283
|
expect(names).not.toContain(removed)
|
|
284
284
|
}
|
package/src/program/build.ts
CHANGED
|
@@ -61,7 +61,6 @@ export async function buildProgram(): Promise<Command> {
|
|
|
61
61
|
registerCommand(program, withKernelOptions((await import('../commands/token')).default))
|
|
62
62
|
registerCommand(program, withKernelOptions((await import('../commands/get')).default))
|
|
63
63
|
registerCommand(program, withKernelOptions((await import('../commands/mutate')).default))
|
|
64
|
-
registerCommand(program, withKernelOptions((await import('../commands/ls')).default))
|
|
65
64
|
registerCommand(program, withKernelOptions((await import('../commands/describe')).default))
|
|
66
65
|
registerCommand(program, withKernelOptions((await import('../commands/query')).default))
|
|
67
66
|
registerCommand(program, withKernelOptions((await import('../commands/logs')).default))
|
|
@@ -159,7 +158,7 @@ export async function buildProgram(): Promise<Command> {
|
|
|
159
158
|
`
|
|
160
159
|
Command groups:
|
|
161
160
|
Getting started setup (sign in, pick an instance, equip your workspace)
|
|
162
|
-
Kernel
|
|
161
|
+
Kernel get, mutate, call, query, describe, token
|
|
163
162
|
Management admin, instance, domain, identity, auth, idp, update
|
|
164
163
|
Agent browser (drive the GUI via agent-browser)
|
|
165
164
|
Studio studio (launch the local Domain Studio GUI for a workspace)
|
|
@@ -173,7 +172,6 @@ Path syntax:
|
|
|
173
172
|
@nodeId::method Instance method on a node by UID
|
|
174
173
|
|
|
175
174
|
Examples:
|
|
176
|
-
$ astrale ls @note --edge /:notes.example.dev:class.references
|
|
177
175
|
$ astrale studio
|
|
178
176
|
$ astrale admin status
|
|
179
177
|
$ astrale update --check
|
|
@@ -49,7 +49,7 @@ describe('extractSignals / hasSignals', () => {
|
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
test('quiet session has no signals until a transcript is attached', () => {
|
|
52
|
-
const s = extractSignals([ev(['status'], 0), ev(['
|
|
52
|
+
const s = extractSignals([ev(['status'], 0), ev(['query', '/'], 0)])
|
|
53
53
|
expect(hasSignals(s)).toBe(false)
|
|
54
54
|
s.harnessSessions = [
|
|
55
55
|
{
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
2
|
-
|
|
3
|
-
import { ClassPath } from '@astrale-os/sdk/graph/class'
|
|
4
|
-
import { NodeId } from '@astrale-os/sdk/graph/node'
|
|
5
|
-
import { normalizeProperties } from '@astrale-os/sdk/graph/properties'
|
|
6
|
-
import { describe, expect, test } from 'bun:test'
|
|
7
|
-
|
|
8
|
-
import { displayName, listProjection } from '../ls'
|
|
9
|
-
|
|
10
|
-
describe('ls display projection', () => {
|
|
11
|
-
const node = {
|
|
12
|
-
id: NodeId('note-1'),
|
|
13
|
-
class: ClassPath.parse('/:notes.example.dev:class.Note'),
|
|
14
|
-
props: normalizeProperties({ 'notes.example.dev:class.Note.property.title': 'Hello' }),
|
|
15
|
-
} satisfies Node
|
|
16
|
-
|
|
17
|
-
/** @evidence TEST-CLI-LS-PROJECTS-CANONICAL-NODES */
|
|
18
|
-
test('uses canonical properties for display and @id for pipeable output', () => {
|
|
19
|
-
expect(displayName(node)).toBe('Hello')
|
|
20
|
-
expect(listProjection([node])).toMatchObject({
|
|
21
|
-
rows: [{ name: 'Hello', class: 'Note', id: 'note-1' }],
|
|
22
|
-
paths: ['@note-1'],
|
|
23
|
-
})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test('falls back to the canonical Node ID when no display property exists', () => {
|
|
27
|
-
expect(displayName({ ...node, props: normalizeProperties({}) })).toBe('@note-1')
|
|
28
|
-
})
|
|
29
|
-
})
|
package/src/commands/ls.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
2
|
-
import type { QueryDirection, QueryResult } from '@astrale-os/sdk/query'
|
|
3
|
-
|
|
4
|
-
import { ClassPath } from '@astrale-os/sdk/graph/class'
|
|
5
|
-
import chalk from 'chalk'
|
|
6
|
-
|
|
7
|
-
import type { KernelCommandOpts } from '../connection'
|
|
8
|
-
import type { ListProjection } from '../lib/output'
|
|
9
|
-
import type { CommandDefinition } from '../program/index'
|
|
10
|
-
|
|
11
|
-
import { expandSelfInPath, runKernelCommand, withSelfHint } from '../connection'
|
|
12
|
-
import { nodeProperty, prepareQuery } from '../graph/index'
|
|
13
|
-
import { log } from '../lib/log'
|
|
14
|
-
import { isMachine, presentList } from '../lib/output'
|
|
15
|
-
|
|
16
|
-
type LsOpts = KernelCommandOpts & {
|
|
17
|
-
edge?: string
|
|
18
|
-
direction?: QueryDirection
|
|
19
|
-
limit?: string
|
|
20
|
-
cursor?: string
|
|
21
|
-
long?: boolean
|
|
22
|
-
quiet?: boolean
|
|
23
|
-
count?: boolean
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function lsCommand(source: string, opts: LsOpts): Promise<void> {
|
|
27
|
-
if (opts.edge === undefined) {
|
|
28
|
-
log.error('ls requires --edge <class>; Kernel V2 has no universal child relation')
|
|
29
|
-
process.exit(1)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
let path: string
|
|
33
|
-
let meta
|
|
34
|
-
let prepared
|
|
35
|
-
try {
|
|
36
|
-
;({ path, meta } = await expandSelfInPath(source, opts))
|
|
37
|
-
prepared = prepareQuery({
|
|
38
|
-
sources: [path],
|
|
39
|
-
edge: opts.edge,
|
|
40
|
-
direction: opts.direction,
|
|
41
|
-
limit: opts.limit,
|
|
42
|
-
cursor: opts.cursor,
|
|
43
|
-
})
|
|
44
|
-
} catch (error) {
|
|
45
|
-
log.error(error instanceof Error ? error.message : 'Invalid edge neighborhood')
|
|
46
|
-
process.exit(1)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
await runKernelCommand({
|
|
50
|
-
opts,
|
|
51
|
-
label: `Neighbors of ${path}`,
|
|
52
|
-
fn: ({ graph }) => withSelfHint(() => graph.query(prepared.ast, { page: prepared.page }), meta),
|
|
53
|
-
format: (response, format) => {
|
|
54
|
-
const graph = completeGraph(response.result)
|
|
55
|
-
presentList(
|
|
56
|
-
[...graph.nodes],
|
|
57
|
-
{ ...format, long: opts.long, quiet: opts.quiet, count: opts.count },
|
|
58
|
-
listProjection,
|
|
59
|
-
)
|
|
60
|
-
if (response.page.next && !isMachine(format) && !opts.quiet && !opts.count) {
|
|
61
|
-
process.stderr.write(` cursor: ${response.page.next}\n`)
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function completeGraph(result: QueryResult) {
|
|
68
|
-
if (result.kind !== 'graph') throw new TypeError('ls expected one complete graph Query result')
|
|
69
|
-
return result.graph
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function listProjection(nodes: Node[]): ListProjection {
|
|
73
|
-
return {
|
|
74
|
-
columns: [
|
|
75
|
-
{ key: 'name', header: 'NAME', color: chalk.cyan },
|
|
76
|
-
{ key: 'class', header: 'CLASS', color: chalk.dim },
|
|
77
|
-
{ key: 'id', header: 'ID', color: chalk.dim },
|
|
78
|
-
],
|
|
79
|
-
rows: nodes.map((node) => ({
|
|
80
|
-
name: displayName(node),
|
|
81
|
-
class: ClassPath.name(node.class),
|
|
82
|
-
id: node.id,
|
|
83
|
-
})),
|
|
84
|
-
paths: nodes.map((node) => `@${node.id}`),
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function displayName(node: Node): string {
|
|
89
|
-
const value =
|
|
90
|
-
nodeProperty(node, 'name') ?? nodeProperty(node, 'title') ?? nodeProperty(node, 'slug')
|
|
91
|
-
return typeof value === 'string' && value.length > 0 ? value : `@${node.id}`
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export default {
|
|
95
|
-
name: 'ls',
|
|
96
|
-
description: 'List one finite exact edge neighborhood',
|
|
97
|
-
afterHelpText: `
|
|
98
|
-
Behavior:
|
|
99
|
-
Lists Nodes reached from one source through one exact Edge Class. Kernel V2
|
|
100
|
-
has no universal parent/child relation, so --edge is required and recursive
|
|
101
|
-
tree walking is intentionally absent. Direction defaults to outgoing and the
|
|
102
|
-
finite default limit is 100. -q emits one @id per line.
|
|
103
|
-
|
|
104
|
-
Examples:
|
|
105
|
-
$ astrale ls @note --edge /:notes.example.dev:class.references
|
|
106
|
-
$ astrale ls @note --edge /:notes.example.dev:class.references --direction incoming --limit 25
|
|
107
|
-
`,
|
|
108
|
-
arguments: [{ name: 'source', description: 'Canonical source Path or @id' }],
|
|
109
|
-
options: [
|
|
110
|
-
{ flags: '--edge <class>', description: 'Exact Edge Class to traverse' },
|
|
111
|
-
{
|
|
112
|
-
flags: '--direction <direction>',
|
|
113
|
-
description: 'Traversal direction (default: outgoing)',
|
|
114
|
-
choices: ['outgoing', 'incoming', 'incident'],
|
|
115
|
-
},
|
|
116
|
-
{ flags: '--limit <n>', description: 'Finite selected Node limit (default: 100)' },
|
|
117
|
-
{ flags: '--cursor <token>', description: 'Resume one live query scope' },
|
|
118
|
-
{ flags: '-l, --long', description: 'Print complete canonical Nodes' },
|
|
119
|
-
{ flags: '-q, --quiet', description: 'Print one @id per line' },
|
|
120
|
-
{ flags: '--count', description: 'Print only the number of returned Nodes' },
|
|
121
|
-
],
|
|
122
|
-
action: async (source, opts) => {
|
|
123
|
-
await lsCommand(source as string, opts as LsOpts)
|
|
124
|
-
},
|
|
125
|
-
} satisfies CommandDefinition
|