@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.
- package/LICENSE +202 -0
- package/README.md +2 -2
- package/THIRD-PARTY-NOTICES.md +27 -0
- package/dist/astrale.js +24396 -9632
- package/package.json +24 -22
- package/src/command.ts +2 -0
- package/src/commands/__tests__/admin-instance.test.ts +3 -2
- package/src/commands/__tests__/domain-list.test.ts +6 -2
- package/src/commands/__tests__/help-contract.test.ts +27 -14
- package/src/commands/__tests__/install-identity-override.test.ts +2 -2
- package/src/commands/__tests__/ls.test.ts +1 -1
- package/src/commands/__tests__/read-commands.test.ts +201 -0
- package/src/commands/__tests__/view.test.ts +100 -0
- package/src/commands/call.ts +27 -44
- package/src/commands/describe.ts +57 -58
- package/src/commands/domain/install.ts +9 -9
- package/src/commands/domain/list.ts +2 -2
- package/src/commands/domain/publish.ts +3 -3
- package/src/commands/get.ts +48 -23
- package/src/commands/identity/register.ts +27 -33
- package/src/commands/instance/active.ts +2 -2
- package/src/commands/instance/create.ts +1 -1
- package/src/commands/instance/delete.ts +3 -3
- package/src/commands/instance/list.ts +8 -3
- package/src/commands/instance/status.ts +4 -3
- package/src/commands/instance/use.ts +2 -2
- package/src/commands/logs.ts +8 -8
- package/src/commands/ls.ts +77 -55
- package/src/commands/mutate.ts +191 -0
- package/src/commands/query.ts +307 -20
- package/src/commands/session/analyze.ts +50 -0
- package/src/commands/session/list.ts +36 -0
- package/src/commands/token.ts +4 -7
- package/src/commands/view-serve.ts +26 -0
- package/src/commands/view.ts +614 -0
- package/src/connect-core.test.ts +42 -0
- package/src/connect-core.ts +53 -0
- package/src/kernel/__tests__/auth.test.ts +1 -0
- package/src/kernel/__tests__/expand.test.ts +123 -0
- package/src/kernel/client.ts +33 -37
- package/src/kernel/expand.ts +55 -61
- package/src/kernel/graph.ts +96 -0
- package/src/kernel/index.ts +18 -3
- package/src/kernel/options.ts +1 -1
- package/src/kernel/run.ts +0 -13
- package/src/lib/__tests__/instance-target.test.ts +34 -0
- package/src/lib/__tests__/table.test.ts +1 -1
- package/src/lib/__tests__/view-open-intent.test.ts +308 -0
- package/src/lib/__tests__/view-snapshot.test.ts +77 -0
- package/src/lib/admin-domain.ts +8 -4
- package/src/lib/admin-instance.ts +5 -1
- package/src/lib/config.ts +1 -0
- package/src/lib/domain-identity.ts +1 -1
- package/src/lib/instance-target.ts +5 -0
- package/src/lib/instance.ts +11 -0
- package/src/lib/log.ts +12 -2
- package/src/lib/login-flow.ts +41 -4
- package/src/lib/provision-instance.ts +2 -2
- package/src/lib/self.ts +1 -3
- package/src/lib/view/open-intent.ts +97 -0
- package/src/lib/view/resolve.ts +104 -0
- package/src/lib/view/server.ts +294 -0
- package/src/lib/view/session.ts +123 -0
- package/src/lib/view/snapshot.ts +101 -0
- package/src/program.ts +16 -3
- package/src/registry.ts +1 -1
- package/src/setup/render.ts +1 -3
- package/src/setup/steps/instance.ts +2 -2
- package/src/telemetry/__tests__/gate.test.ts +63 -0
- package/src/telemetry/__tests__/recorder.test.ts +110 -0
- package/src/telemetry/__tests__/redact.test.ts +79 -0
- package/src/telemetry/__tests__/session.test.ts +166 -0
- package/src/telemetry/__tests__/trigger.test.ts +49 -0
- package/src/telemetry/adapters/__tests__/claude-code.test.ts +89 -0
- package/src/telemetry/adapters/__tests__/codex.test.ts +138 -0
- package/src/telemetry/adapters/__tests__/index.test.ts +88 -0
- package/src/telemetry/adapters/claude-code.ts +90 -0
- package/src/telemetry/adapters/codex.ts +160 -0
- package/src/telemetry/adapters/index.ts +45 -0
- package/src/telemetry/adapters/types.ts +21 -0
- package/src/telemetry/analyze.ts +227 -0
- package/src/telemetry/gate.ts +79 -0
- package/src/telemetry/recorder.ts +57 -0
- package/src/telemetry/redact.ts +53 -0
- package/src/telemetry/session.ts +88 -0
- package/src/telemetry/settings.ts +26 -0
- package/src/telemetry/store.ts +91 -0
- package/src/telemetry/trigger.ts +119 -0
- package/src/telemetry/types.ts +64 -0
- package/studio/client/dist/assets/index-CyN5G8IA.js +109 -0
- package/studio/client/dist/assets/index-DKKMHBBC.css +1 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/server/agent/ask.ts +7 -2
- package/studio/server/agent/claude.ts +15 -3
- package/studio/server/agent/runner.ts +4 -1
- package/studio/server/agent/session-id.ts +13 -0
- package/studio/server/api.ts +50 -32
- package/studio/server/cache.ts +17 -6
- package/studio/server/client-package.test.ts +147 -0
- package/studio/server/client-package.ts +242 -0
- package/studio/server/index.ts +13 -0
- package/studio/server/introspect/anatomy-extras.test.ts +91 -0
- package/studio/server/introspect/anatomy-extras.ts +48 -49
- package/studio/server/introspect/anatomy.ts +7 -7
- package/studio/server/introspect/overlay-tsmorph.test.ts +104 -0
- package/studio/server/introspect/overlay-tsmorph.ts +230 -72
- package/studio/server/state/harness-gateway.ts +12 -3
- package/studio/server/state/harness-token.ts +0 -0
- package/studio/server/state/views.test.ts +90 -0
- package/studio/server/state/views.ts +396 -99
- package/studio/server/state/visibility.ts +10 -6
- package/studio/server/view-dev-server.test.ts +111 -0
- package/studio/server/view-dev-server.ts +372 -0
- package/studio/shared/types.ts +57 -10
- package/viewer/dist/index.html +93 -0
- package/viewer/dist/main.js +71 -0
- package/src/kernel/__tests__/remote-routing.test.ts +0 -70
- package/src/kernel/remote-routing.ts +0 -88
- package/studio/client/dist/assets/index-DOwzZAEK.css +0 -1
- package/studio/client/dist/assets/index-wtU0Zxhy.js +0 -183
- package/studio/tsconfig.json +0 -23
|
@@ -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
|
|
35
|
-
*
|
|
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
|
|
34
|
+
* permission error on a hardcoded origin.
|
|
39
35
|
*/
|
|
40
|
-
async function resolveUserClassPath(ctx: {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
const domains =
|
|
45
|
-
.filter((
|
|
46
|
-
.map((
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
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:
|
|
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
|
|
131
|
-
// bearer class may require one
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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(`@${
|
|
140
|
+
const result = (await ctx.client.call(`@${nodeId}::registerIdentity`, {
|
|
147
141
|
signingKey: {
|
|
148
142
|
publicKey: { jwk: publicJwk },
|
|
149
143
|
credential: bootstrap,
|
|
@@ -3,7 +3,7 @@ import chalk from 'chalk'
|
|
|
3
3
|
import type { CommandDefinition } from '../../command'
|
|
4
4
|
|
|
5
5
|
import { withAdminKernelClient } from '../../kernel/client'
|
|
6
|
-
import {
|
|
6
|
+
import { adminInstanceMethod, type InstanceInfo } from '../../lib/admin-instance'
|
|
7
7
|
import { getActive, normalizeInstanceKernelUrl } from '../../lib/instance'
|
|
8
8
|
import { log } from '../../lib/log'
|
|
9
9
|
import { RAW_OUTPUT_OPTIONS, isMachine, output, type RawOutputOpts } from '../../lib/output'
|
|
@@ -51,7 +51,7 @@ async function resolveActiveForDisplay(): Promise<{
|
|
|
51
51
|
const managed = await withAdminKernelClient(
|
|
52
52
|
{},
|
|
53
53
|
async (ctx) =>
|
|
54
|
-
(await ctx.client.call(
|
|
54
|
+
(await ctx.client.call(adminInstanceMethod('info'), { id: active.name })) as InstanceInfo,
|
|
55
55
|
)
|
|
56
56
|
return {
|
|
57
57
|
name: managed.slug,
|
|
@@ -2,7 +2,7 @@ import type { CommandDefinition } from '../../command'
|
|
|
2
2
|
import type { KernelCommandOpts } from '../../kernel'
|
|
3
3
|
|
|
4
4
|
import { withAdminKernelClient } from '../../kernel/client'
|
|
5
|
-
import {
|
|
5
|
+
import { adminInstanceMethod, type InstanceInfo } from '../../lib/admin-instance'
|
|
6
6
|
import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
|
|
7
7
|
import { clearActive, readInstances, removeInstance, resolveInstanceKey } from '../../lib/instance'
|
|
8
8
|
import { fatal, log, withSpinner } from '../../lib/log'
|
|
@@ -40,7 +40,7 @@ Behavior:
|
|
|
40
40
|
withAdminKernelClient(
|
|
41
41
|
opts,
|
|
42
42
|
async (ctx) =>
|
|
43
|
-
(await ctx.client.call(
|
|
43
|
+
(await ctx.client.call(adminInstanceMethod('delete'), { id })) as InstanceInfo,
|
|
44
44
|
),
|
|
45
45
|
{ success: (deleted) => `Deleted instance: ${deleted.slug}` },
|
|
46
46
|
)
|
|
@@ -66,7 +66,7 @@ Behavior:
|
|
|
66
66
|
log.dim(` hint: it's a local bookmark — drop it with: astrale instance forget ${id}`)
|
|
67
67
|
process.exit(1)
|
|
68
68
|
}
|
|
69
|
-
fatal(e)
|
|
69
|
+
fatal(e, opts)
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
} satisfies CommandDefinition
|
|
@@ -5,7 +5,11 @@ import type { KernelCommandOpts } from '../../kernel'
|
|
|
5
5
|
import type { Column } from '../../lib/output'
|
|
6
6
|
|
|
7
7
|
import { withAdminKernelClient } from '../../kernel/client'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
adminInstanceMethod,
|
|
10
|
+
formatInstanceLocation,
|
|
11
|
+
type InstanceInfo,
|
|
12
|
+
} from '../../lib/admin-instance'
|
|
9
13
|
import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
|
|
10
14
|
import { normalizeInstanceKernelUrl, readInstances } from '../../lib/instance'
|
|
11
15
|
import { fatal, log, withSpinner } from '../../lib/log'
|
|
@@ -60,7 +64,8 @@ export default {
|
|
|
60
64
|
managed = await withSpinner('Fetching instances', !isMachine(opts), () =>
|
|
61
65
|
withAdminKernelClient(
|
|
62
66
|
opts,
|
|
63
|
-
async (ctx) =>
|
|
67
|
+
async (ctx) =>
|
|
68
|
+
(await ctx.client.call(adminInstanceMethod('list'), {})) as InstanceInfo[],
|
|
64
69
|
),
|
|
65
70
|
)
|
|
66
71
|
}
|
|
@@ -87,7 +92,7 @@ export default {
|
|
|
87
92
|
}
|
|
88
93
|
process.stdout.write(renderTable(rows, { columns: COLUMNS }) + '\n')
|
|
89
94
|
} catch (e) {
|
|
90
|
-
fatal(e)
|
|
95
|
+
fatal(e, opts)
|
|
91
96
|
}
|
|
92
97
|
},
|
|
93
98
|
} satisfies CommandDefinition
|
|
@@ -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 {
|
|
7
|
+
import { adminInstanceMethod, type InstanceInfo } from '../../lib/admin-instance'
|
|
8
8
|
import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
|
|
9
9
|
import { fatal, log, withSpinner } from '../../lib/log'
|
|
10
10
|
import { isMachine, output, type RawOutputOpts } from '../../lib/output'
|
|
@@ -21,7 +21,8 @@ export default {
|
|
|
21
21
|
const result = await withSpinner(`Fetching instance ${id}`, !isMachine(opts), () =>
|
|
22
22
|
withAdminKernelClient(
|
|
23
23
|
opts,
|
|
24
|
-
async (ctx) =>
|
|
24
|
+
async (ctx) =>
|
|
25
|
+
(await ctx.client.call(adminInstanceMethod('info'), { id })) as InstanceInfo,
|
|
25
26
|
),
|
|
26
27
|
)
|
|
27
28
|
if (isMachine(opts)) {
|
|
@@ -36,7 +37,7 @@ export default {
|
|
|
36
37
|
if (result.hostId) log.dim(` host: ${result.hostId}`)
|
|
37
38
|
if (result.createdAt) log.dim(` created: ${result.createdAt}`)
|
|
38
39
|
} catch (e) {
|
|
39
|
-
fatal(e)
|
|
40
|
+
fatal(e, opts)
|
|
40
41
|
}
|
|
41
42
|
},
|
|
42
43
|
} satisfies CommandDefinition
|
|
@@ -2,7 +2,7 @@ import type { CommandDefinition } from '../../command'
|
|
|
2
2
|
|
|
3
3
|
import { AstraleError } from '../../errors'
|
|
4
4
|
import { withAdminKernelClient } from '../../kernel/client'
|
|
5
|
-
import {
|
|
5
|
+
import { adminInstanceMethod, type InstanceInfo } from '../../lib/admin-instance'
|
|
6
6
|
import { ADMIN_TARGET_OPTIONS } from '../../lib/admin-target'
|
|
7
7
|
import { getDefault, setDefault } from '../../lib/identity'
|
|
8
8
|
import {
|
|
@@ -142,7 +142,7 @@ async function fetchManagedInstances(name: string, opts: UseOpts): Promise<Insta
|
|
|
142
142
|
try {
|
|
143
143
|
return await withAdminKernelClient(
|
|
144
144
|
opts,
|
|
145
|
-
async (ctx) => (await ctx.client.call(
|
|
145
|
+
async (ctx) => (await ctx.client.call(adminInstanceMethod('list'), {})) as InstanceInfo[],
|
|
146
146
|
)
|
|
147
147
|
} catch {
|
|
148
148
|
return []
|
package/src/commands/logs.ts
CHANGED
|
@@ -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 (
|
|
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/
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
// ──
|
|
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(
|
|
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 ${
|
|
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
|
package/src/commands/ls.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import { rawOf } from '@astrale-os/kernel-client/graph'
|
|
1
2
|
import chalk from 'chalk'
|
|
2
3
|
|
|
3
4
|
import type { CommandDefinition } from '../command'
|
|
4
|
-
import type {
|
|
5
|
+
import type { GraphNode, KernelCommandOpts, SelfExpansionMeta } from '../kernel'
|
|
5
6
|
import type { ListProjection } from '../lib/output'
|
|
6
7
|
|
|
7
8
|
import {
|
|
9
|
+
bindGraph,
|
|
10
|
+
childrenCursor,
|
|
8
11
|
expandSelfInPath,
|
|
9
|
-
extractItems,
|
|
10
12
|
formatKernelError,
|
|
11
13
|
runKernelCommand,
|
|
14
|
+
splitRoot,
|
|
12
15
|
withKernelClient,
|
|
13
16
|
withSelfHint,
|
|
14
17
|
} from '../kernel'
|
|
@@ -23,25 +26,19 @@ type LsOpts = KernelCommandOpts & {
|
|
|
23
26
|
filter?: string
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
/** A child node
|
|
27
|
-
type Item =
|
|
28
|
-
id?: string
|
|
29
|
-
class?: string
|
|
30
|
-
path?: string
|
|
31
|
-
props?: Record<string, unknown>
|
|
32
|
-
__labels?: string[]
|
|
33
|
-
}
|
|
29
|
+
/** A child node row — the shared `function.get` node shape. */
|
|
30
|
+
type Item = GraphNode
|
|
34
31
|
|
|
35
32
|
// ── Display projection ──────────────────────────────────────
|
|
36
33
|
|
|
37
|
-
/** `/
|
|
34
|
+
/** `/example.astrale.ai` → `example.astrale.ai`; `/` stays `/`. */
|
|
38
35
|
export function basename(path?: string): string {
|
|
39
36
|
if (!path || path === '/') return path ?? ''
|
|
40
37
|
return path.slice(path.lastIndexOf('/') + 1)
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
/** `/:kernel.astrale.ai:class.Domain` → `Domain`; falls back to the most specific label. */
|
|
44
|
-
export function classNameOf(item:
|
|
41
|
+
export function classNameOf(item: { class?: string; __labels?: string[] }): string {
|
|
45
42
|
const tail = item.class?.split(/[/:.]/).pop()
|
|
46
43
|
return tail || item.__labels?.[item.__labels.length - 1] || '?'
|
|
47
44
|
}
|
|
@@ -92,14 +89,36 @@ export async function lsCommand(path: string, opts: LsOpts): Promise<void> {
|
|
|
92
89
|
await runKernelCommand({
|
|
93
90
|
opts,
|
|
94
91
|
label: `Children of ${expandedPath}`,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
// Flat listing = ONE function.get depth:1; the root is dropped, the rest are
|
|
93
|
+
// the direct children (replaces the removed `::listChildren` syscall).
|
|
94
|
+
fn: async (ctx) => {
|
|
95
|
+
const result = await withSelfHint(
|
|
96
|
+
() => bindGraph(ctx).query((q) => q.from(expandedPath).children()),
|
|
97
|
+
meta,
|
|
98
|
+
)
|
|
99
|
+
return {
|
|
100
|
+
children: splitRoot(result.wire.nodes, expandedPath).children,
|
|
101
|
+
next: childrenCursor(result.wire),
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
format: ({ children, next }, fmtOpts) => {
|
|
105
|
+
const items = applyFilter(children, opts.filter)
|
|
98
106
|
presentList(
|
|
99
107
|
items,
|
|
100
108
|
{ ...fmtOpts, quiet: opts.quiet, count: opts.count, long: opts.long },
|
|
101
109
|
lsProjection,
|
|
102
110
|
)
|
|
111
|
+
if (next && !isMachine(fmtOpts) && !opts.quiet && !opts.count) {
|
|
112
|
+
process.stdout.write(
|
|
113
|
+
chalk.dim(
|
|
114
|
+
' more children truncated - page with `query ' +
|
|
115
|
+
expandedPath +
|
|
116
|
+
' --depth 1 --children \'{"cursor":"' +
|
|
117
|
+
next +
|
|
118
|
+
'"}\'`\n',
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
}
|
|
103
122
|
},
|
|
104
123
|
})
|
|
105
124
|
}
|
|
@@ -107,9 +126,8 @@ export async function lsCommand(path: string, opts: LsOpts): Promise<void> {
|
|
|
107
126
|
// ── Recursive tree ──────────────────────────────────────────
|
|
108
127
|
|
|
109
128
|
const MAX_DEPTH = 5
|
|
110
|
-
const MAX_NODES = 200
|
|
111
129
|
|
|
112
|
-
type TreeNode = Item & { children
|
|
130
|
+
type TreeNode = Item & { children: TreeNode[] }
|
|
113
131
|
|
|
114
132
|
async function recursiveLs(
|
|
115
133
|
path: string,
|
|
@@ -121,8 +139,13 @@ async function recursiveLs(
|
|
|
121
139
|
|
|
122
140
|
try {
|
|
123
141
|
await withKernelClient(opts, async (ctx) => {
|
|
124
|
-
|
|
125
|
-
|
|
142
|
+
// The recursive walk is now ONE function.get to the depth cap; the tree is
|
|
143
|
+
// reassembled client-side from the flat node page (was an N+1 buildTree).
|
|
144
|
+
const result = await withSelfHint(
|
|
145
|
+
() => bindGraph(ctx).query((q) => q.from(path).descend(MAX_DEPTH)),
|
|
146
|
+
meta,
|
|
147
|
+
)
|
|
148
|
+
const tree = buildTree(result.wire.nodes, path)
|
|
126
149
|
spin?.succeed(`Tree of ${path}`)
|
|
127
150
|
if (!machine) console.log('')
|
|
128
151
|
|
|
@@ -141,32 +164,30 @@ async function recursiveLs(
|
|
|
141
164
|
}
|
|
142
165
|
}
|
|
143
166
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
path
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return nodes
|
|
167
|
-
} catch {
|
|
168
|
-
return []
|
|
167
|
+
/** Parent absolute path of an absolute node path (`/a/b` → `/a`; `/a` → `/`). */
|
|
168
|
+
function parentPathOf(path: string): string {
|
|
169
|
+
const i = path.lastIndexOf('/')
|
|
170
|
+
return i <= 0 ? '/' : path.slice(0, i)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Reassemble the flat `function.get` node page into a tree rooted at `rootPath`. */
|
|
174
|
+
function buildTree(nodes: readonly GraphNode[], rootPath: string): TreeNode[] {
|
|
175
|
+
const rootRaw = rawOf(rootPath)
|
|
176
|
+
const byPath = new Map<string, TreeNode>()
|
|
177
|
+
for (const n of nodes) {
|
|
178
|
+
const raw = rawOf(n.path)
|
|
179
|
+
if (raw === rootRaw) continue
|
|
180
|
+
byPath.set(raw, { ...n, children: [] })
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const roots: TreeNode[] = []
|
|
184
|
+
for (const [raw, node] of byPath) {
|
|
185
|
+
const parent = parentPathOf(raw)
|
|
186
|
+
const parentNode = parent === rootRaw ? undefined : byPath.get(parent)
|
|
187
|
+
if (parentNode) parentNode.children.push(node)
|
|
188
|
+
else roots.push(node)
|
|
169
189
|
}
|
|
190
|
+
return roots
|
|
170
191
|
}
|
|
171
192
|
|
|
172
193
|
function printTree(nodes: TreeNode[], prefix: string): void {
|
|
@@ -179,7 +200,7 @@ function printTree(nodes: TreeNode[], prefix: string): void {
|
|
|
179
200
|
const name = basename(node.path) || node.id || '?'
|
|
180
201
|
console.log(`${prefix}${connector}${chalk.cyan(name)} ${chalk.dim(classNameOf(node))}`)
|
|
181
202
|
|
|
182
|
-
if (node.children
|
|
203
|
+
if (node.children.length > 0) {
|
|
183
204
|
printTree(node.children, prefix + childPrefix)
|
|
184
205
|
}
|
|
185
206
|
}
|
|
@@ -188,7 +209,7 @@ function printTree(nodes: TreeNode[], prefix: string): void {
|
|
|
188
209
|
function printTreeQuiet(nodes: TreeNode[]): void {
|
|
189
210
|
for (const node of nodes) {
|
|
190
211
|
process.stdout.write(itemPath(node) + '\n')
|
|
191
|
-
if (node.children) printTreeQuiet(node.children)
|
|
212
|
+
if (node.children.length > 0) printTreeQuiet(node.children)
|
|
192
213
|
}
|
|
193
214
|
}
|
|
194
215
|
|
|
@@ -197,13 +218,14 @@ export default {
|
|
|
197
218
|
description: 'List children of a node',
|
|
198
219
|
afterHelpText: `
|
|
199
220
|
Behavior:
|
|
200
|
-
Default output is a NAME/KIND/ID table on a TTY, JSON when piped.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
221
|
+
Default output is a NAME/KIND/ID table on a TTY, JSON when piped. One
|
|
222
|
+
function.get depth:1 fetches the direct children; -R fetches the whole
|
|
223
|
+
subtree (to depth ${MAX_DEPTH}) in a SINGLE call and renders it as a tree.
|
|
224
|
+
--filter matches a child KIND or label (post-filter): Folder, Function,
|
|
225
|
+
Domain. At a domain's tree position the children are Folder nodes
|
|
226
|
+
(class.X), not Class — so --filter Class returns nothing; use --filter
|
|
227
|
+
Folder, or descend into class.<X>. -q prints one absolute path per line
|
|
228
|
+
(pipeable).
|
|
207
229
|
|
|
208
230
|
Examples:
|
|
209
231
|
$ astrale ls /
|
|
@@ -220,10 +242,10 @@ Examples:
|
|
|
220
242
|
{ flags: '--count', description: 'Print only the number of children' },
|
|
221
243
|
{
|
|
222
244
|
flags: '--filter <kind>',
|
|
223
|
-
description: 'Filter children by kind or label (e.g., Folder,
|
|
245
|
+
description: 'Filter children by kind or label (e.g., Folder, Function, Domain)',
|
|
224
246
|
},
|
|
225
247
|
],
|
|
226
248
|
action: async (path, opts) => {
|
|
227
|
-
await lsCommand((path as string | undefined) ?? '/', opts as
|
|
249
|
+
await lsCommand((path as string | undefined) ?? '/', opts as LsOpts)
|
|
228
250
|
},
|
|
229
251
|
} satisfies CommandDefinition
|