@astrale-os/cli 0.4.0-alpha.13 → 0.5.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 (46) 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 +8687 -6398
  5. package/package.json +19 -21
  6. package/src/commands/__tests__/help-contract.test.ts +27 -14
  7. package/src/commands/__tests__/install-identity-override.test.ts +2 -2
  8. package/src/commands/__tests__/ls.test.ts +1 -1
  9. package/src/commands/__tests__/read-commands.test.ts +201 -0
  10. package/src/commands/call.ts +27 -44
  11. package/src/commands/describe.ts +57 -58
  12. package/src/commands/domain/install.ts +4 -4
  13. package/src/commands/get.ts +48 -23
  14. package/src/commands/identity/register.ts +27 -33
  15. package/src/commands/logs.ts +8 -8
  16. package/src/commands/ls.ts +77 -55
  17. package/src/commands/mutate.ts +191 -0
  18. package/src/commands/query.ts +288 -20
  19. package/src/commands/token.ts +4 -7
  20. package/src/kernel/__tests__/expand.test.ts +123 -0
  21. package/src/kernel/client.ts +3 -13
  22. package/src/kernel/expand.ts +52 -59
  23. package/src/kernel/graph.ts +96 -0
  24. package/src/kernel/index.ts +11 -2
  25. package/src/kernel/run.ts +0 -13
  26. package/src/lib/__tests__/table.test.ts +1 -1
  27. package/src/lib/admin-domain.ts +3 -3
  28. package/src/lib/domain-identity.ts +1 -1
  29. package/src/lib/self.ts +1 -3
  30. package/src/program.ts +5 -3
  31. package/src/setup/render.ts +1 -3
  32. package/studio/client/dist/assets/index-BcejyJpa.css +1 -0
  33. package/studio/client/dist/assets/index-Cqz3Oy_B.js +179 -0
  34. package/studio/client/dist/index.html +2 -2
  35. package/studio/server/agent/ask.ts +5 -1
  36. package/studio/server/agent/claude.ts +15 -3
  37. package/studio/server/api.ts +7 -7
  38. package/studio/server/introspect/overlay-tsmorph.ts +108 -50
  39. package/studio/server/state/harness-gateway.ts +12 -3
  40. package/studio/server/state/harness-token.ts +0 -0
  41. package/studio/server/state/visibility.ts +5 -1
  42. package/src/kernel/__tests__/remote-routing.test.ts +0 -70
  43. package/src/kernel/remote-routing.ts +0 -88
  44. package/studio/client/dist/assets/index-DOwzZAEK.css +0 -1
  45. package/studio/client/dist/assets/index-wtU0Zxhy.js +0 -183
  46. package/studio/tsconfig.json +0 -23
@@ -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
@@ -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
@@ -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,
@@ -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
@@ -1,11 +1,10 @@
1
- import { K } from '@astrale-os/kernel-core'
2
1
  import { importJWK, SignJWT, type JWK } from 'jose'
3
2
  import { readFile } from 'node:fs/promises'
4
3
 
5
4
  import type { CommandDefinition } from '../../command'
6
- import type { KernelCommandOpts } from '../../kernel'
5
+ import type { ClientContext, KernelCommandOpts } from '../../kernel'
7
6
 
8
- import { runKernelCommand } from '../../kernel'
7
+ import { bindGraph, runKernelCommand } from '../../kernel'
9
8
  import { KERNEL_PASSTHROUGH_OPTIONS } from '../../kernel/options'
10
9
  import { getIdentity, setRegistration } from '../../lib/identity'
11
10
  import { getActive } from '../../lib/instance'
@@ -13,7 +12,6 @@ import { fileExists, keypairPaths } from '../../lib/keys'
13
12
  import { fatal, log } from '../../lib/log'
14
13
  import { output } from '../../lib/output'
15
14
 
16
- type CreateNodeResult = { id: string; path: string }
17
15
  type RegisterIdentityResult = { iss: string; sub: string }
18
16
 
19
17
  type RegisterOpts = KernelCommandOpts & {
@@ -27,37 +25,32 @@ async function readJwk(path: string): Promise<JWK> {
27
25
  return JSON.parse(raw) as JWK
28
26
  }
29
27
 
30
- type ChildNode = { class?: string; path?: string }
31
-
32
28
  /**
33
29
  * Discover the identity-bearer class on the target instance: find the
34
- * installed distribution domain (whatever its origin `dist.astrale.ai` in
35
- * prod, `dist.localhost` locally) and address its `User` class semantically.
30
+ * installed domain that owns a `User` class (workspace on managed instances)
31
+ * and address it semantically.
36
32
  * The bearer class must exist before a non-root identity can be registered;
37
33
  * failing here names the real problem instead of surfacing as a confusing
38
- * permission error on a hardcoded prod origin.
34
+ * permission error on a hardcoded origin.
39
35
  */
40
- async function resolveUserClassPath(ctx: {
41
- client: { call(path: string, params: unknown): Promise<unknown> }
42
- }): Promise<string> {
43
- const children = (await ctx.client.call('/::listChildren', {})) as ChildNode[]
44
- const domains = children
45
- .filter((c) => typeof c.class === 'string' && c.class.endsWith(':class.Domain'))
46
- .map((c) => (c.path ?? '').replace(/^\//, ''))
36
+ async function resolveUserClassPath(ctx: ClientContext): Promise<string> {
37
+ const graph = bindGraph(ctx)
38
+ // Root's direct children are the installed Domain nodes.
39
+ const result = await graph.children('/')
40
+ const domains = result.nodes
41
+ .filter((n) => n.class.className === 'Domain')
42
+ .map((n) => n.path.raw.replace(/^\//, ''))
47
43
  .filter(Boolean)
48
44
  for (const origin of domains) {
49
- try {
50
- // The class materializes as a `class.User` Folder under the domain mount;
51
- // a resolvable read means the domain declares it.
52
- await ctx.client.call(`/${origin}/class.User::get`, {})
45
+ // The class materializes as a `class.User` Folder under the domain mount;
46
+ // a resolving node means the domain declares it (null when absent).
47
+ if (await graph.get(`/${origin}/class.User`)) {
53
48
  return `/:${origin}:class.User`
54
- } catch {
55
- // This domain has no User class — try the next one.
56
49
  }
57
50
  }
58
51
  throw new Error(
59
52
  'No installed domain declares a `User` class on this instance — registering a ' +
60
- 'non-root identity needs an identity-bearer class (install the distribution ' +
53
+ 'non-root identity needs an identity-bearer class (install workspace or another ' +
61
54
  'domain, or pass --class <classPath> explicitly).',
62
55
  )
63
56
  }
@@ -85,7 +78,7 @@ export default {
85
78
  {
86
79
  flags: '--class <classPath>',
87
80
  description:
88
- 'Class path of the identity node to create (default: /:dist.<origin>:class.User when distribution is installed)',
81
+ 'Class path of the identity node to create (default: the installed domain that declares class.User)',
89
82
  },
90
83
  {
91
84
  flags: '--path <nodePath>',
@@ -127,23 +120,24 @@ export default {
127
120
 
128
121
  const classPath = opts.class ?? (await resolveUserClassPath(ctx))
129
122
 
130
- // A bare `astrale identity create <name>` carries no profile, but the
131
- // bearer class may require one (distribution's User wants
132
- // firstName/lastName) — default both to the identity name so a dev
123
+ // A bare `astrale identity create <name>` carries no profile, but a
124
+ // User bearer class may require one. Default both names so a dev
133
125
  // registration works out of the box; `--props` overrides.
134
126
  const extraProps = opts.props ? (JSON.parse(opts.props) as Record<string, unknown>) : {}
135
127
  const userDefaults = classPath.endsWith(':class.User')
136
128
  ? { firstName: name, lastName: name }
137
129
  : {}
138
130
 
139
- const node = (await ctx.client.call(K.Node.createNode.path.method.raw, {
140
- class: classPath,
141
- path: nodePath,
142
- props: { 'Statused.status': 'creating', ...userDefaults, ...extraProps },
143
- })) as CreateNodeResult
131
+ // One-arm create through function.mutate; the minted node id comes
132
+ // back in createdNodes (keyed by the `at` path).
133
+ const nodeId = await bindGraph(ctx).createNode(classPath, nodePath, {
134
+ 'Statused.status': 'creating',
135
+ ...userDefaults,
136
+ ...extraProps,
137
+ })
144
138
 
145
139
  const bootstrap = await mintBootstrapJwt(privateJwk)
146
- const result = (await ctx.client.call(`@${node.id}::registerIdentity`, {
140
+ const result = (await ctx.client.call(`@${nodeId}::registerIdentity`, {
147
141
  signingKey: {
148
142
  publicKey: { jwk: publicJwk },
149
143
  credential: bootstrap,
@@ -1,9 +1,9 @@
1
1
  import type { JournalEntry, JournalFilter } from '@astrale-os/kernel-core'
2
2
 
3
3
  /**
4
- * `astrale logs` — tail the kernel event journal (Root.journal) for the target
4
+ * `astrale logs` — tail the kernel event journal (function.journal) for the target
5
5
  * instance. Defaults to the kernel journal syscall
6
- * `/kernel.astrale.ai/class.Root/journal`; `--service <name>` switches to the
6
+ * `/kernel.astrale.ai/functions/journal`; `--service <name>` switches to the
7
7
  * per-instance `services` domain log buffer (the historical behavior).
8
8
  *
9
9
  * Target the instance with `-i <instance>` (inherited from withKernelOptions).
@@ -18,13 +18,13 @@ import { runKernelCommand, withKernelClient } from '../kernel'
18
18
  import { fatal, withSpinner } from '../lib/log'
19
19
  import { isMachine, output, presentList } from '../lib/output'
20
20
 
21
- const ROOT_JOURNAL_PATH = '/kernel.astrale.ai/class.Root/journal'
21
+ const JOURNAL_FN_PATH = '/kernel.astrale.ai/functions/journal'
22
22
  const DEFAULT_SERVICES_ORIGIN = 'services.astrale.ai'
23
23
  const DEFAULT_LIMIT = 200
24
24
  const FOLLOW_INTERVAL_MS = 2000
25
25
  // The journal-read syscall journals its own ops; hide them by default so polling
26
26
  // doesn't self-pollute the view. `--all` shows them.
27
- const SELF_READ_PREFIX = 'op:class.Root.method.journal:'
27
+ const SELF_READ_PREFIX = 'op:function.journal:'
28
28
 
29
29
  // The published @astrale-os/kernel-core@0.5.0 JournalFilter has no `cursor` yet
30
30
  // (added on the kernel branch). Type against the augmented shape so the
@@ -48,7 +48,7 @@ type LogsOpts = KernelCommandOpts & {
48
48
  }
49
49
 
50
50
  /**
51
- * Kernel journal page. The Root.journal syscall returns a BARE `JournalEntry[]`;
51
+ * Kernel journal page. The function.journal syscall returns a BARE `JournalEntry[]`;
52
52
  * the client derives the next cursor from the max `seq`. We still model a
53
53
  * `nextCursor` field so the TTY footer / incremental tail share one shape, and
54
54
  * so a future paged kernel response degrades gracefully.
@@ -58,7 +58,7 @@ type EventsPage = {
58
58
  nextCursor?: number | null
59
59
  }
60
60
 
61
- // ── Root.journal (default) ───────────────────────────────────
61
+ // ── function.journal (default) ───────────────────────────────────
62
62
 
63
63
  /** The highest `seq` across entries, or null when empty. */
64
64
  function maxSeq(entries: JournalEntry[]): number | null {
@@ -177,7 +177,7 @@ function eventsProjection(entries: JournalEntry[], showTiming = false): ListProj
177
177
  }
178
178
 
179
179
  async function fetchEventsPage(ctx: ClientContext, opts: LogsOpts): Promise<EventsPage> {
180
- const raw = await ctx.client.call(ROOT_JOURNAL_PATH, buildEventsParams(opts))
180
+ const raw = await ctx.client.call(JOURNAL_FN_PATH, buildEventsParams(opts))
181
181
  const page = normalizePage(raw)
182
182
  // Strip the journal's own read ops by default (but keep nextCursor past them).
183
183
  const entries = opts.all
@@ -313,7 +313,7 @@ export default {
313
313
  },
314
314
  ],
315
315
  afterHelpText: `
316
- Default: tails the kernel event journal via ${ROOT_JOURNAL_PATH} on the target
316
+ Default: tails the kernel event journal via ${JOURNAL_FN_PATH} on the target
317
317
  instance (-i <instance>). Topics use ':'-segmented globs ('*' one segment,
318
318
  '**' zero-or-more). Machine output (--json / pipe) emits the JournalEntry[]
319
319
  array; a TTY shows a SEQ/TIME/TOPIC/LATENCY/PRINCIPAL table (LATENCY is the