@astrale-os/cli 0.8.1-alpha.3 → 0.8.1-alpha.5
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 +21 -200
- 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 +30 -1
- package/src/lib/command-dx.ts +13 -0
- package/src/program/__tests__/program.test.ts +4 -4
- package/src/program/build.ts +1 -4
- package/src/telemetry/__tests__/gate.test.ts +1 -1
- package/src/commands/__tests__/ls.test.ts +0 -29
- package/src/commands/describe.ts +0 -86
- 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`, `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.5",
|
|
2580
2580
|
description: "Astrale CLI — connect to existing Astrale kernels",
|
|
2581
2581
|
keywords: [
|
|
2582
2582
|
"astrale",
|
|
@@ -90847,200 +90847,6 @@ Examples:
|
|
|
90847
90847
|
};
|
|
90848
90848
|
});
|
|
90849
90849
|
|
|
90850
|
-
// node_modules/.pnpm/@astrale-os+sdk@file+vendor+astrale-os-sdk-0.5.0-beta.1.tgz_@astrale-os+ox@0.1.2_oxfmt@_72b33f8043e5d121b8114f4a315f9796/node_modules/@astrale-os/sdk/dist/graph/class/index.js
|
|
90851
|
-
var init_class3 = __esm(() => {
|
|
90852
|
-
init_class();
|
|
90853
|
-
});
|
|
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
|
-
// src/commands/describe.ts
|
|
90965
|
-
var exports_describe = {};
|
|
90966
|
-
__export(exports_describe, {
|
|
90967
|
-
describeCommand: () => describeCommand,
|
|
90968
|
-
default: () => describe_default
|
|
90969
|
-
});
|
|
90970
|
-
async function describeCommand(target2, opts) {
|
|
90971
|
-
let path5;
|
|
90972
|
-
let meta3;
|
|
90973
|
-
try {
|
|
90974
|
-
({ path: path5, meta: meta3 } = await expandSelfInPath(target2, opts));
|
|
90975
|
-
} catch (error52) {
|
|
90976
|
-
log.error(error52 instanceof Error ? error52.message : "Invalid target");
|
|
90977
|
-
process.exit(1);
|
|
90978
|
-
}
|
|
90979
|
-
await runKernelCommand({
|
|
90980
|
-
opts,
|
|
90981
|
-
label: `Describe ${path5}`,
|
|
90982
|
-
fn: ({ graph }) => withSelfHint(() => graph.getOrThrow(Path.parse(path5)), meta3),
|
|
90983
|
-
format: (node4, format3, machine) => {
|
|
90984
|
-
const shown = opts.schema === false ? withoutSchema(node4) : node4;
|
|
90985
|
-
if (machine || format3.format !== undefined)
|
|
90986
|
-
output(shown, format3);
|
|
90987
|
-
else
|
|
90988
|
-
printDescription(path5, shown);
|
|
90989
|
-
}
|
|
90990
|
-
});
|
|
90991
|
-
}
|
|
90992
|
-
function withoutSchema(node4) {
|
|
90993
|
-
return {
|
|
90994
|
-
...node4,
|
|
90995
|
-
props: Object.fromEntries(Object.entries(node4.props).filter(([key]) => unqualifyProperty(key) !== "schema"))
|
|
90996
|
-
};
|
|
90997
|
-
}
|
|
90998
|
-
function printDescription(target2, node4) {
|
|
90999
|
-
console.log(` ${source_default.bold.cyan(target2)}`);
|
|
91000
|
-
console.log(` ${source_default.dim("id")} ${node4.id}`);
|
|
91001
|
-
console.log(` ${source_default.dim("class")} ${ClassPath.name(node4.class)} ${source_default.dim(`(${node4.class})`)}`);
|
|
91002
|
-
const properties = Object.entries(node4.props);
|
|
91003
|
-
if (properties.length === 0)
|
|
91004
|
-
return;
|
|
91005
|
-
console.log(`
|
|
91006
|
-
${source_default.bold("Properties:")}`);
|
|
91007
|
-
for (const [key, value2] of properties) {
|
|
91008
|
-
const rendered = typeof value2 === "string" ? value2 : JSON.stringify(value2);
|
|
91009
|
-
console.log(` ${source_default.cyan(unqualifyProperty(key))}: ${source_default.dim(rendered)}`);
|
|
91010
|
-
}
|
|
91011
|
-
}
|
|
91012
|
-
var describe_default;
|
|
91013
|
-
var init_describe = __esm(() => {
|
|
91014
|
-
init_class3();
|
|
91015
|
-
init_path5();
|
|
91016
|
-
init_source();
|
|
91017
|
-
init_connection2();
|
|
91018
|
-
init_graph6();
|
|
91019
|
-
init_log();
|
|
91020
|
-
init_output();
|
|
91021
|
-
describe_default = {
|
|
91022
|
-
name: "describe",
|
|
91023
|
-
description: "Describe the canonical facts of one node",
|
|
91024
|
-
afterHelpText: `
|
|
91025
|
-
Behavior:
|
|
91026
|
-
Reads one canonical Node and presents its ID, Class, and properties.
|
|
91027
|
-
It does not infer operations or children from path layout. --no-schema
|
|
91028
|
-
omits schema-valued properties from Domain-sized results.
|
|
91029
|
-
|
|
91030
|
-
Examples:
|
|
91031
|
-
$ astrale describe /:kernel.astrale.ai:class.Domain
|
|
91032
|
-
$ astrale describe @self --no-schema
|
|
91033
|
-
`,
|
|
91034
|
-
arguments: [{ name: "target", description: "Canonical Node Path or @id" }],
|
|
91035
|
-
options: [
|
|
91036
|
-
{ flags: "--no-schema", description: "Omit properties whose qualified leaf is schema" }
|
|
91037
|
-
],
|
|
91038
|
-
action: async (target2, opts) => {
|
|
91039
|
-
await describeCommand(target2, opts);
|
|
91040
|
-
}
|
|
91041
|
-
};
|
|
91042
|
-
});
|
|
91043
|
-
|
|
91044
90850
|
// src/commands/query.ts
|
|
91045
90851
|
var exports_query = {};
|
|
91046
90852
|
__export(exports_query, {
|
|
@@ -93569,7 +93375,7 @@ Behavior:
|
|
|
93569
93375
|
Reads the admin catalog — every domain that has been \`publish\`ed
|
|
93570
93376
|
(origin → published worker URL). Listing only shows what is INSTALLABLE;
|
|
93571
93377
|
what is actually mounted where lives on each instance's own graph
|
|
93572
|
-
(\`astrale
|
|
93378
|
+
(\`astrale query\` against that instance).
|
|
93573
93379
|
|
|
93574
93380
|
Default output is a NAME/ORIGIN/URL/DEFAULT table on a TTY, JSON when piped
|
|
93575
93381
|
or with --json/--raw (agent-friendly — full DomainInfo objects). -q prints
|
|
@@ -94174,6 +93980,11 @@ var init_create3 = __esm(() => {
|
|
|
94174
93980
|
};
|
|
94175
93981
|
});
|
|
94176
93982
|
|
|
93983
|
+
// node_modules/.pnpm/@astrale-os+sdk@file+vendor+astrale-os-sdk-0.5.0-beta.1.tgz_@astrale-os+ox@0.1.2_oxfmt@_72b33f8043e5d121b8114f4a315f9796/node_modules/@astrale-os/sdk/dist/graph/class/index.js
|
|
93984
|
+
var init_class3 = __esm(() => {
|
|
93985
|
+
init_class();
|
|
93986
|
+
});
|
|
93987
|
+
|
|
94177
93988
|
// node_modules/.pnpm/@astrale-os+sdk@file+vendor+astrale-os-sdk-0.5.0-beta.1.tgz_@astrale-os+ox@0.1.2_oxfmt@_72b33f8043e5d121b8114f4a315f9796/node_modules/@astrale-os/sdk/dist/graph/model/index.js
|
|
94178
93989
|
var init_model8 = __esm(() => {
|
|
94179
93990
|
init_graph2();
|
|
@@ -95904,9 +95715,22 @@ function renderCommanderError(program2, error, argv = process.argv.slice(2)) {
|
|
|
95904
95715
|
].join(`
|
|
95905
95716
|
`);
|
|
95906
95717
|
}
|
|
95718
|
+
var RETIRED_COMMANDS = {
|
|
95719
|
+
ls: "astrale query <source> --edge <class>",
|
|
95720
|
+
describe: "astrale get <target>"
|
|
95721
|
+
};
|
|
95907
95722
|
function renderUnknownCommand(tokens, catalog) {
|
|
95908
95723
|
const command = tokens.join(" ");
|
|
95909
95724
|
const first = tokens[0];
|
|
95725
|
+
const retired = first === undefined ? undefined : RETIRED_COMMANDS[first];
|
|
95726
|
+
if (retired !== undefined) {
|
|
95727
|
+
return [
|
|
95728
|
+
`Unknown command: ${source_default.bold(`astrale ${command}`)}`,
|
|
95729
|
+
"",
|
|
95730
|
+
`\`astrale ${first}\` was removed. Use ${source_default.bold(retired)} instead.`
|
|
95731
|
+
].join(`
|
|
95732
|
+
`);
|
|
95733
|
+
}
|
|
95910
95734
|
const namespaceMatches = catalog.filter((entry) => entry.path.at(-1) === first);
|
|
95911
95735
|
if (namespaceMatches.length > 0) {
|
|
95912
95736
|
return [
|
|
@@ -96106,8 +95930,6 @@ async function buildProgram() {
|
|
|
96106
95930
|
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_token(), exports_token))).default));
|
|
96107
95931
|
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_get(), exports_get))).default));
|
|
96108
95932
|
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
|
-
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_describe(), exports_describe))).default));
|
|
96111
95933
|
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_query7(), exports_query))).default));
|
|
96112
95934
|
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_logs(), exports_logs))).default));
|
|
96113
95935
|
registerCommand(program3, withKernelOptions((await Promise.resolve().then(() => (init_view6(), exports_view2))).default));
|
|
@@ -96194,7 +96016,7 @@ async function buildProgram() {
|
|
|
96194
96016
|
program3.addHelpText("after", `
|
|
96195
96017
|
Command groups:
|
|
96196
96018
|
Getting started setup (sign in, pick an instance, equip your workspace)
|
|
96197
|
-
Kernel
|
|
96019
|
+
Kernel get, mutate, call, query, token
|
|
96198
96020
|
Management admin, instance, domain, identity, auth, idp, update
|
|
96199
96021
|
Agent browser (drive the GUI via agent-browser)
|
|
96200
96022
|
Studio studio (launch the local Domain Studio GUI for a workspace)
|
|
@@ -96208,7 +96030,6 @@ Path syntax:
|
|
|
96208
96030
|
@nodeId::method Instance method on a node by UID
|
|
96209
96031
|
|
|
96210
96032
|
Examples:
|
|
96211
|
-
$ astrale ls @note --edge /:notes.example.dev:class.references
|
|
96212
96033
|
$ astrale studio
|
|
96213
96034
|
$ astrale admin status
|
|
96214
96035
|
$ 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,11 @@ 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
|
+
expect(usages).toContain('get <target>')
|
|
26
27
|
expect(usages).toContain('status')
|
|
28
|
+
expect(usages).not.toContain('ls <source>')
|
|
29
|
+
expect(usages).not.toContain('describe <target>')
|
|
27
30
|
})
|
|
28
31
|
|
|
29
32
|
test('explains shared verbs as namespaced commands, not arity errors', async () => {
|
|
@@ -42,6 +45,32 @@ describe('command DX suggestions', () => {
|
|
|
42
45
|
expect(rendered).not.toContain('too many arguments')
|
|
43
46
|
})
|
|
44
47
|
|
|
48
|
+
test('points retired describe at get', async () => {
|
|
49
|
+
const program = await buildProgram()
|
|
50
|
+
const error = new CommanderError(
|
|
51
|
+
1,
|
|
52
|
+
'commander.unknownCommand',
|
|
53
|
+
"error: unknown command 'describe'",
|
|
54
|
+
)
|
|
55
|
+
const rendered = stripAnsi(renderCommanderError(program, error, ['describe', '@note']))
|
|
56
|
+
|
|
57
|
+
expect(rendered).toContain('Unknown command: astrale describe @note')
|
|
58
|
+
expect(rendered).toContain('astrale describe` was removed')
|
|
59
|
+
expect(rendered).toContain('astrale get <target>')
|
|
60
|
+
expect(rendered).not.toContain('Did you mean:')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('points retired ls at query', async () => {
|
|
64
|
+
const program = await buildProgram()
|
|
65
|
+
const error = new CommanderError(1, 'commander.unknownCommand', "error: unknown command 'ls'")
|
|
66
|
+
const rendered = stripAnsi(renderCommanderError(program, error, ['ls', '@note']))
|
|
67
|
+
|
|
68
|
+
expect(rendered).toContain('Unknown command: astrale ls @note')
|
|
69
|
+
expect(rendered).toContain('astrale ls` was removed')
|
|
70
|
+
expect(rendered).toContain('astrale query <source> --edge <class>')
|
|
71
|
+
expect(rendered).not.toContain('Did you mean:')
|
|
72
|
+
})
|
|
73
|
+
|
|
45
74
|
test('suggests nearest command for typo paths', async () => {
|
|
46
75
|
const program = await buildProgram()
|
|
47
76
|
const error = new CommanderError(
|
package/src/lib/command-dx.ts
CHANGED
|
@@ -72,9 +72,22 @@ 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
|
+
describe: 'astrale get <target>',
|
|
78
|
+
}
|
|
79
|
+
|
|
75
80
|
function renderUnknownCommand(tokens: string[], catalog: CommandCatalogEntry[]): string {
|
|
76
81
|
const command = tokens.join(' ')
|
|
77
82
|
const first = tokens[0]
|
|
83
|
+
const retired = first === undefined ? undefined : RETIRED_COMMANDS[first]
|
|
84
|
+
if (retired !== undefined) {
|
|
85
|
+
return [
|
|
86
|
+
`Unknown command: ${chalk.bold(`astrale ${command}`)}`,
|
|
87
|
+
'',
|
|
88
|
+
`\`astrale ${first}\` was removed. Use ${chalk.bold(retired)} instead.`,
|
|
89
|
+
].join('\n')
|
|
90
|
+
}
|
|
78
91
|
const namespaceMatches = catalog.filter((entry) => entry.path.at(-1) === first)
|
|
79
92
|
if (namespaceMatches.length > 0) {
|
|
80
93
|
return [
|
|
@@ -138,7 +138,6 @@ describe('program composition', () => {
|
|
|
138
138
|
'auth token',
|
|
139
139
|
'browser',
|
|
140
140
|
'call',
|
|
141
|
-
'describe',
|
|
142
141
|
'domain',
|
|
143
142
|
'domain install',
|
|
144
143
|
'domain list',
|
|
@@ -171,7 +170,6 @@ describe('program composition', () => {
|
|
|
171
170
|
'instance status',
|
|
172
171
|
'instance use',
|
|
173
172
|
'logs',
|
|
174
|
-
'ls',
|
|
175
173
|
'mutate',
|
|
176
174
|
'query',
|
|
177
175
|
'session',
|
|
@@ -187,7 +185,7 @@ describe('program composition', () => {
|
|
|
187
185
|
'whoami',
|
|
188
186
|
])
|
|
189
187
|
expect(createHash('sha256').update(JSON.stringify(surface)).digest('hex')).toBe(
|
|
190
|
-
'
|
|
188
|
+
'369e7d1d51871f0ca1c6007d2a5c45023d093e70b85db07c1f58b06b7a0b17d4',
|
|
191
189
|
)
|
|
192
190
|
})
|
|
193
191
|
|
|
@@ -267,7 +265,7 @@ describe('help contract — connect-only command surface', () => {
|
|
|
267
265
|
const program = await buildProgram()
|
|
268
266
|
const names = allCommands(program).map((command) => command.name())
|
|
269
267
|
|
|
270
|
-
// `logs` and `domain` have managed/admin meanings, so only retired
|
|
268
|
+
// `logs` and `domain` have managed/admin meanings, so only retired verbs are absent.
|
|
271
269
|
for (const removed of [
|
|
272
270
|
'init',
|
|
273
271
|
'start',
|
|
@@ -279,6 +277,8 @@ describe('help contract — connect-only command surface', () => {
|
|
|
279
277
|
'graph',
|
|
280
278
|
'server',
|
|
281
279
|
'env',
|
|
280
|
+
'ls',
|
|
281
|
+
'describe',
|
|
282
282
|
]) {
|
|
283
283
|
expect(names).not.toContain(removed)
|
|
284
284
|
}
|
package/src/program/build.ts
CHANGED
|
@@ -61,8 +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
|
-
registerCommand(program, withKernelOptions((await import('../commands/describe')).default))
|
|
66
64
|
registerCommand(program, withKernelOptions((await import('../commands/query')).default))
|
|
67
65
|
registerCommand(program, withKernelOptions((await import('../commands/logs')).default))
|
|
68
66
|
registerCommand(program, withKernelOptions((await import('../commands/view')).default))
|
|
@@ -159,7 +157,7 @@ export async function buildProgram(): Promise<Command> {
|
|
|
159
157
|
`
|
|
160
158
|
Command groups:
|
|
161
159
|
Getting started setup (sign in, pick an instance, equip your workspace)
|
|
162
|
-
Kernel
|
|
160
|
+
Kernel get, mutate, call, query, token
|
|
163
161
|
Management admin, instance, domain, identity, auth, idp, update
|
|
164
162
|
Agent browser (drive the GUI via agent-browser)
|
|
165
163
|
Studio studio (launch the local Domain Studio GUI for a workspace)
|
|
@@ -173,7 +171,6 @@ Path syntax:
|
|
|
173
171
|
@nodeId::method Instance method on a node by UID
|
|
174
172
|
|
|
175
173
|
Examples:
|
|
176
|
-
$ astrale ls @note --edge /:notes.example.dev:class.references
|
|
177
174
|
$ astrale studio
|
|
178
175
|
$ astrale admin status
|
|
179
176
|
$ 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/describe.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import type { Node } from '@astrale-os/sdk/graph/node'
|
|
2
|
-
|
|
3
|
-
import { ClassPath } from '@astrale-os/sdk/graph/class'
|
|
4
|
-
import { Path } from '@astrale-os/sdk/graph/path'
|
|
5
|
-
import chalk from 'chalk'
|
|
6
|
-
|
|
7
|
-
import type { KernelCommandOpts } from '../connection'
|
|
8
|
-
import type { CommandDefinition } from '../program/index'
|
|
9
|
-
|
|
10
|
-
import { expandSelfInPath, runKernelCommand, withSelfHint } from '../connection'
|
|
11
|
-
import { unqualifyProperty } from '../graph/index'
|
|
12
|
-
import { log } from '../lib/log'
|
|
13
|
-
import { output } from '../lib/output'
|
|
14
|
-
|
|
15
|
-
type DescribeOpts = KernelCommandOpts & { schema?: boolean }
|
|
16
|
-
type DescribedNode = Omit<Node, 'props'> & { readonly props: Readonly<Record<string, unknown>> }
|
|
17
|
-
|
|
18
|
-
/** Describe only facts present on the canonical Node; no hierarchy is inferred from its Path. */
|
|
19
|
-
export async function describeCommand(target: string, opts: DescribeOpts): Promise<void> {
|
|
20
|
-
let path: string
|
|
21
|
-
let meta
|
|
22
|
-
try {
|
|
23
|
-
;({ path, meta } = await expandSelfInPath(target, opts))
|
|
24
|
-
} catch (error) {
|
|
25
|
-
log.error(error instanceof Error ? error.message : 'Invalid target')
|
|
26
|
-
process.exit(1)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
await runKernelCommand({
|
|
30
|
-
opts,
|
|
31
|
-
label: `Describe ${path}`,
|
|
32
|
-
fn: ({ graph }) => withSelfHint(() => graph.getOrThrow(Path.parse(path)), meta),
|
|
33
|
-
format: (node, format, machine) => {
|
|
34
|
-
const shown = opts.schema === false ? withoutSchema(node) : node
|
|
35
|
-
if (machine || format.format !== undefined) output(shown, format)
|
|
36
|
-
else printDescription(path, shown)
|
|
37
|
-
},
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function withoutSchema(node: Node): DescribedNode {
|
|
42
|
-
return {
|
|
43
|
-
...node,
|
|
44
|
-
props: Object.fromEntries(
|
|
45
|
-
Object.entries(node.props).filter(([key]) => unqualifyProperty(key) !== 'schema'),
|
|
46
|
-
),
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function printDescription(target: string, node: DescribedNode): void {
|
|
51
|
-
console.log(` ${chalk.bold.cyan(target)}`)
|
|
52
|
-
console.log(` ${chalk.dim('id')} ${node.id}`)
|
|
53
|
-
console.log(
|
|
54
|
-
` ${chalk.dim('class')} ${ClassPath.name(node.class)} ${chalk.dim(`(${node.class})`)}`,
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
const properties = Object.entries(node.props)
|
|
58
|
-
if (properties.length === 0) return
|
|
59
|
-
console.log(`\n ${chalk.bold('Properties:')}`)
|
|
60
|
-
for (const [key, value] of properties) {
|
|
61
|
-
const rendered = typeof value === 'string' ? value : JSON.stringify(value)
|
|
62
|
-
console.log(` ${chalk.cyan(unqualifyProperty(key))}: ${chalk.dim(rendered)}`)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export default {
|
|
67
|
-
name: 'describe',
|
|
68
|
-
description: 'Describe the canonical facts of one node',
|
|
69
|
-
afterHelpText: `
|
|
70
|
-
Behavior:
|
|
71
|
-
Reads one canonical Node and presents its ID, Class, and properties.
|
|
72
|
-
It does not infer operations or children from path layout. --no-schema
|
|
73
|
-
omits schema-valued properties from Domain-sized results.
|
|
74
|
-
|
|
75
|
-
Examples:
|
|
76
|
-
$ astrale describe /:kernel.astrale.ai:class.Domain
|
|
77
|
-
$ astrale describe @self --no-schema
|
|
78
|
-
`,
|
|
79
|
-
arguments: [{ name: 'target', description: 'Canonical Node Path or @id' }],
|
|
80
|
-
options: [
|
|
81
|
-
{ flags: '--no-schema', description: 'Omit properties whose qualified leaf is schema' },
|
|
82
|
-
],
|
|
83
|
-
action: async (target, opts) => {
|
|
84
|
-
await describeCommand(target as string, opts as DescribeOpts)
|
|
85
|
-
},
|
|
86
|
-
} satisfies CommandDefinition
|
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
|