@astrale-os/cli 0.8.1-alpha.0 → 0.8.1-alpha.3
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/dist/astrale.js +63133 -41267
- package/dist/public/connect-core.js +55030 -0
- package/dist/public/keys/index.js +45631 -0
- package/dist/public/paths/index.js +43990 -0
- package/dist/types/admin/instance/model.d.ts +40 -0
- package/dist/types/connect-core.d.ts +22 -0
- package/dist/types/connection/auth.d.ts +33 -0
- package/dist/types/errors.d.ts +23 -0
- package/dist/types/identity/registry.d.ts +25 -0
- package/dist/types/keys/algorithm.d.ts +6 -0
- package/dist/types/keys/credential.d.ts +21 -0
- package/dist/types/keys/index.d.ts +4 -0
- package/dist/types/keys/pair.d.ts +34 -0
- package/dist/types/lib/admin-target.d.ts +74 -0
- package/dist/types/lib/ca-fetch.d.ts +2 -0
- package/dist/types/lib/config.d.ts +71 -0
- package/dist/types/lib/format.d.ts +1 -0
- package/dist/types/lib/idp-session.d.ts +33 -0
- package/dist/types/lib/idp.d.ts +397 -0
- package/dist/types/lib/instance-target.d.ts +36 -0
- package/dist/types/lib/instance.d.ts +186 -0
- package/dist/types/lib/log.d.ts +30 -0
- package/dist/types/lib/login-flow.d.ts +49 -0
- package/dist/types/lib/meta.d.ts +36 -0
- package/dist/types/lib/output.d.ts +83 -0
- package/dist/types/lib/table.d.ts +18 -0
- package/dist/types/lib/validation.d.ts +11 -0
- package/dist/types/paths/index.d.ts +2 -0
- package/dist/types/state/exchange-credentials.d.ts +25 -0
- package/dist/types/state/files.d.ts +9 -0
- package/dist/types/state/identities.d.ts +42 -0
- package/dist/types/state/index.d.ts +8 -0
- package/dist/types/state/paths.d.ts +32 -0
- package/package.json +22 -7
- package/src/admin/.spec/api.d.ts +3 -0
- package/src/admin/.spec/architecture.md +15 -0
- package/src/admin/.spec/icon.svg +3 -0
- package/src/admin/.spec/layout.ts +6 -0
- package/src/admin/catalog/.spec/api.d.ts +65 -0
- package/src/admin/catalog/.spec/architecture.md +6 -0
- package/src/admin/catalog/.spec/icon.svg +3 -0
- package/src/admin/catalog/.spec/layout.ts +6 -0
- package/src/admin/catalog/__tests__/client.test.ts +171 -0
- package/src/admin/catalog/client.ts +214 -0
- package/src/admin/catalog/index.ts +13 -0
- package/src/admin/catalog/model.ts +42 -0
- package/src/admin/catalog/tsconfig.json +8 -0
- package/src/admin/graph/.spec/api.d.ts +28 -0
- package/src/admin/graph/.spec/architecture.md +5 -0
- package/src/admin/graph/.spec/icon.svg +3 -0
- package/src/admin/graph/.spec/layout.ts +3 -0
- package/src/admin/graph/index.ts +6 -0
- package/src/admin/graph/nodes.ts +56 -0
- package/src/admin/index.ts +2 -0
- package/src/admin/instance/.spec/api.d.ts +66 -0
- package/src/admin/instance/.spec/architecture.md +10 -0
- package/src/admin/instance/.spec/icon.svg +3 -0
- package/src/admin/instance/.spec/layout.ts +6 -0
- package/src/admin/instance/__tests__/client.test.ts +289 -0
- package/src/admin/instance/client.ts +298 -0
- package/src/admin/instance/index.ts +16 -0
- package/src/admin/instance/model.ts +65 -0
- package/src/admin/instance/tsconfig.json +8 -0
- package/src/commands/__tests__/admin-instance.test.ts +3 -36
- package/src/commands/__tests__/call-describe.test.ts +67 -0
- package/src/commands/__tests__/call.test.ts +21 -68
- package/src/commands/__tests__/domain-install-owned.test.ts +26 -27
- package/src/commands/__tests__/domain-list.test.ts +2 -6
- package/src/commands/__tests__/install-direct.test.ts +20 -0
- package/src/commands/__tests__/instance-list-admin.test.ts +26 -0
- package/src/commands/__tests__/logs.test.ts +71 -96
- package/src/commands/__tests__/ls.test.ts +21 -17
- package/src/commands/__tests__/read-commands.test.ts +171 -101
- package/src/commands/__tests__/view.test.ts +100 -51
- package/src/commands/admin/status.ts +3 -3
- package/src/commands/admin/use.ts +29 -6
- package/src/commands/auth/login.ts +1 -1
- package/src/commands/auth/logout.ts +4 -2
- package/src/commands/auth/status.ts +2 -2
- package/src/commands/auth/token.ts +2 -2
- package/src/commands/browser.ts +1 -1
- package/src/commands/call-describe.ts +131 -0
- package/src/commands/call.ts +69 -109
- package/src/commands/describe.ts +52 -147
- package/src/commands/domain/install.ts +106 -49
- package/src/commands/domain/list.ts +4 -8
- package/src/commands/domain/publish.ts +9 -31
- package/src/commands/get.ts +33 -66
- package/src/commands/identity/__tests__/register.test.ts +93 -0
- package/src/commands/identity/create.ts +2 -2
- package/src/commands/identity/delete.ts +2 -2
- package/src/commands/identity/export.ts +7 -33
- package/src/commands/identity/import.ts +21 -70
- package/src/commands/identity/list.ts +2 -2
- package/src/commands/identity/register.ts +148 -117
- package/src/commands/identity/sync.ts +2 -2
- package/src/commands/identity/unsync.ts +2 -2
- package/src/commands/identity/use.ts +2 -2
- package/src/commands/identity/whoami.ts +2 -2
- package/src/commands/idp/add.ts +1 -1
- package/src/commands/idp/list.ts +1 -1
- package/src/commands/idp/refresh.ts +1 -1
- package/src/commands/idp/remove.ts +2 -2
- package/src/commands/idp/show.ts +1 -1
- package/src/commands/instance/active.ts +2 -2
- package/src/commands/instance/bookmark.ts +8 -2
- package/src/commands/instance/create.ts +1 -1
- package/src/commands/instance/delete.ts +4 -10
- package/src/commands/instance/forget.ts +1 -1
- package/src/commands/instance/list.ts +39 -6
- package/src/commands/instance/status.ts +5 -7
- package/src/commands/instance/use.ts +3 -3
- package/src/commands/logs.ts +171 -278
- package/src/commands/ls.ts +78 -204
- package/src/commands/mutate.ts +70 -139
- package/src/commands/query.ts +100 -278
- package/src/commands/session/analyze.ts +1 -1
- package/src/commands/session/list.ts +1 -1
- package/src/commands/setup.ts +1 -1
- package/src/commands/status.ts +1 -1
- package/src/commands/studio.ts +1 -1
- package/src/commands/token.ts +26 -21
- package/src/commands/update.ts +1 -1
- package/src/commands/use.ts +2 -3
- package/src/commands/view-serve.ts +1 -1
- package/src/commands/view.ts +67 -109
- package/src/connect-core.test.ts +38 -0
- package/src/connect-core.ts +42 -0
- package/src/connection/.spec/api.d.ts +111 -0
- package/src/connection/.spec/architecture.md +41 -0
- package/src/connection/.spec/capabilities/connection.ts +13 -0
- package/src/connection/.spec/examples/call.ts +7 -0
- package/src/connection/.spec/flows/command.ts +20 -0
- package/src/connection/.spec/flows/session.ts +61 -0
- package/src/connection/.spec/laws/connection.ts +181 -0
- package/src/connection/.spec/layout.ts +19 -0
- package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
- package/src/connection/__tests__/ca-fetch.test.ts +53 -0
- package/src/connection/__tests__/call.test.ts +17 -0
- package/src/connection/__tests__/credential.test.ts +109 -0
- package/src/connection/__tests__/errors.test.ts +96 -0
- package/src/connection/__tests__/exchange.test.ts +224 -0
- package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
- package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
- package/src/connection/__tests__/managed.test.ts +39 -0
- package/src/connection/__tests__/self.test.ts +51 -0
- package/src/connection/__tests__/session.test.ts +140 -0
- package/src/connection/__tests__/target.test.ts +93 -0
- package/src/{kernel → connection}/auth.ts +8 -8
- package/src/connection/call.ts +7 -0
- package/src/{kernel/run.ts → connection/command.ts} +22 -13
- package/src/connection/credential.ts +114 -0
- package/src/{kernel → connection}/errors.ts +32 -9
- package/src/connection/exchange.ts +274 -0
- package/src/connection/index.ts +8 -0
- package/src/connection/self.ts +120 -0
- package/src/connection/session.ts +176 -0
- package/src/connection/target.ts +111 -0
- package/src/connection/tsconfig.json +6 -0
- package/src/graph/.spec/api.d.ts +34 -0
- package/src/graph/.spec/laws/documents.ts +24 -0
- package/src/graph/.spec/laws/projection.ts +13 -0
- package/src/graph/.spec/layout.ts +6 -0
- package/src/graph/__tests__/mutation.test.ts +45 -0
- package/src/graph/__tests__/projection.test.ts +22 -0
- package/src/graph/__tests__/query.test.ts +154 -0
- package/src/graph/index.ts +3 -0
- package/src/graph/mutation.ts +33 -0
- package/src/graph/projection.ts +21 -0
- package/src/graph/query.ts +108 -0
- package/src/graph/tsconfig.json +6 -0
- package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
- package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
- package/src/identity/.spec/api.d.ts +124 -0
- package/src/identity/.spec/architecture.md +33 -0
- package/src/identity/.spec/capabilities/identity.ts +7 -0
- package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
- package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
- package/src/identity/.spec/flows/import.ts +29 -0
- package/src/identity/.spec/laws/identity.ts +56 -0
- package/src/identity/.spec/layout.ts +13 -0
- package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
- package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
- package/src/identity/__tests__/registration.test.ts +64 -0
- package/src/identity/__tests__/registry.test.ts +70 -0
- package/src/identity/__tests__/transfer.test.ts +198 -0
- package/src/identity/index.ts +23 -0
- package/src/identity/registration.ts +72 -0
- package/src/identity/registry.ts +172 -0
- package/src/identity/transfer.ts +187 -0
- package/src/identity/tsconfig.json +6 -0
- package/src/index.ts +1 -0
- package/src/keys/.spec/api.d.ts +74 -0
- package/src/keys/.spec/architecture.md +13 -0
- package/src/keys/.spec/laws/keys.ts +43 -0
- package/src/keys/.spec/layout.ts +6 -0
- package/src/keys/__tests__/algorithm.test.ts +39 -0
- package/src/keys/__tests__/keys.test.ts +168 -0
- package/src/keys/algorithm.ts +33 -0
- package/src/keys/credential.ts +112 -0
- package/src/keys/index.ts +14 -0
- package/src/keys/pair.ts +219 -0
- package/src/keys/tsconfig.json +6 -0
- package/src/lib/__tests__/admin-target.test.ts +67 -7
- package/src/lib/__tests__/binary.test.ts +2 -3
- package/src/lib/__tests__/command-dx.test.ts +2 -2
- package/src/lib/__tests__/config.test.ts +3 -2
- package/src/lib/__tests__/idp.test.ts +4 -6
- package/src/lib/__tests__/instance-target.test.ts +7 -4
- package/src/lib/__tests__/local-status.test.ts +1 -1
- package/src/lib/__tests__/log.test.ts +31 -0
- package/src/lib/__tests__/meta.test.ts +55 -0
- package/src/lib/__tests__/sdk-deps.test.ts +1 -1
- package/src/lib/__tests__/self.test.ts +8 -230
- package/src/lib/__tests__/use-target.test.ts +1 -1
- package/src/lib/__tests__/view-open-intent.test.ts +78 -104
- package/src/lib/__tests__/view-session.test.ts +99 -0
- package/src/lib/admin-domain.ts +39 -25
- package/src/lib/admin-instance.ts +38 -43
- package/src/lib/admin-target.ts +67 -13
- package/src/lib/binary.ts +13 -34
- package/src/lib/browser.ts +1 -1
- package/src/{kernel → lib}/ca-fetch.ts +35 -27
- package/src/lib/config.ts +1 -1
- package/src/lib/idp-session.ts +1 -1
- package/src/lib/idp.ts +3 -8
- package/src/lib/instance-target.ts +12 -8
- package/src/lib/instance.ts +34 -2
- package/src/lib/local-status.ts +2 -2
- package/src/lib/log.ts +3 -1
- package/src/lib/login-flow.ts +32 -5
- package/src/lib/meta.ts +15 -11
- package/src/lib/provision-instance.ts +4 -12
- package/src/lib/self.ts +5 -147
- package/src/lib/update.ts +1 -1
- package/src/lib/use-target.ts +1 -1
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/open-intent.ts +7 -37
- package/src/lib/view/port-allocation.ts +1 -1
- package/src/lib/view/resolve.ts +52 -45
- package/src/lib/view/server.ts +16 -33
- package/src/lib/view/session.ts +51 -22
- package/src/paths/.spec/api.d.ts +13 -0
- package/src/paths/.spec/architecture.md +5 -0
- package/src/paths/.spec/layout.ts +3 -0
- package/src/paths/index.ts +13 -0
- package/src/program/.spec/api.d.ts +42 -0
- package/src/program/.spec/architecture.md +18 -0
- package/src/program/.spec/laws/program.ts +29 -0
- package/src/program/.spec/layout.ts +14 -0
- package/src/program/__tests__/program.test.ts +359 -0
- package/src/program/build.ts +190 -0
- package/src/program/command.ts +42 -0
- package/src/program/index.ts +2 -0
- package/src/program/options.ts +40 -0
- package/src/{registry.ts → program/registry.ts} +5 -1
- package/src/program/tsconfig.json +6 -0
- package/src/setup/steps/admin.ts +18 -3
- package/src/setup/steps/instance.ts +1 -1
- package/src/state/.spec/api.d.ts +133 -0
- package/src/state/.spec/architecture.md +31 -0
- package/src/state/.spec/capabilities/state.ts +7 -0
- package/src/state/.spec/flows/identity-update.ts +22 -0
- package/src/state/.spec/laws/state.ts +73 -0
- package/src/state/.spec/layout.ts +14 -0
- package/src/state/__tests__/exchange-credentials.test.ts +188 -0
- package/src/state/__tests__/files.test.ts +113 -0
- package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
- package/src/state/__tests__/identities.test.ts +142 -0
- package/src/state/__tests__/paths.test.ts +27 -0
- package/src/state/exchange-credentials.ts +219 -0
- package/src/state/files.ts +129 -0
- package/src/state/identities.ts +236 -0
- package/src/state/index.ts +29 -0
- package/src/state/paths.ts +61 -0
- package/src/state/tsconfig.json +6 -0
- package/src/telemetry/__tests__/redact.test.ts +3 -3
- package/src/telemetry/__tests__/trigger.test.ts +1 -1
- package/src/telemetry/settings.ts +1 -1
- package/src/telemetry/store.ts +1 -1
- package/src/test-utils.ts +1 -1
- package/studio/client/dist/assets/{elk-api-DVw5WDTH.js → elk-api-D0cBetPW.js} +1 -1
- package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
- package/studio/client/dist/assets/{index-Dl709Ra1.js → index-bVD2KJgz.js} +4 -4
- package/studio/client/dist/index.html +1 -1
- package/studio/package.json +10 -1
- package/studio/server/introspect/extractor.ts +1 -1
- package/studio/server/state/views.test.ts +3 -7
- package/studio/server/state/views.ts +23 -30
- package/tsconfig.json +1 -1
- package/viewer/dist/main.js +65 -39
- package/src/command.ts +0 -42
- package/src/commands/__tests__/help-contract.test.ts +0 -164
- package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
- package/src/kernel/__tests__/errors.test.ts +0 -43
- package/src/kernel/__tests__/expand.test.ts +0 -123
- package/src/kernel/client.ts +0 -214
- package/src/kernel/expand.ts +0 -192
- package/src/kernel/graph.ts +0 -96
- package/src/kernel/index.ts +0 -29
- package/src/kernel/options.ts +0 -22
- package/src/kernel/types.ts +0 -14
- package/src/lib/__tests__/domain-identity.test.ts +0 -60
- package/src/lib/__tests__/fs-atomic.test.ts +0 -104
- package/src/lib/__tests__/identity.test.ts +0 -79
- package/src/lib/__tests__/keys.test.ts +0 -129
- package/src/lib/domain-identity.ts +0 -49
- package/src/lib/env.ts +0 -49
- package/src/lib/fs-atomic.ts +0 -126
- package/src/lib/identity.ts +0 -256
- package/src/lib/keys.ts +0 -294
- package/src/lib/paths.ts +0 -11
- package/src/program.ts +0 -213
- package/studio/client/dist/assets/index-CTbkDp3z.js +0 -81
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import type { KernelCommandOpts } from '../../kernel'
|
|
3
|
+
import type { KernelCommandOpts } from '../../connection'
|
|
5
4
|
import type { Column } from '../../lib/output'
|
|
5
|
+
import type { CommandDefinition } from '../../program/index'
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { AstraleError } from '../../errors'
|
|
8
|
+
import { listOwnedInstances } from '../../lib/admin-instance'
|
|
8
9
|
import {
|
|
9
10
|
formatInstanceLocation,
|
|
10
11
|
type InstanceInfo,
|
|
@@ -61,9 +62,13 @@ export default {
|
|
|
61
62
|
|
|
62
63
|
let managed: OwnedInstanceInfo[] = []
|
|
63
64
|
if (!opts.bookmarked) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
try {
|
|
66
|
+
managed = await withSpinner('Fetching instances', !isMachine(opts), () =>
|
|
67
|
+
listOwnedInstances(opts),
|
|
68
|
+
)
|
|
69
|
+
} catch (error) {
|
|
70
|
+
throw adminInventoryUnavailable(error)
|
|
71
|
+
}
|
|
67
72
|
}
|
|
68
73
|
if (isMachine(opts)) {
|
|
69
74
|
output(
|
|
@@ -148,3 +153,31 @@ export function buildInstanceRows(
|
|
|
148
153
|
|
|
149
154
|
return rows
|
|
150
155
|
}
|
|
156
|
+
|
|
157
|
+
const ADMIN_INVENTORY_CODES = new Set([
|
|
158
|
+
'TOKEN_EXCHANGE_SOURCE_INVALID',
|
|
159
|
+
'TOKEN_EXCHANGE_SOURCE_EXPIRED',
|
|
160
|
+
'TOKEN_EXCHANGE_UNSUPPORTED',
|
|
161
|
+
'TOKEN_EXCHANGE_DISCOVERY_FAILED',
|
|
162
|
+
'TOKEN_EXCHANGE_PROTOCOL_ERROR',
|
|
163
|
+
'TOKEN_EXCHANGE_INSECURE',
|
|
164
|
+
'ADMIN_DOMAIN_ISSUER_MISSING',
|
|
165
|
+
])
|
|
166
|
+
|
|
167
|
+
/** Admin-managed inventory is not available without a deployed Admin Domain + IdP identity. */
|
|
168
|
+
export function adminInventoryUnavailable(cause: unknown): AstraleError {
|
|
169
|
+
const code = cause instanceof AstraleError ? cause.code : undefined
|
|
170
|
+
if (code !== undefined && ADMIN_INVENTORY_CODES.has(code)) {
|
|
171
|
+
return new AstraleError(
|
|
172
|
+
'ADMIN_INVENTORY_UNAVAILABLE',
|
|
173
|
+
'Managed instance listing needs the Admin Domain and an IdP-backed identity. Admin is not deployed in this environment.',
|
|
174
|
+
'Use `astrale instance list --bookmarked` for local kernel bookmarks. Key-backed identities cannot mint an Admin Domain token.',
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
if (cause instanceof AstraleError) return cause
|
|
178
|
+
return new AstraleError(
|
|
179
|
+
'ADMIN_INVENTORY_UNAVAILABLE',
|
|
180
|
+
cause instanceof Error ? cause.message : String(cause),
|
|
181
|
+
'Use `astrale instance list --bookmarked` for local kernel bookmarks.',
|
|
182
|
+
)
|
|
183
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
3
|
+
import type { KernelCommandOpts } from '../../connection'
|
|
4
|
+
import type { CommandDefinition } from '../../program/index'
|
|
5
5
|
|
|
6
6
|
import { AstraleError } from '../../errors'
|
|
7
|
-
import {
|
|
8
|
-
import { findOwnedInstance } from '../../lib/admin-instance'
|
|
7
|
+
import { statusOwnedInstance } from '../../lib/admin-instance'
|
|
9
8
|
import { ADMIN_TARGET_OPTIONS, type AdminTargetCommandOpts } from '../../lib/admin-target'
|
|
10
9
|
import { fatal, log, withSpinner } from '../../lib/log'
|
|
11
10
|
import { isMachine, output, type RawOutputOpts } from '../../lib/output'
|
|
@@ -20,9 +19,8 @@ export default {
|
|
|
20
19
|
action: async (id: string, opts: StatusOpts) => {
|
|
21
20
|
try {
|
|
22
21
|
const result = await withSpinner(`Fetching instance ${id}`, !isMachine(opts), () =>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (match) return match
|
|
22
|
+
statusOwnedInstance(opts, id).catch((error) => {
|
|
23
|
+
if (!(error instanceof Error) || error.name !== 'NotFoundError') throw error
|
|
26
24
|
throw new AstraleError(
|
|
27
25
|
'INSTANCE_NOT_FOUND',
|
|
28
26
|
`No owned instance matches "${id}".`,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { CommandDefinition } from '../../command'
|
|
2
1
|
import type { OwnedInstanceInfo } from '../../lib/admin-instance'
|
|
2
|
+
import type { CommandDefinition } from '../../program/index'
|
|
3
3
|
|
|
4
4
|
import { AstraleError } from '../../errors'
|
|
5
|
-
import {
|
|
5
|
+
import { getDefault, setDefault } from '../../identity/index'
|
|
6
|
+
import { listOwnedInstances } from '../../lib/admin-instance'
|
|
6
7
|
import { ADMIN_TARGET_OPTIONS } from '../../lib/admin-target'
|
|
7
|
-
import { getDefault, setDefault } from '../../lib/identity'
|
|
8
8
|
import {
|
|
9
9
|
getActive,
|
|
10
10
|
readInstances,
|
package/src/commands/logs.ts
CHANGED
|
@@ -1,347 +1,240 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* `astrale logs` — tail the kernel event journal (function.journal) for the target
|
|
5
|
-
* instance. Defaults to the kernel journal syscall
|
|
6
|
-
* `/kernel.astrale.ai/functions/journal`; `--service <name>` switches to the
|
|
7
|
-
* per-instance `services` domain log buffer (the historical behavior).
|
|
8
|
-
*
|
|
9
|
-
* Target the instance with `-i <instance>` (inherited from withKernelOptions).
|
|
10
|
-
*/
|
|
1
|
+
import { Path } from '@astrale-os/sdk/graph/path'
|
|
2
|
+
import { syscalls } from '@astrale-os/sdk/schema/kernel'
|
|
11
3
|
import chalk from 'chalk'
|
|
12
4
|
|
|
13
|
-
import type {
|
|
14
|
-
import type { ClientContext, KernelCommandOpts } from '../kernel'
|
|
5
|
+
import type { ConnectionContext, KernelCommandOpts } from '../connection'
|
|
15
6
|
import type { Column, ListProjection } from '../lib/output'
|
|
7
|
+
import type { CommandDefinition } from '../program/index'
|
|
16
8
|
|
|
17
|
-
import { runKernelCommand,
|
|
18
|
-
import { fatal, withSpinner } from '../lib/log'
|
|
9
|
+
import { createPathCall, runKernelCommand, withClientSession } from '../connection'
|
|
19
10
|
import { isMachine, output, presentList } from '../lib/output'
|
|
20
11
|
|
|
21
|
-
const
|
|
22
|
-
const DEFAULT_SERVICES_ORIGIN = 'services.astrale.ai'
|
|
12
|
+
const JOURNAL_PATH = Path.project(syscalls.journal.ref).raw
|
|
23
13
|
const DEFAULT_LIMIT = 200
|
|
24
|
-
const FOLLOW_INTERVAL_MS =
|
|
25
|
-
// The journal-read syscall journals its own ops; hide them by default so polling
|
|
26
|
-
// doesn't self-pollute the view. `--all` shows them.
|
|
27
|
-
const SELF_READ_PREFIX = 'op:function.journal:'
|
|
28
|
-
|
|
29
|
-
// The published @astrale-os/kernel-core@0.5.0 JournalFilter has no `cursor` yet
|
|
30
|
-
// (added on the kernel branch). Type against the augmented shape so the
|
|
31
|
-
// incremental cursor compiles against the consumer dependency.
|
|
32
|
-
type EventsParams = JournalFilter & { cursor?: number }
|
|
14
|
+
const FOLLOW_INTERVAL_MS = 2_000
|
|
33
15
|
|
|
34
16
|
type LogsOpts = KernelCommandOpts & {
|
|
35
17
|
since?: string
|
|
36
18
|
until?: string
|
|
37
19
|
topic?: string
|
|
20
|
+
topicPrefix?: string
|
|
38
21
|
principal?: string
|
|
39
22
|
limit?: string
|
|
40
23
|
cursor?: string
|
|
41
24
|
follow?: boolean
|
|
42
|
-
all?: boolean
|
|
43
|
-
timing?: boolean
|
|
44
|
-
// service mode
|
|
45
|
-
service?: string
|
|
46
|
-
tail?: string
|
|
47
|
-
servicesOrigin?: string
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Kernel journal page. The function.journal syscall returns a BARE `JournalEntry[]`;
|
|
52
|
-
* the client derives the next cursor from the max `seq`. We still model a
|
|
53
|
-
* `nextCursor` field so the TTY footer / incremental tail share one shape, and
|
|
54
|
-
* so a future paged kernel response degrades gracefully.
|
|
55
|
-
*/
|
|
56
|
-
type EventsPage = {
|
|
57
|
-
entries: JournalEntry[]
|
|
58
|
-
nextCursor?: number | null
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ── function.journal (default) ───────────────────────────────────
|
|
62
|
-
|
|
63
|
-
/** The highest `seq` across entries, or null when empty. */
|
|
64
|
-
function maxSeq(entries: JournalEntry[]): number | null {
|
|
65
|
-
let max: number | null = null
|
|
66
|
-
for (const e of entries) {
|
|
67
|
-
if (typeof e.seq === 'number' && (max === null || e.seq > max)) max = e.seq
|
|
68
|
-
}
|
|
69
|
-
return max
|
|
70
25
|
}
|
|
71
26
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const obj = (raw ?? {}) as { entries?: unknown; nextCursor?: unknown }
|
|
83
|
-
const entries = Array.isArray(obj.entries) ? (obj.entries as JournalEntry[]) : []
|
|
84
|
-
const nextCursor = typeof obj.nextCursor === 'number' ? obj.nextCursor : maxSeq(entries)
|
|
85
|
-
return { entries, nextCursor }
|
|
27
|
+
export interface JournalRecord {
|
|
28
|
+
readonly sequence: number
|
|
29
|
+
readonly timestamp: string
|
|
30
|
+
readonly topic: string
|
|
31
|
+
readonly payload: unknown
|
|
32
|
+
readonly occurredAt?: string
|
|
33
|
+
readonly committedAt?: string
|
|
34
|
+
readonly principal?: string
|
|
35
|
+
readonly correlationId?: string
|
|
36
|
+
readonly causationId?: string
|
|
86
37
|
}
|
|
87
38
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (opts.topic) params.topic = opts.topic
|
|
92
|
-
if (opts.principal) params.principal = opts.principal as JournalFilter['principal']
|
|
93
|
-
if (opts.since !== undefined) params.since = parseTimeFlag('--since', opts.since)
|
|
94
|
-
if (opts.until !== undefined) params.until = parseTimeFlag('--until', opts.until)
|
|
95
|
-
params.limit = opts.limit !== undefined ? parsePositiveInt('--limit', opts.limit) : DEFAULT_LIMIT
|
|
96
|
-
if (opts.cursor !== undefined) params.cursor = parsePositiveInt('--cursor', opts.cursor)
|
|
97
|
-
return params
|
|
39
|
+
export interface JournalPage {
|
|
40
|
+
readonly records: readonly JournalRecord[]
|
|
41
|
+
readonly cursor?: string
|
|
98
42
|
}
|
|
99
43
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
44
|
+
export interface JournalInput {
|
|
45
|
+
readonly topics?: { readonly exact?: readonly string[]; readonly prefixes?: readonly string[] }
|
|
46
|
+
readonly principal?: string
|
|
47
|
+
readonly since?: string
|
|
48
|
+
readonly until?: string
|
|
49
|
+
readonly cursor?: string
|
|
50
|
+
readonly limit: number
|
|
106
51
|
}
|
|
107
52
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
53
|
+
/** Map flags to the exact public journal syscall input without legacy glob/sequence lowering. */
|
|
54
|
+
export function buildJournalInput(opts: LogsOpts): JournalInput {
|
|
55
|
+
const exact = nonEmpty(opts.topic)
|
|
56
|
+
const prefix = nonEmpty(opts.topicPrefix)
|
|
57
|
+
return {
|
|
58
|
+
...(exact === undefined && prefix === undefined
|
|
59
|
+
? {}
|
|
60
|
+
: {
|
|
61
|
+
topics: {
|
|
62
|
+
...(exact === undefined ? {} : { exact: [exact] }),
|
|
63
|
+
...(prefix === undefined ? {} : { prefixes: [prefix] }),
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
...(nonEmpty(opts.principal) === undefined ? {} : { principal: opts.principal }),
|
|
67
|
+
...(nonEmpty(opts.since) === undefined ? {} : { since: opts.since }),
|
|
68
|
+
...(nonEmpty(opts.until) === undefined ? {} : { until: opts.until }),
|
|
69
|
+
...(nonEmpty(opts.cursor) === undefined ? {} : { cursor: opts.cursor }),
|
|
70
|
+
limit: opts.limit === undefined ? DEFAULT_LIMIT : positiveInteger('--limit', opts.limit),
|
|
112
71
|
}
|
|
113
|
-
return n
|
|
114
72
|
}
|
|
115
73
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
74
|
+
/** Validate the record fields the CLI presentation consumes and retain the opaque cursor. */
|
|
75
|
+
export function acceptJournalPage(input: unknown): JournalPage {
|
|
76
|
+
if (!isRecord(input) || !Array.isArray(input.records)) {
|
|
77
|
+
throw new TypeError('Kernel journal response must contain a records array')
|
|
78
|
+
}
|
|
79
|
+
const records = input.records.map((record, index) => acceptRecord(record, index))
|
|
80
|
+
if (input.cursor !== undefined && typeof input.cursor !== 'string') {
|
|
81
|
+
throw new TypeError('Kernel journal cursor must be text')
|
|
82
|
+
}
|
|
83
|
+
return Object.freeze({
|
|
84
|
+
records: Object.freeze(records),
|
|
85
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
86
|
+
})
|
|
123
87
|
}
|
|
124
88
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (ms >= 500) return chalk.red(s)
|
|
130
|
-
if (ms >= 100) return chalk.yellow(s)
|
|
131
|
-
return chalk.green(s)
|
|
89
|
+
async function fetchPage(context: ConnectionContext, opts: LogsOpts): Promise<JournalPage> {
|
|
90
|
+
return acceptJournalPage(
|
|
91
|
+
await context.session.call(createPathCall(JOURNAL_PATH, buildJournalInput(opts))),
|
|
92
|
+
)
|
|
132
93
|
}
|
|
133
94
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
95
|
+
async function runOnce(opts: LogsOpts): Promise<void> {
|
|
96
|
+
await runKernelCommand({
|
|
97
|
+
opts,
|
|
98
|
+
label: 'Kernel journal',
|
|
99
|
+
fn: (context) => fetchPage(context, opts),
|
|
100
|
+
format: (page, format) => {
|
|
101
|
+
if (isMachine(format) || format.format !== undefined) output(page, format)
|
|
102
|
+
else {
|
|
103
|
+
presentList([...page.records], format, journalProjection)
|
|
104
|
+
if (page.cursor) process.stderr.write(` cursor: ${page.cursor}\n`)
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
})
|
|
144
108
|
}
|
|
145
109
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
110
|
+
async function follow(opts: LogsOpts): Promise<void> {
|
|
111
|
+
await withClientSession(opts, async (context) => {
|
|
112
|
+
let cursor = opts.cursor
|
|
113
|
+
for (;;) {
|
|
114
|
+
const page = await fetchPage(context, { ...opts, cursor })
|
|
115
|
+
for (const record of page.records) printRecord(record)
|
|
116
|
+
cursor = page.cursor ?? cursor
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, FOLLOW_INTERVAL_MS))
|
|
118
|
+
}
|
|
119
|
+
})
|
|
154
120
|
}
|
|
155
121
|
|
|
156
|
-
function
|
|
122
|
+
function journalProjection(records: JournalRecord[]): ListProjection {
|
|
157
123
|
const columns: Column[] = [
|
|
158
|
-
{ key: '
|
|
159
|
-
{ key: '
|
|
160
|
-
{ key: 'topic', header: 'TOPIC', color:
|
|
161
|
-
{ key: 'latency', header: 'LATENCY', color: LATENCY_COLOR },
|
|
162
|
-
...(showTiming ? [{ key: 'steps', header: 'STEPS', color: chalk.dim } as Column] : []),
|
|
124
|
+
{ key: 'sequence', header: 'SEQ', color: chalk.dim },
|
|
125
|
+
{ key: 'timestamp', header: 'TIME', color: chalk.dim },
|
|
126
|
+
{ key: 'topic', header: 'TOPIC', color: chalk.cyan },
|
|
163
127
|
{ key: 'principal', header: 'PRINCIPAL', color: chalk.dim },
|
|
164
128
|
]
|
|
165
129
|
return {
|
|
166
130
|
columns,
|
|
167
|
-
rows:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
topic:
|
|
171
|
-
|
|
172
|
-
...(showTiming ? { steps: timingOf(e) } : {}),
|
|
173
|
-
principal: String(e.event.metadata.principal),
|
|
131
|
+
rows: records.map((record) => ({
|
|
132
|
+
sequence: String(record.sequence),
|
|
133
|
+
timestamp: record.timestamp,
|
|
134
|
+
topic: record.topic,
|
|
135
|
+
principal: record.principal ?? '',
|
|
174
136
|
})),
|
|
175
|
-
paths:
|
|
137
|
+
paths: records.map((record) => String(record.sequence)),
|
|
176
138
|
}
|
|
177
139
|
}
|
|
178
140
|
|
|
179
|
-
|
|
180
|
-
const raw = await ctx.client.call(JOURNAL_FN_PATH, buildEventsParams(opts))
|
|
181
|
-
const page = normalizePage(raw)
|
|
182
|
-
// Strip the journal's own read ops by default (but keep nextCursor past them).
|
|
183
|
-
const entries = opts.all
|
|
184
|
-
? page.entries
|
|
185
|
-
: page.entries.filter((e) => !e.event.topic.startsWith(SELF_READ_PREFIX))
|
|
186
|
-
return { entries, nextCursor: page.nextCursor }
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function printEventLine(e: JournalEntry, showTiming = false): void {
|
|
190
|
-
const ts = new Date(e.event.metadata.timestamp).toISOString()
|
|
191
|
-
const lat = latencyOf(e)
|
|
192
|
-
const steps = showTiming ? timingOf(e) : ''
|
|
141
|
+
function printRecord(record: JournalRecord): void {
|
|
193
142
|
process.stdout.write(
|
|
194
|
-
`${chalk.dim(String(
|
|
143
|
+
`${chalk.dim(String(record.sequence).padStart(6))} ${chalk.dim(record.timestamp)} ${chalk.cyan(record.topic)} ${chalk.dim(record.principal ?? '')}\n`,
|
|
195
144
|
)
|
|
196
145
|
}
|
|
197
146
|
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
147
|
+
function acceptRecord(input: unknown, index: number): JournalRecord {
|
|
148
|
+
if (
|
|
149
|
+
!isRecord(input) ||
|
|
150
|
+
!Number.isSafeInteger(input.sequence) ||
|
|
151
|
+
typeof input.topic !== 'string'
|
|
152
|
+
) {
|
|
153
|
+
throw new TypeError(`Kernel journal record ${index} is invalid`)
|
|
154
|
+
}
|
|
155
|
+
const occurredAt = optionalText(input.occurredAt, index, 'occurredAt')
|
|
156
|
+
const timestamp = optionalText(input.timestamp, index, 'timestamp') ?? occurredAt
|
|
157
|
+
if (timestamp === undefined) {
|
|
158
|
+
throw new TypeError(`Kernel journal record ${index} is missing occurredAt/timestamp`)
|
|
159
|
+
}
|
|
160
|
+
const correlation = isRecord(input.correlation) ? input.correlation : undefined
|
|
161
|
+
const correlationId =
|
|
162
|
+
optionalText(input.correlationId, index, 'correlationId') ??
|
|
163
|
+
optionalText(correlation?.invocationId, index, 'correlation.invocationId')
|
|
164
|
+
const principal = optionalText(input.principal, index, 'principal')
|
|
165
|
+
return Object.freeze({
|
|
166
|
+
sequence: input.sequence as number,
|
|
167
|
+
timestamp,
|
|
168
|
+
topic: input.topic,
|
|
169
|
+
payload: input.payload,
|
|
170
|
+
...(occurredAt === undefined ? {} : { occurredAt }),
|
|
171
|
+
...(optionalText(input.committedAt, index, 'committedAt') === undefined
|
|
172
|
+
? {}
|
|
173
|
+
: { committedAt: input.committedAt as string }),
|
|
174
|
+
...(principal === undefined ? {} : { principal }),
|
|
175
|
+
...(correlationId === undefined ? {} : { correlationId }),
|
|
176
|
+
...(optionalText(input.causationId, index, 'causationId') === undefined
|
|
177
|
+
? {}
|
|
178
|
+
: { causationId: input.causationId as string }),
|
|
216
179
|
})
|
|
217
180
|
}
|
|
218
181
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
await withKernelClient(opts, async (ctx) => {
|
|
226
|
-
let cursor = opts.cursor !== undefined ? parsePositiveInt('--cursor', opts.cursor) : undefined
|
|
227
|
-
for (;;) {
|
|
228
|
-
const page = await fetchEventsPage(ctx, { ...opts, cursor: cursor?.toString() })
|
|
229
|
-
for (const e of page.entries) printEventLine(e, opts.timing)
|
|
230
|
-
if (typeof page.nextCursor === 'number') cursor = page.nextCursor
|
|
231
|
-
await new Promise((resolve) => setTimeout(resolve, FOLLOW_INTERVAL_MS))
|
|
232
|
-
}
|
|
233
|
-
})
|
|
182
|
+
function optionalText(input: unknown, index: number, field: string): string | undefined {
|
|
183
|
+
if (input === undefined) return undefined
|
|
184
|
+
if (typeof input !== 'string') {
|
|
185
|
+
throw new TypeError(`Kernel journal record ${index}.${field} must be text`)
|
|
186
|
+
}
|
|
187
|
+
return input
|
|
234
188
|
}
|
|
235
189
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
190
|
+
function positiveInteger(flag: string, raw: string): number {
|
|
191
|
+
if (!/^\d+$/.test(raw)) throw new TypeError(`${flag} must be a positive integer`)
|
|
192
|
+
const value = Number.parseInt(raw, 10)
|
|
193
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
194
|
+
throw new TypeError(`${flag} must be a positive integer`)
|
|
195
|
+
}
|
|
196
|
+
return value
|
|
241
197
|
}
|
|
242
198
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
access: chalk.cyan,
|
|
247
|
-
debug: chalk.dim,
|
|
199
|
+
function nonEmpty(input: string | undefined): string | undefined {
|
|
200
|
+
const value = input?.trim()
|
|
201
|
+
return value ? value : undefined
|
|
248
202
|
}
|
|
249
203
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
let host = ref.trim()
|
|
253
|
-
if (host.includes('://')) {
|
|
254
|
-
try {
|
|
255
|
-
host = new URL(host).hostname
|
|
256
|
-
} catch {
|
|
257
|
-
// keep the raw value — the call will fail loud with the name
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return host.includes('.') ? (host.split('.')[0] ?? host) : host
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
async function runService(opts: LogsOpts): Promise<void> {
|
|
264
|
-
const name = parseServiceName(opts.service as string)
|
|
265
|
-
const origin = opts.servicesOrigin ?? DEFAULT_SERVICES_ORIGIN
|
|
266
|
-
const tail = opts.tail !== undefined ? Number(opts.tail) : undefined
|
|
267
|
-
if (tail !== undefined && (!Number.isInteger(tail) || tail <= 0)) {
|
|
268
|
-
throw new Error(`--tail needs a positive integer, got "${opts.tail}"`)
|
|
269
|
-
}
|
|
270
|
-
const result = await withSpinner(`Fetching logs for ${name}`, !isMachine(opts), () =>
|
|
271
|
-
withKernelClient(
|
|
272
|
-
opts,
|
|
273
|
-
async (ctx) =>
|
|
274
|
-
(await ctx.client.call(
|
|
275
|
-
`/${origin}/services/${name}::logs`,
|
|
276
|
-
tail !== undefined ? { tail } : {},
|
|
277
|
-
)) as ServiceLogs,
|
|
278
|
-
),
|
|
279
|
-
)
|
|
280
|
-
if (isMachine(opts)) {
|
|
281
|
-
output(result, opts)
|
|
282
|
-
return
|
|
283
|
-
}
|
|
284
|
-
if (result.lines.length === 0) {
|
|
285
|
-
console.log(chalk.dim(`no log lines captured yet for ${result.name}`))
|
|
286
|
-
return
|
|
287
|
-
}
|
|
288
|
-
for (const entry of result.lines) {
|
|
289
|
-
const ts = new Date(entry.ts).toISOString()
|
|
290
|
-
const paint = LEVEL_COLOR[entry.level] ?? ((s: string) => s)
|
|
291
|
-
console.log(`${chalk.dim(ts)} ${paint(entry.level.padEnd(6))} ${entry.line}`)
|
|
292
|
-
}
|
|
204
|
+
function isRecord(input: unknown): input is Record<string, unknown> {
|
|
205
|
+
return input !== null && typeof input === 'object' && !Array.isArray(input)
|
|
293
206
|
}
|
|
294
207
|
|
|
295
208
|
export default {
|
|
296
209
|
name: 'logs',
|
|
297
|
-
description: '
|
|
298
|
-
options: [
|
|
299
|
-
{ flags: '--since <t>', description: 'Events at/after this time (epoch-ms or ISO-8601)' },
|
|
300
|
-
{ flags: '--until <t>', description: 'Events at/before this time (epoch-ms or ISO-8601)' },
|
|
301
|
-
{ flags: '--topic <glob>', description: 'Topic glob (e.g. op:*:failed, graph:node:**)' },
|
|
302
|
-
{ flags: '--principal <id>', description: 'Filter by the triggering identity' },
|
|
303
|
-
{ flags: '--limit <n>', description: `Max entries (default ${DEFAULT_LIMIT})` },
|
|
304
|
-
{ flags: '--cursor <n>', description: 'Start after this journal sequence number' },
|
|
305
|
-
{ flags: '--follow', description: 'Poll for new events (Ctrl-C to stop)' },
|
|
306
|
-
{ flags: '--all', description: 'Include the journal-read syscall ops (hidden by default)' },
|
|
307
|
-
{ flags: '--timing', description: 'Show the per-step dispatch breakdown (auth/authz/exec/…)' },
|
|
308
|
-
{ flags: '--service <name>', description: 'Tail a deployed service log buffer instead' },
|
|
309
|
-
{ flags: '--tail <n>', description: '[--service] lines to return (default 200, max 500)' },
|
|
310
|
-
{
|
|
311
|
-
flags: '--services-origin <origin>',
|
|
312
|
-
description: `[--service] services-domain origin (default ${DEFAULT_SERVICES_ORIGIN})`,
|
|
313
|
-
},
|
|
314
|
-
],
|
|
210
|
+
description: 'Read or follow the authorized Kernel journal',
|
|
315
211
|
afterHelpText: `
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
with the per-step dispatch breakdown (auth/in/authz/resolve/inv/exec/out/fx,
|
|
322
|
-
non-zero only). --follow polls for new entries (client-side, tailing by
|
|
323
|
-
sequence number). The journal-read syscall's own ops are hidden unless --all.
|
|
212
|
+
Behavior:
|
|
213
|
+
Calls the public Kernel journal syscall and emits its { records, cursor }
|
|
214
|
+
page. Topic selection is exact or prefix-based; cursors and timestamps are
|
|
215
|
+
opaque strings owned by the journal backend. --follow reuses one Client Session
|
|
216
|
+
and advances only with the returned cursor.
|
|
324
217
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
domain installed on the target instance.
|
|
218
|
+
Historical event-glob lowering and the application-specific services-domain
|
|
219
|
+
log buffer are not part of the Kernel V2 journal contract.
|
|
328
220
|
|
|
329
221
|
Examples:
|
|
330
|
-
astrale logs -i staging
|
|
331
|
-
astrale logs
|
|
332
|
-
astrale logs
|
|
333
|
-
astrale logs -i staging --since 2026-06-20T00:00:00Z --follow
|
|
334
|
-
astrale logs --service my-notes -i staging --tail 50
|
|
222
|
+
$ astrale logs -i staging --limit 50
|
|
223
|
+
$ astrale logs --topic op:function.failed
|
|
224
|
+
$ astrale logs --topic-prefix op:function. --follow
|
|
335
225
|
`,
|
|
226
|
+
options: [
|
|
227
|
+
{ flags: '--since <timestamp>', description: 'Inclusive journal timestamp lower bound' },
|
|
228
|
+
{ flags: '--until <timestamp>', description: 'Inclusive journal timestamp upper bound' },
|
|
229
|
+
{ flags: '--topic <topic>', description: 'Match one exact topic' },
|
|
230
|
+
{ flags: '--topic-prefix <prefix>', description: 'Match one topic prefix' },
|
|
231
|
+
{ flags: '--principal <id>', description: 'Filter by triggering identity ID' },
|
|
232
|
+
{ flags: '--limit <n>', description: `Maximum records (default: ${DEFAULT_LIMIT})` },
|
|
233
|
+
{ flags: '--cursor <token>', description: 'Resume from an opaque journal cursor' },
|
|
234
|
+
{ flags: '--follow', description: 'Poll using returned cursors until interrupted' },
|
|
235
|
+
],
|
|
336
236
|
action: async (opts: LogsOpts) => {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
await runService(opts)
|
|
340
|
-
} else {
|
|
341
|
-
await runEvents(opts)
|
|
342
|
-
}
|
|
343
|
-
} catch (e) {
|
|
344
|
-
fatal(e)
|
|
345
|
-
}
|
|
237
|
+
if (opts.follow) await follow(opts)
|
|
238
|
+
else await runOnce(opts)
|
|
346
239
|
},
|
|
347
240
|
} satisfies CommandDefinition
|