@astrale-os/cli 0.4.0-alpha.13 → 0.6.0-alpha.0

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.
Files changed (121) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +2 -2
  3. package/THIRD-PARTY-NOTICES.md +27 -0
  4. package/dist/astrale.js +24396 -9632
  5. package/package.json +24 -22
  6. package/src/command.ts +2 -0
  7. package/src/commands/__tests__/admin-instance.test.ts +3 -2
  8. package/src/commands/__tests__/domain-list.test.ts +6 -2
  9. package/src/commands/__tests__/help-contract.test.ts +27 -14
  10. package/src/commands/__tests__/install-identity-override.test.ts +2 -2
  11. package/src/commands/__tests__/ls.test.ts +1 -1
  12. package/src/commands/__tests__/read-commands.test.ts +201 -0
  13. package/src/commands/__tests__/view.test.ts +100 -0
  14. package/src/commands/call.ts +27 -44
  15. package/src/commands/describe.ts +57 -58
  16. package/src/commands/domain/install.ts +9 -9
  17. package/src/commands/domain/list.ts +2 -2
  18. package/src/commands/domain/publish.ts +3 -3
  19. package/src/commands/get.ts +48 -23
  20. package/src/commands/identity/register.ts +27 -33
  21. package/src/commands/instance/active.ts +2 -2
  22. package/src/commands/instance/create.ts +1 -1
  23. package/src/commands/instance/delete.ts +3 -3
  24. package/src/commands/instance/list.ts +8 -3
  25. package/src/commands/instance/status.ts +4 -3
  26. package/src/commands/instance/use.ts +2 -2
  27. package/src/commands/logs.ts +8 -8
  28. package/src/commands/ls.ts +77 -55
  29. package/src/commands/mutate.ts +191 -0
  30. package/src/commands/query.ts +307 -20
  31. package/src/commands/session/analyze.ts +50 -0
  32. package/src/commands/session/list.ts +36 -0
  33. package/src/commands/token.ts +4 -7
  34. package/src/commands/view-serve.ts +26 -0
  35. package/src/commands/view.ts +614 -0
  36. package/src/connect-core.test.ts +42 -0
  37. package/src/connect-core.ts +53 -0
  38. package/src/kernel/__tests__/auth.test.ts +1 -0
  39. package/src/kernel/__tests__/expand.test.ts +123 -0
  40. package/src/kernel/client.ts +33 -37
  41. package/src/kernel/expand.ts +55 -61
  42. package/src/kernel/graph.ts +96 -0
  43. package/src/kernel/index.ts +18 -3
  44. package/src/kernel/options.ts +1 -1
  45. package/src/kernel/run.ts +0 -13
  46. package/src/lib/__tests__/instance-target.test.ts +34 -0
  47. package/src/lib/__tests__/table.test.ts +1 -1
  48. package/src/lib/__tests__/view-open-intent.test.ts +308 -0
  49. package/src/lib/__tests__/view-snapshot.test.ts +77 -0
  50. package/src/lib/admin-domain.ts +8 -4
  51. package/src/lib/admin-instance.ts +5 -1
  52. package/src/lib/config.ts +1 -0
  53. package/src/lib/domain-identity.ts +1 -1
  54. package/src/lib/instance-target.ts +5 -0
  55. package/src/lib/instance.ts +11 -0
  56. package/src/lib/log.ts +12 -2
  57. package/src/lib/login-flow.ts +41 -4
  58. package/src/lib/provision-instance.ts +2 -2
  59. package/src/lib/self.ts +1 -3
  60. package/src/lib/view/open-intent.ts +97 -0
  61. package/src/lib/view/resolve.ts +104 -0
  62. package/src/lib/view/server.ts +294 -0
  63. package/src/lib/view/session.ts +123 -0
  64. package/src/lib/view/snapshot.ts +101 -0
  65. package/src/program.ts +16 -3
  66. package/src/registry.ts +1 -1
  67. package/src/setup/render.ts +1 -3
  68. package/src/setup/steps/instance.ts +2 -2
  69. package/src/telemetry/__tests__/gate.test.ts +63 -0
  70. package/src/telemetry/__tests__/recorder.test.ts +110 -0
  71. package/src/telemetry/__tests__/redact.test.ts +79 -0
  72. package/src/telemetry/__tests__/session.test.ts +166 -0
  73. package/src/telemetry/__tests__/trigger.test.ts +49 -0
  74. package/src/telemetry/adapters/__tests__/claude-code.test.ts +89 -0
  75. package/src/telemetry/adapters/__tests__/codex.test.ts +138 -0
  76. package/src/telemetry/adapters/__tests__/index.test.ts +88 -0
  77. package/src/telemetry/adapters/claude-code.ts +90 -0
  78. package/src/telemetry/adapters/codex.ts +160 -0
  79. package/src/telemetry/adapters/index.ts +45 -0
  80. package/src/telemetry/adapters/types.ts +21 -0
  81. package/src/telemetry/analyze.ts +227 -0
  82. package/src/telemetry/gate.ts +79 -0
  83. package/src/telemetry/recorder.ts +57 -0
  84. package/src/telemetry/redact.ts +53 -0
  85. package/src/telemetry/session.ts +88 -0
  86. package/src/telemetry/settings.ts +26 -0
  87. package/src/telemetry/store.ts +91 -0
  88. package/src/telemetry/trigger.ts +119 -0
  89. package/src/telemetry/types.ts +64 -0
  90. package/studio/client/dist/assets/index-CyN5G8IA.js +109 -0
  91. package/studio/client/dist/assets/index-DKKMHBBC.css +1 -0
  92. package/studio/client/dist/index.html +2 -2
  93. package/studio/server/agent/ask.ts +7 -2
  94. package/studio/server/agent/claude.ts +15 -3
  95. package/studio/server/agent/runner.ts +4 -1
  96. package/studio/server/agent/session-id.ts +13 -0
  97. package/studio/server/api.ts +50 -32
  98. package/studio/server/cache.ts +17 -6
  99. package/studio/server/client-package.test.ts +147 -0
  100. package/studio/server/client-package.ts +242 -0
  101. package/studio/server/index.ts +13 -0
  102. package/studio/server/introspect/anatomy-extras.test.ts +91 -0
  103. package/studio/server/introspect/anatomy-extras.ts +48 -49
  104. package/studio/server/introspect/anatomy.ts +7 -7
  105. package/studio/server/introspect/overlay-tsmorph.test.ts +104 -0
  106. package/studio/server/introspect/overlay-tsmorph.ts +230 -72
  107. package/studio/server/state/harness-gateway.ts +12 -3
  108. package/studio/server/state/harness-token.ts +0 -0
  109. package/studio/server/state/views.test.ts +90 -0
  110. package/studio/server/state/views.ts +396 -99
  111. package/studio/server/state/visibility.ts +10 -6
  112. package/studio/server/view-dev-server.test.ts +111 -0
  113. package/studio/server/view-dev-server.ts +372 -0
  114. package/studio/shared/types.ts +57 -10
  115. package/viewer/dist/index.html +93 -0
  116. package/viewer/dist/main.js +71 -0
  117. package/src/kernel/__tests__/remote-routing.test.ts +0 -70
  118. package/src/kernel/remote-routing.ts +0 -88
  119. package/studio/client/dist/assets/index-DOwzZAEK.css +0 -1
  120. package/studio/client/dist/assets/index-wtU0Zxhy.js +0 -183
  121. package/studio/tsconfig.json +0 -23
@@ -3,7 +3,14 @@ import { K } from '@astrale-os/kernel-core'
3
3
  import type { CommandDefinition } from '../command'
4
4
  import type { CallCommandOpts, ClientContext, SelfExpansionMeta } from '../kernel'
5
5
 
6
- import { buildSelfContext, resolveSelfIdLazy, runKernelCommand, withSelfHint } from '../kernel'
6
+ import {
7
+ bindGraph,
8
+ buildSelfContext,
9
+ nodeProp,
10
+ resolveSelfIdLazy,
11
+ runKernelCommand,
12
+ withSelfHint,
13
+ } from '../kernel'
7
14
  import { presentBinary, type BinaryLike } from '../lib/binary'
8
15
  import { log } from '../lib/log'
9
16
  import { output, present } from '../lib/output'
@@ -19,12 +26,8 @@ export async function callCommand(
19
26
  rawParams: string[],
20
27
  opts: CallOpts,
21
28
  ): Promise<void> {
22
- // ── Expand `@self` in path + raw param strings ──────────
23
- // Local resolution, with ONE lazy whoami round-trip for IdP identities
24
- // missing a cached registration (persisted, so it's local afterwards).
25
- // Throws a typed SelfRefusalError (manager, instance-signed, …) which we
26
- // surface as a fatal CLI error. Runs BEFORE `--describe` so users get the
27
- // typed refusal instead of a generic NotFoundError from the kernel.
29
+ // Expand @self before describe/execute so refusals are typed and stale IdP
30
+ // registrations can refresh through whoami.
28
31
  let expandedPath = path
29
32
  let expandedRaw = rawParams
30
33
  let selfMeta: SelfExpansionMeta | undefined
@@ -35,9 +38,7 @@ export async function callCommand(
35
38
  const selfId = await resolveSelfIdLazy(selfCtx, opts)
36
39
  expandedPath = expandSelfReferences(path, selfId)
37
40
  expandedRaw = rawParams.map((p) => expandSelfReferences(p, selfId))
38
- // Stamp metadata whenever ANY input mutated the stale-registration
39
- // hint in `formatKernelError` is just as useful when `@self` lived in
40
- // a param (`node=@self`) as when it was in the path head.
41
+ // Stale-registration hints apply when @self appears in either path or params.
41
42
  const rawMutated = expandedRaw.some((p, i) => p !== rawParams[i])
42
43
  if (expandedPath !== path || rawMutated) {
43
44
  selfMeta = {
@@ -80,14 +81,7 @@ export async function callCommand(
80
81
  opts,
81
82
  label: expandedPath,
82
83
  fn: async (ctx): Promise<CallResult> => {
83
- // Remote-bound functions live on an external worker. The kernel resolves
84
- // the call to a redirect carrying the worker's URL + `iss`; the session
85
- // follows it, minting a worker-scoped delegation for that `iss` (the
86
- // delegation cache wired in `client.ts`). No client-side binding lookup.
87
- //
88
- // The one thing the reactive path can't discover after dispatch is a
89
- // binary output mode (a JSON decode would corrupt the bytes), so detect
90
- // it up front and route to the binary transport — which also auto-follows.
84
+ // Binary outputs must be routed before dispatch; JSON decoding would corrupt bytes.
91
85
  if (await isBinaryOutput(ctx, expandedPath)) {
92
86
  const response = await withSelfHint(() => ctx.client.binary(expandedPath, params), selfMeta)
93
87
  return { kind: 'binary', response }
@@ -106,26 +100,15 @@ export async function callCommand(
106
100
  }
107
101
 
108
102
  /**
109
- * Best-effort pre-flight: does the target Function declare a binary output?
110
- * A binary method must use the binary transport (the value path would JSON-
111
- * decode and corrupt the bytes), and the client can't discover that after
112
- * dispatch. For a static path the path IS the Function node — read `output`
113
- * off it via `::get`. For an instance-method path (`<node>::method`) the
114
- * Function node isn't addressable that way: resolve the instance's class
115
- * first, then probe the class's method node (`<classPath>:method`). An
116
- * interface-hosted instance method still escapes the probe (its Function node
117
- * hangs off the interface, not the class) — that and any other failure returns
118
- * false and falls through to the value path, letting the kernel surface the
119
- * real error. `::get` is a kernel syscall (same origin) so it neither
120
- * redirects nor triggers delegation.
103
+ * Best-effort binary preflight. Static paths read the Function node directly;
104
+ * instance paths probe the class method node and fall back to value transport on miss.
121
105
  */
122
106
  async function isBinaryOutput(ctx: ClientContext, path: string): Promise<boolean> {
123
107
  try {
124
108
  const target = path.includes('::') ? await instanceMethodNodePath(ctx, path) : path
125
109
  if (!target) return false
126
- const node = (await ctx.client.call(`${target}::get`, {})) as {
127
- props?: Record<string, unknown>
128
- } | null
110
+ const node = await bindGraph(ctx).get(target)
111
+ // Props travel under fully-qualified keys; `output.key` IS that key.
129
112
  return node?.props?.[K.$.i('Function').output.key] === 'binary'
130
113
  } catch {
131
114
  return false
@@ -141,22 +124,23 @@ async function instanceMethodNodePath(
141
124
  const source = path.slice(0, sep)
142
125
  const method = path.slice(sep + 2)
143
126
  if (!source || !method) return undefined
144
- const node = (await ctx.client.call(`${source}::get`, {})) as { class?: string } | null
145
- // node.class is a ClassPath (`/:domain:class.Name`); appending `:<method>`
146
- // forms the MethodPath of the class-owned Function node.
147
- return node?.class ? `${node.class}:${method}` : undefined
127
+ const node = await bindGraph(ctx).get(source)
128
+ // ClassPath + method name forms the class-owned Function MethodPath.
129
+ return node?.class ? `${node.class.raw}:${method}` : undefined
148
130
  }
149
131
 
150
132
  async function describeOperation(path: string, opts: CallOpts): Promise<void> {
151
- await runKernelCommand<Record<string, unknown>>({
133
+ await runKernelCommand({
152
134
  opts,
153
135
  label: `Schema for ${path}`,
154
- fn: (ctx) => ctx.client.call(`${path}::get`, {}) as Promise<Record<string, unknown>>,
136
+ // The Function node carries the schemas as props (function.get depth:0).
137
+ fn: async (ctx) => await bindGraph(ctx).get(path),
155
138
  format: (node, fmtOpts) => {
156
- const props = (node.properties ?? node) as Record<string, unknown>
139
+ const input = nodeProp(node, 'inputSchema')
140
+ const outputSchema = nodeProp(node, 'outputSchema')
157
141
  const schema: Record<string, unknown> = {}
158
- if (props.inputSchema) schema.input = tryParseJson(props.inputSchema)
159
- if (props.outputSchema) schema.output = tryParseJson(props.outputSchema)
142
+ if (input) schema.input = tryParseJson(input)
143
+ if (outputSchema) schema.output = tryParseJson(outputSchema)
160
144
  output(Object.keys(schema).length > 0 ? schema : node, fmtOpts)
161
145
  },
162
146
  })
@@ -277,8 +261,7 @@ Examples:
277
261
  $ astrale call /:host.astrale.ai:class.KernelInstance:list
278
262
  $ astrale call /:blog.acme.com:class.Author:list limit=10
279
263
  $ astrale call '@self::deactivate'
280
- $ astrale call /:dist.astrale.ai:class.Domain:install --creds "$TOKEN" \\
281
- -d "$(cat spec.json)"
264
+ $ astrale call /:shell.astrale.ai:class.Application:available --json
282
265
  `,
283
266
  arguments: [
284
267
  {
@@ -2,20 +2,21 @@ import { ClassPath } from '@astrale-os/kernel-core/domain'
2
2
  import chalk from 'chalk'
3
3
 
4
4
  import type { CommandDefinition } from '../command'
5
- import type { KernelCommandOpts } from '../kernel'
6
-
7
- import { expandSelfInPath, extractItems, runKernelCommand, withSelfHint } from '../kernel'
5
+ import type { GraphNode, KernelCommandOpts } from '../kernel'
6
+
7
+ import {
8
+ bindGraph,
9
+ expandSelfInPath,
10
+ nodeProp,
11
+ runKernelCommand,
12
+ splitRoot,
13
+ unqualifyKey,
14
+ withSelfHint,
15
+ } from '../kernel'
8
16
  import { log } from '../lib/log'
9
17
  import { output } from '../lib/output'
10
18
 
11
- type NodeItem = {
12
- id?: string
13
- slug?: string
14
- class?: string
15
- properties?: Record<string, unknown>
16
- }
17
-
18
- type DescribeResult = { node: NodeItem; children: NodeItem[] }
19
+ type DescribeResult = { node: GraphNode | undefined; children: GraphNode[] }
19
20
 
20
21
  // Commander turns `--no-schema` into `schema: false`.
21
22
  type DescribeOpts = KernelCommandOpts & { schema?: boolean }
@@ -32,27 +33,18 @@ export async function describeCommand(path: string, opts: DescribeOpts): Promise
32
33
  await runKernelCommand<DescribeResult>({
33
34
  opts,
34
35
  label: expandedPath,
36
+ // One function.get depth:1 = the node AND its children (was ::get +
37
+ // ::listChildren). Function-class children are the node's operations.
35
38
  fn: async (ctx) => {
36
- const node = (await withSelfHint(
37
- () => ctx.client.call(`${expandedPath}::get`, {}),
39
+ const result = await withSelfHint(
40
+ () => bindGraph(ctx).query((q) => q.from(expandedPath).children()),
38
41
  meta,
39
- )) as NodeItem
40
-
41
- let children: NodeItem[] = []
42
- try {
43
- const result = await withSelfHint(
44
- () => ctx.client.call(`${expandedPath}::listChildren`, {}),
45
- meta,
46
- )
47
- children = extractItems<NodeItem>(result)
48
- } catch {
49
- // Node may have no children (leaf node)
50
- }
51
-
52
- return { node, children }
42
+ )
43
+ const { root, children } = splitRoot(result.wire.nodes, expandedPath)
44
+ return { node: root, children }
53
45
  },
54
46
  format: (result, fmtOpts, isRaw) => {
55
- const shown = opts.schema === false ? stripSchemaProp(result) : result
47
+ const shown = opts.schema === false ? stripSchema(result) : result
56
48
  if (isRaw) {
57
49
  output(shown, fmtOpts)
58
50
  return
@@ -62,23 +54,21 @@ export async function describeCommand(path: string, opts: DescribeOpts): Promise
62
54
  })
63
55
  }
64
56
 
65
- function stripSchemaProp(result: DescribeResult): DescribeResult {
66
- const props = result.node.properties
67
- if (!props || !('schema' in props)) return result
68
- const { schema: _omitted, ...rest } = props
69
- return { ...result, node: { ...result.node, properties: rest } }
57
+ function stripSchema(result: DescribeResult): DescribeResult {
58
+ if (!result.node) return result
59
+ const entries = Object.entries(result.node.props).filter(([k]) => unqualifyKey(k) !== 'schema')
60
+ return { ...result, node: { ...result.node, props: Object.fromEntries(entries) } }
70
61
  }
71
62
 
72
63
  // ── Pretty-print ────────────────────────────────────────────
73
64
 
74
65
  function printDescribe({ node, children }: DescribeResult, path: string): void {
75
66
  const kind = classNameOf(node) ?? 'Node'
76
- const slug = node.properties?.slug ?? node.slug ?? path.split('/').pop()
77
- console.log(` ${chalk.bold.cyan(String(slug))} ${chalk.dim(`(${kind})`)}`)
67
+ const name = basename(path) || path
68
+ console.log(` ${chalk.bold.cyan(name)} ${chalk.dim(`(${kind})`)}`)
78
69
 
79
- if (node.properties?.description) {
80
- console.log(` ${chalk.dim(String(node.properties.description))}`)
81
- }
70
+ const description = nodeProp(node, 'description')
71
+ if (description) console.log(` ${chalk.dim(String(description))}`)
82
72
  console.log('')
83
73
 
84
74
  const operations = children.filter(isFunction)
@@ -86,12 +76,13 @@ function printDescribe({ node, children }: DescribeResult, path: string): void {
86
76
 
87
77
  if (operations.length > 0) {
88
78
  console.log(` ${chalk.bold('Operations:')}`)
89
- const slugW = Math.max(4, ...operations.map((o) => (o.slug ?? '').length))
90
- for (const op of operations) {
91
- const opSlug = (op.slug ?? '?').padEnd(slugW)
92
- const schema = formatInputSchema(op.properties)
93
- console.log(` ${chalk.green(opSlug)} ${chalk.dim(schema)}`)
94
- }
79
+ const names = operations.map((o) => basename(o.path))
80
+ const slugW = Math.max(4, ...names.map((n) => n.length))
81
+ operations.forEach((op, i) => {
82
+ const opName = names[i].padEnd(slugW)
83
+ const schema = formatInputSchema(op)
84
+ console.log(` ${chalk.green(opName)} ${chalk.dim(schema)}`)
85
+ })
95
86
  console.log('')
96
87
  }
97
88
 
@@ -99,7 +90,7 @@ function printDescribe({ node, children }: DescribeResult, path: string): void {
99
90
  console.log(` ${chalk.bold('Children:')}`)
100
91
  for (const child of otherChildren) {
101
92
  const childKind = classNameOf(child) ?? '?'
102
- console.log(` ${chalk.cyan(child.slug ?? '?')} ${chalk.dim(childKind)}`)
93
+ console.log(` ${chalk.cyan(basename(child.path) || '?')} ${chalk.dim(childKind)}`)
103
94
  }
104
95
  console.log('')
105
96
  }
@@ -109,18 +100,26 @@ function printDescribe({ node, children }: DescribeResult, path: string): void {
109
100
  }
110
101
  }
111
102
 
103
+ /** `/a/b/c` → `c`; `/` → `/`. */
104
+ function basename(path?: string): string {
105
+ if (!path || path === '/') return path ?? ''
106
+ return path.slice(path.lastIndexOf('/') + 1)
107
+ }
108
+
112
109
  /** Short class name from the contract field `class` (a serialized ClassPath). */
113
- function classNameOf(item: NodeItem): string | undefined {
114
- return item.class ? (ClassPath.tryParse(item.class)?.className ?? undefined) : undefined
110
+ function classNameOf(item: GraphNode | undefined): string | undefined {
111
+ return item?.class ? (ClassPath.tryParse(item.class)?.className ?? undefined) : undefined
115
112
  }
116
113
 
117
- function isFunction(item: NodeItem): boolean {
114
+ function isFunction(item: GraphNode): boolean {
118
115
  return classNameOf(item) === 'Function'
119
116
  }
120
117
 
121
- function printProperties(node: NodeItem): void {
122
- const props = node.properties ?? (node as Record<string, unknown>)
123
- const entries = Object.entries(props).filter(([k]) => k !== '__labels' && k !== 'id')
118
+ function printProperties(node: GraphNode | undefined): void {
119
+ if (!node) return
120
+ const entries = Object.entries(node.props)
121
+ .map(([k, v]) => [unqualifyKey(k), v] as const)
122
+ .filter(([k]) => k !== '__labels' && k !== 'id')
124
123
  if (entries.length === 0) return
125
124
 
126
125
  console.log(` ${chalk.bold('Properties:')}`)
@@ -130,9 +129,8 @@ function printProperties(node: NodeItem): void {
130
129
  }
131
130
  }
132
131
 
133
- function formatInputSchema(properties?: Record<string, unknown>): string {
134
- if (!properties) return '{}'
135
- const raw = properties.inputSchema
132
+ function formatInputSchema(node: GraphNode): string {
133
+ const raw = nodeProp(node, 'inputSchema')
136
134
  if (!raw) return '{}'
137
135
 
138
136
  try {
@@ -160,9 +158,10 @@ export default {
160
158
  description: 'Describe a node: its kind, operations, children, and schemas',
161
159
  afterHelpText: `
162
160
  Behavior:
163
- Raw node dump: full properties + children. For Domain nodes this
164
- includes a multi-kB serialized 'schema' use --no-schema, and pipe
165
- to jq rather than reading by eye (it is not a curated summary).
161
+ One function.get depth:1 the node plus its children (Function-class
162
+ children are shown as Operations). Raw dump: full properties + children.
163
+ For Domain nodes the properties include a multi-kB serialized 'schema'
164
+ use --no-schema, and pipe to jq rather than reading by eye.
166
165
 
167
166
  Examples:
168
167
  $ astrale describe /kernel.astrale.ai
@@ -177,6 +176,6 @@ Examples:
177
176
  },
178
177
  ],
179
178
  action: async (path, opts) => {
180
- await describeCommand(path as string, opts as Parameters<typeof describeCommand>[1])
179
+ await describeCommand(path as string, opts as DescribeOpts)
181
180
  },
182
181
  } satisfies CommandDefinition
@@ -7,8 +7,8 @@ import type { KernelCommandOpts } from '../../kernel'
7
7
  import { AstraleError } from '../../errors'
8
8
  import { runKernelCommand } from '../../kernel'
9
9
  import { withAdminKernelClient } from '../../kernel/client'
10
- import { ADMIN_DOMAIN, type DomainInfo } from '../../lib/admin-domain'
11
- import { ADMIN_INSTANCE, type InstanceInfo } from '../../lib/admin-instance'
10
+ import { adminDomainMethod, type DomainInfo } from '../../lib/admin-domain'
11
+ import { adminInstanceMethod, type InstanceInfo } from '../../lib/admin-instance'
12
12
  import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
13
13
  import { getActive } from '../../lib/instance'
14
14
  import { fatal, log, withSpinner } from '../../lib/log'
@@ -26,7 +26,7 @@ type DomainInstallResult = {
26
26
  error?: string | null
27
27
  }
28
28
 
29
- /** Mirrors the kernel's `Root.installDomain` return. */
29
+ /** Mirrors the kernel's `Domain.install` return. */
30
30
  type DirectInstallResult = { domainId: string; origin: string }
31
31
 
32
32
  type InstallOpts = KernelCommandOpts &
@@ -50,7 +50,7 @@ Behavior:
50
50
  interactively. The target instance is the active one, or -i <slug>; it must be
51
51
  admin-managed (otherwise the command fails loudly and points you at --direct).
52
52
 
53
- --direct: installs a url STRAIGHT onto the instance kernel (Root.installDomain),
53
+ --direct: installs a url STRAIGHT onto the instance kernel (Domain.install),
54
54
  bypassing the admin/catalog. Works on any instance you can authenticate to
55
55
  (managed, bookmarked, or local), using your own authority. This is the only
56
56
  mode that runs the identity-override consent gate: when the domain's declared
@@ -125,7 +125,7 @@ async function installViaAdmin(target: string | undefined, opts: InstallOpts): P
125
125
 
126
126
  try {
127
127
  await withAdminKernelClient(adminOpts, async (ctx) => {
128
- const instances = (await ctx.client.call(`${ADMIN_INSTANCE}/list`, {})) as InstanceInfo[]
128
+ const instances = (await ctx.client.call(adminInstanceMethod('list'), {})) as InstanceInfo[]
129
129
 
130
130
  const ref = await resolveDomainRef(ctx, target, interactive)
131
131
  const slug = await resolveTargetSlug(opts, target, interactive, instances)
@@ -145,7 +145,7 @@ async function installViaAdmin(target: string | undefined, opts: InstallOpts): P
145
145
  `Installing ${label} on ${match.slug}`,
146
146
  !isMachine(opts),
147
147
  () =>
148
- ctx.client.call(`${ADMIN_DOMAIN}/install`, {
148
+ ctx.client.call(adminDomainMethod('install'), {
149
149
  instanceId: match.slug,
150
150
  ...ref,
151
151
  }) as Promise<DomainInstallResult>,
@@ -190,7 +190,7 @@ async function resolveDomainRef(
190
190
  if (target) return domainRefFromTarget(target)
191
191
  if (!interactive) throw new AstraleError('MISSING_ARG', 'No domain given.')
192
192
 
193
- const catalog = (await ctx.client.call(`${ADMIN_DOMAIN}/list`, {})) as DomainInfo[]
193
+ const catalog = (await ctx.client.call(adminDomainMethod('list'), {})) as DomainInfo[]
194
194
  const installable = catalog.filter((d) => d.url)
195
195
  if (installable.length === 0) {
196
196
  throw new AstraleError(
@@ -260,7 +260,7 @@ async function activeSlug(): Promise<string | undefined> {
260
260
  // ── Direct path (--direct) ────────────────────────────────────────────────────
261
261
 
262
262
  /**
263
- * Install a url straight onto the instance kernel (`Root.installDomain`),
263
+ * Install a url straight onto the instance kernel (`Domain.install`),
264
264
  * bypassing the admin/catalog — the classic path, with the §5 identity-override
265
265
  * consent gate. Works on any instance the caller can authenticate to.
266
266
  */
@@ -290,7 +290,7 @@ async function installDirect(target: string | undefined, opts: InstallOpts): Pro
290
290
  opts,
291
291
  label: `Installing domain from ${url}`,
292
292
  fn: async (ctx) =>
293
- (await ctx.client.call(K.Root.installDomain.path.method.raw, {
293
+ (await ctx.client.call(K.Domain.install.path.method.raw, {
294
294
  url,
295
295
  ...(opts.token ? { token: opts.token } : {}),
296
296
  })) as DirectInstallResult,
@@ -5,7 +5,7 @@ import type { KernelCommandOpts } from '../../kernel'
5
5
  import type { ListProjection, RawOutputOpts } from '../../lib/output'
6
6
 
7
7
  import { withAdminKernelClient } from '../../kernel/client'
8
- import { ADMIN_DOMAIN, type DomainInfo } from '../../lib/admin-domain'
8
+ import { adminDomainMethod, type DomainInfo } from '../../lib/admin-domain'
9
9
  import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
10
10
  import { fatal, withSpinner } from '../../lib/log'
11
11
  import { isMachine, presentList } from '../../lib/output'
@@ -102,7 +102,7 @@ Examples:
102
102
  async (): Promise<DomainRow[]> => {
103
103
  const list = await withAdminKernelClient(
104
104
  opts,
105
- async (ctx) => (await ctx.client.call(`${ADMIN_DOMAIN}/list`, {})) as DomainInfo[],
105
+ async (ctx) => (await ctx.client.call(adminDomainMethod('list'), {})) as DomainInfo[],
106
106
  )
107
107
  const filtered = opts.defaultOnly ? list.filter((d) => d.installByDefault) : list
108
108
  filtered.sort(byDefaultThenName)
@@ -4,7 +4,7 @@ import type { CommandDefinition } from '../../command'
4
4
  import type { KernelCommandOpts } from '../../kernel'
5
5
 
6
6
  import { withAdminKernelClient } from '../../kernel/client'
7
- import { ADMIN_DOMAIN, type DomainInfo } from '../../lib/admin-domain'
7
+ import { adminDomainMethod, type DomainInfo } from '../../lib/admin-domain'
8
8
  import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
9
9
  import { fatal, withSpinner } from '../../lib/log'
10
10
  import { isMachine, output } from '../../lib/output'
@@ -115,7 +115,7 @@ Examples:
115
115
  // silently bumping `updatedAt`. `info` throws when absent → treat as
116
116
  // a fresh publish.
117
117
  const existing = (await ctx.client
118
- .call(`${ADMIN_DOMAIN}/info`, { origin })
118
+ .call(adminDomainMethod('info'), { origin })
119
119
  .catch(() => null)) as DomainInfo | null
120
120
  if (
121
121
  existing &&
@@ -127,7 +127,7 @@ Examples:
127
127
  ) {
128
128
  return { entry: existing, changed: false as const, isNew: false }
129
129
  }
130
- const entry = (await ctx.client.call(`${ADMIN_DOMAIN}/publish`, {
130
+ const entry = (await ctx.client.call(adminDomainMethod('publish'), {
131
131
  origin,
132
132
  name,
133
133
  url: publicUrl,
@@ -1,29 +1,46 @@
1
1
  import type { CommandDefinition } from '../command'
2
2
  import type { KernelCommandOpts } from '../kernel'
3
3
 
4
- import { expandSelfInPath, runKernelCommand, withSelfHint } from '../kernel'
4
+ import { bindGraph, expandSelfInPath, runKernelCommand, withSelfHint } from '../kernel'
5
5
  import { log } from '../lib/log'
6
6
  import { output } from '../lib/output'
7
7
 
8
- type GetOpts = KernelCommandOpts & { long?: boolean }
8
+ type GetOpts = KernelCommandOpts & {
9
+ long?: boolean
10
+ }
9
11
 
10
12
  const INTERNAL_KEYS = new Set(['__labels', 'classId'])
11
13
 
12
- export async function getCommand(path: string, opts: GetOpts): Promise<void> {
13
- let expandedPath: string
14
+ export async function getCommand(pathArg: string, opts: GetOpts): Promise<void> {
15
+ if (!pathArg) {
16
+ log.error('Usage: astrale get <path>')
17
+ process.exit(1)
18
+ return
19
+ }
20
+
21
+ let path: string
14
22
  let meta
15
23
  try {
16
- ;({ path: expandedPath, meta } = await expandSelfInPath(path, opts))
24
+ ;({ path, meta } = await expandSelfInPath(pathArg, opts))
17
25
  } catch (e) {
18
26
  log.error(e instanceof Error ? e.message : 'Invalid @self expansion')
19
27
  process.exit(1)
28
+ return
20
29
  }
30
+
21
31
  await runKernelCommand({
22
32
  opts,
23
- label: `Node ${expandedPath}`,
24
- fn: (ctx) => withSelfHint(() => ctx.client.call(`${expandedPath}::get`, {}), meta),
25
- format: (result, fmtOpts) => {
26
- output(opts.long ? result : cleanNode(result), fmtOpts)
33
+ label: 'Node ' + path,
34
+ fn: async (ctx) => {
35
+ const node = await withSelfHint(() => bindGraph(ctx).get(path), meta)
36
+ if (node === null) {
37
+ log.error('node "' + path + '" not found or not visible')
38
+ process.exit(1)
39
+ }
40
+ return node
41
+ },
42
+ format: (node, fmtOpts) => {
43
+ output(opts.long ? node : cleanNode(node), fmtOpts)
27
44
  },
28
45
  })
29
46
  }
@@ -40,21 +57,29 @@ function cleanNode(data: unknown): unknown {
40
57
 
41
58
  export default {
42
59
  name: 'get',
43
- description: 'Get a node by path or ID',
44
- afterHelpText: `
45
- Behavior:
46
- Accepts a tree path (/domain/class.Name) or an id (@nodeId). -l adds
47
- the internal fields (__labels, classId) hidden in the summary view.
48
-
49
- Examples:
50
- $ astrale get /kernel.astrale.ai/class.Root
51
- $ astrale get @abc123 -l
52
- `,
60
+ description: 'Get one node by path or id',
61
+ afterHelpText: [
62
+ '',
63
+ 'Behavior:',
64
+ ' Point read through the kernel-client GraphApi.get shorthand. Accepts exactly',
65
+ ' one node path (tree path /domain/class.Name, or an id @nodeId). @self is',
66
+ ' expanded before dispatch. Missing and masked nodes share one opaque error:',
67
+ ' node "<path>" not found or not visible.',
68
+ '',
69
+ ' Output is the historical flat wire node projection { id, class, path, props }.',
70
+ ' Add -l to keep the internal fields (__labels, classId). Richer reads -',
71
+ ' multiple roots, child expansion, edge expansion, cursors, or full GraphData -',
72
+ ' live on astrale query.',
73
+ '',
74
+ 'Examples:',
75
+ ' $ astrale get /kernel.astrale.ai/class.Root',
76
+ ' $ astrale get @abc123 -l',
77
+ ' $ astrale get @self --json',
78
+ '',
79
+ ].join('\n'),
53
80
  arguments: [{ name: 'path', description: 'Node path (/domain/Class) or ID (@nodeId)' }],
54
- options: [
55
- { flags: '-l, --long', description: 'Include all internal fields (__labels, classId)' },
56
- ],
81
+ options: [{ flags: '-l, --long', description: 'Include internal fields (__labels, classId)' }],
57
82
  action: async (path, opts) => {
58
- await getCommand(path as string, opts as Parameters<typeof getCommand>[1])
83
+ await getCommand(path as string, opts as GetOpts)
59
84
  },
60
85
  } satisfies CommandDefinition