@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 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`, `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.3",
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 ls /\` against that instance).
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 ls, get, mutate, call, query, describe, token
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
@@ -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.4",
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,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('ls <source>')
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(
@@ -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
- '4a57bfc347f9abebcea239427214eb25fbaf9ca704170c8b4a933d0c9ba4d14e',
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 local-runtime verbs are absent.
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
  }
@@ -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 ls, get, mutate, call, query, describe, token
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(['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,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