@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 CHANGED
@@ -110,7 +110,7 @@ Main command groups:
110
110
 
111
111
  | Group | What it covers |
112
112
  |-------|----------------|
113
- | Kernel | `ls`, `get`, `call`, `query`, `describe`, `token` |
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.3",
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 ls /\` against that instance).
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 ls, get, mutate, call, query, describe, token
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/cli",
3
- "version": "0.8.1-alpha.3",
3
+ "version": "0.8.1-alpha.5",
4
4
  "description": "Astrale CLI — connect to existing Astrale kernels",
5
5
  "keywords": [
6
6
  "astrale",
@@ -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 ls /\` against that instance).
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('ls <source>')
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(
@@ -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
- '4a57bfc347f9abebcea239427214eb25fbaf9ca704170c8b4a933d0c9ba4d14e',
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 local-runtime verbs are absent.
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
  }
@@ -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 ls, get, mutate, call, query, describe, token
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(['ls', '/'], 0)])
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
- })
@@ -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
@@ -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