@astrale-os/cli 1.0.0-beta.3 → 1.0.0-beta.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -6
- package/dist/astrale.js +55471 -58674
- package/dist/public/connect-core.js +2158 -30706
- package/dist/public/keys/index.js +1016 -29541
- package/dist/public/paths/index.js +370 -28654
- package/dist/types/admin/contract.d.ts +26 -0
- package/dist/types/admin/instance/client.d.ts +20 -0
- package/dist/types/admin/instance/index.d.ts +2 -0
- package/dist/types/admin/instance/model.d.ts +6 -10
- package/dist/types/connection/auth.d.ts +3 -0
- package/dist/types/connection/call.d.ts +3 -0
- package/dist/types/connection/command.d.ts +27 -0
- package/dist/types/connection/credential.d.ts +14 -0
- package/dist/types/connection/errors.d.ts +1 -0
- package/dist/types/connection/exchange.d.ts +9 -0
- package/dist/types/connection/failure/classify.d.ts +2 -0
- package/dist/types/connection/failure/index.d.ts +5 -0
- package/dist/types/connection/failure/model.d.ts +25 -0
- package/dist/types/connection/failure/render.d.ts +3 -0
- package/dist/types/connection/index.d.ts +8 -0
- package/dist/types/connection/reasons.d.ts +37 -0
- package/dist/types/connection/self.d.ts +37 -0
- package/dist/types/connection/session.d.ts +31 -0
- package/dist/types/connection/target.d.ts +38 -0
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/lib/admin-instance.d.ts +17 -0
- package/dist/types/lib/admin-target.d.ts +11 -36
- package/dist/types/lib/config.d.ts +13 -59
- package/dist/types/lib/failure-debug.d.ts +1 -0
- package/dist/types/lib/idp.d.ts +25 -173
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/instance.d.ts +30 -88
- package/dist/types/lib/log.d.ts +5 -3
- package/dist/types/lib/output.d.ts +1 -1
- package/dist/types/lib/proc.d.ts +43 -0
- package/dist/types/lib/self.d.ts +9 -0
- package/dist/types/lib/update.d.ts +109 -0
- package/dist/types/lib/validation.d.ts +4 -1
- package/dist/types/state/exchange-credentials.d.ts +6 -2
- package/dist/types/state/files.d.ts +2 -0
- package/dist/types/state/index.d.ts +3 -2
- package/dist/types/state/paths.d.ts +2 -0
- package/dist/types/state/session-routes.d.ts +10 -0
- package/package.json +16 -19
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/.spec/architecture.md +12 -5
- package/src/admin/__tests__/fixture.ts +25 -0
- package/src/admin/catalog/.spec/api.d.ts +1 -3
- package/src/admin/catalog/.spec/architecture.md +5 -4
- package/src/admin/catalog/__tests__/client.test.ts +143 -69
- package/src/admin/catalog/client.ts +40 -54
- package/src/admin/catalog/model.ts +3 -4
- package/src/admin/contract.ts +47 -0
- package/src/admin/graph/.spec/api.d.ts +4 -10
- package/src/admin/graph/nodes.ts +1 -1
- package/src/admin/instance/.spec/api.d.ts +5 -9
- package/src/admin/instance/.spec/architecture.md +7 -7
- package/src/admin/instance/__tests__/client.test.ts +157 -175
- package/src/admin/instance/client.ts +39 -128
- package/src/admin/instance/index.ts +1 -2
- package/src/admin/instance/model.ts +12 -18
- package/src/commands/__tests__/admin-instance.test.ts +11 -8
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/call.test.ts +34 -0
- package/src/commands/__tests__/domain-install-operation.test.ts +30 -22
- package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
- package/src/commands/__tests__/domain-list.test.ts +5 -25
- package/src/commands/__tests__/install-direct.test.ts +115 -8
- package/src/commands/__tests__/install-identity-override.test.ts +6 -17
- package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
- package/src/commands/__tests__/instance-status.test.ts +139 -0
- package/src/commands/__tests__/introspect-parse.test.ts +80 -1
- package/src/commands/__tests__/logs.test.ts +250 -2
- package/src/commands/__tests__/read-commands.test.ts +118 -10
- package/src/commands/__tests__/token-ttl.test.ts +49 -4
- package/src/commands/__tests__/update.test.ts +25 -3
- package/src/commands/__tests__/view.test.ts +50 -2
- package/src/commands/auth/logout.ts +2 -1
- package/src/commands/browser.ts +29 -0
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +71 -59
- package/src/commands/domain/install.ts +73 -46
- package/src/commands/domain/list.ts +1 -1
- package/src/commands/domain/publish.ts +14 -11
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +15 -16
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +8 -8
- package/src/commands/instance/create.ts +8 -16
- package/src/commands/instance/delete.ts +12 -9
- package/src/commands/instance/list.ts +2 -2
- package/src/commands/instance/status.ts +66 -20
- package/src/commands/instance/use.ts +16 -12
- package/src/commands/introspect.ts +53 -41
- package/src/commands/logs.ts +123 -36
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +35 -32
- package/src/commands/session/analyze.ts +26 -4
- package/src/commands/token.ts +42 -27
- package/src/commands/ui/__tests__/commands.test.ts +152 -0
- package/src/commands/ui/add.ts +51 -0
- package/src/commands/ui/doctor.ts +21 -0
- package/src/commands/ui/init.ts +38 -0
- package/src/commands/ui/list.ts +26 -0
- package/src/commands/ui/preset-apply.ts +19 -0
- package/src/commands/ui/preset-list.ts +12 -0
- package/src/commands/ui/shared.ts +25 -0
- package/src/commands/update.ts +89 -48
- package/src/commands/view.ts +82 -37
- package/src/connection/.spec/api.d.ts +7 -8
- package/src/connection/.spec/architecture.md +14 -5
- package/src/connection/.spec/flows/session.ts +2 -2
- package/src/connection/.spec/laws/connection.ts +6 -2
- package/src/connection/.spec/layout.ts +1 -0
- package/src/connection/__tests__/auth.test.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +21 -1
- package/src/connection/__tests__/errors.test.ts +27 -20
- package/src/connection/__tests__/exchange.test.ts +64 -11
- package/src/connection/__tests__/failure-fixtures.ts +39 -0
- package/src/connection/__tests__/failure-safety.test.ts +75 -0
- package/src/connection/__tests__/self.test.ts +31 -11
- package/src/connection/__tests__/session.test.ts +8 -4
- package/src/connection/__tests__/target.test.ts +2 -0
- package/src/connection/auth.ts +13 -0
- package/src/connection/call.ts +1 -1
- package/src/connection/credential.ts +2 -2
- package/src/connection/errors.ts +1 -355
- package/src/connection/exchange.ts +110 -61
- package/src/connection/failure/classify.ts +110 -0
- package/src/connection/failure/index.ts +18 -0
- package/src/connection/failure/model.ts +28 -0
- package/src/connection/failure/render.ts +116 -0
- package/src/connection/reasons.ts +16 -0
- package/src/connection/self.ts +39 -21
- package/src/connection/session.ts +27 -9
- package/src/errors.ts +3 -2
- package/src/graph/.spec/api.d.ts +12 -5
- package/src/graph/.spec/layout.ts +9 -1
- package/src/graph/__tests__/mutation.test.ts +2 -2
- package/src/graph/__tests__/query.test.ts +25 -6
- package/src/graph/class.ts +30 -0
- package/src/graph/index.ts +1 -0
- package/src/graph/query.ts +21 -32
- package/src/identity/.spec/api.d.ts +2 -2
- package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
- package/src/identity/__tests__/registration.test.ts +1 -1
- package/src/identity/__tests__/registry.test.ts +4 -0
- package/src/identity/registration.ts +3 -3
- package/src/identity/registry.ts +2 -0
- package/src/keys/__tests__/keys.test.ts +4 -0
- package/src/keys/credential.ts +1 -0
- package/src/lib/__tests__/admin-target.test.ts +12 -0
- package/src/lib/__tests__/binary.test.ts +16 -1
- package/src/lib/__tests__/browser-retention.test.ts +212 -0
- package/src/lib/__tests__/command-dx.test.ts +1 -1
- package/src/lib/__tests__/config.test.ts +27 -0
- package/src/lib/__tests__/domain-publication.test.ts +10 -16
- package/src/lib/__tests__/idp.test.ts +34 -4
- package/src/lib/__tests__/instance-candidates.test.ts +1 -3
- package/src/lib/__tests__/instance-target.test.ts +7 -39
- package/src/lib/__tests__/log-errors.test.ts +64 -0
- package/src/lib/__tests__/skills.test.ts +809 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
- package/src/lib/__tests__/update.test.ts +264 -11
- package/src/lib/__tests__/view-assets.test.ts +25 -0
- package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
- package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +26 -3
- package/src/lib/__tests__/view-session.test.ts +2 -1
- package/src/lib/admin-instance.ts +14 -8
- package/src/lib/admin-target.ts +21 -2
- package/src/lib/binary.ts +22 -5
- package/src/lib/browser-retention.ts +210 -0
- package/src/lib/command-dx.ts +9 -9
- package/src/lib/config.ts +12 -1
- package/src/lib/domain-publication.ts +9 -7
- package/src/lib/failure-debug.ts +26 -0
- package/src/lib/idp.ts +34 -3
- package/src/lib/instance-candidates.ts +2 -2
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/instance.ts +16 -11
- package/src/lib/log.ts +16 -9
- package/src/lib/output.ts +1 -1
- package/src/lib/proc.ts +7 -2
- package/src/lib/provision-instance.ts +34 -57
- package/src/lib/skills/lock.ts +117 -0
- package/src/lib/skills/sync.ts +814 -0
- package/src/lib/skills.ts +1 -52
- package/src/lib/update.ts +298 -45
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/assets.ts +11 -2
- package/src/lib/view/external-open-intent.ts +41 -0
- package/src/lib/view/external-open-origins.ts +37 -0
- package/src/lib/view/host-capabilities.ts +17 -0
- package/src/lib/view/resolve.ts +1 -1
- package/src/lib/view/server.ts +18 -3
- package/src/lib/view/session.ts +2 -0
- package/src/program/.spec/api.d.ts +1 -0
- package/src/program/__tests__/program.test.ts +163 -6
- package/src/program/argv.ts +14 -0
- package/src/program/build.ts +26 -4
- package/src/program/command.ts +1 -0
- package/src/program/index.ts +1 -0
- package/src/program/registry.ts +2 -1
- package/src/setup/__tests__/instance-step.test.ts +27 -10
- package/src/setup/steps/instance.ts +22 -15
- package/src/setup/steps/skills.ts +40 -38
- package/src/state/.spec/api.d.ts +21 -2
- package/src/state/.spec/architecture.md +18 -4
- package/src/state/.spec/layout.ts +1 -0
- package/src/state/__tests__/exchange-credentials.test.ts +88 -42
- package/src/state/__tests__/files.test.ts +24 -1
- package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
- package/src/state/__tests__/paths.test.ts +1 -0
- package/src/state/__tests__/session-routes.test.ts +138 -0
- package/src/state/exchange-credentials.ts +22 -9
- package/src/state/files.ts +41 -0
- package/src/state/index.ts +3 -1
- package/src/state/paths.ts +3 -0
- package/src/state/session-routes.ts +34 -0
- package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
- package/src/telemetry/__tests__/recorder.test.ts +1 -0
- package/src/telemetry/__tests__/retention.test.ts +267 -0
- package/src/telemetry/__tests__/settings.test.ts +102 -0
- package/src/telemetry/__tests__/store-scan.test.ts +128 -0
- package/src/telemetry/__tests__/trigger.test.ts +33 -4
- package/src/telemetry/analyze.ts +24 -2
- package/src/telemetry/recorder.ts +15 -4
- package/src/telemetry/retention.ts +176 -0
- package/src/telemetry/session.ts +18 -12
- package/src/telemetry/settings.ts +64 -11
- package/src/telemetry/store.ts +92 -9
- package/src/telemetry/trigger.ts +16 -28
- package/src/ui/.spec/api.d.ts +14 -0
- package/src/ui/.spec/architecture.md +14 -0
- package/src/ui/.spec/laws.ts +50 -0
- package/src/ui/.spec/layout.ts +16 -0
- package/src/ui/__tests__/ui.test.ts +1742 -0
- package/src/ui/index.ts +13 -0
- package/src/ui/lock.ts +89 -0
- package/src/ui/model.ts +83 -0
- package/src/ui/operations.ts +1044 -0
- package/src/ui/project.ts +333 -0
- package/src/ui/release.ts +398 -0
- package/src/ui/runner.ts +18 -0
- package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
- package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
- package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
- package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
- package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
- package/studio/client/dist/index.html +4 -3
- package/studio/package.json +8 -14
- package/studio/server/agent/ask.test.ts +1 -1
- package/studio/server/agent/bridge/grant.test.ts +1 -1
- package/studio/server/agent/bridge/routes.test.ts +1 -1
- package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
- package/studio/server/agent/harness/gateway/token.test.ts +1 -1
- package/studio/server/agent/harness/gateway/token.ts +3 -3
- package/studio/server/agent/prompts/anchors.test.ts +41 -32
- package/studio/server/agent/prompts/anchors.ts +23 -51
- package/studio/server/agent/prompts/system.test.ts +4 -4
- package/studio/server/agent/prompts/system.ts +5 -5
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/agent/run/preparation.ts +1 -1
- package/studio/server/api/canvas.ts +0 -1
- package/studio/server/api/context.ts +1 -1
- package/studio/server/api/deployment.ts +1 -1
- package/studio/server/api/views.ts +2 -2
- package/studio/server/api/workspace.ts +1 -1
- package/studio/server/api-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +1 -1
- package/studio/server/cache.test.ts +24 -18
- package/studio/server/cache.ts +10 -54
- package/studio/server/cli-consumers.test.ts +31 -3
- package/studio/server/domain.test.ts +50 -68
- package/studio/server/domain.ts +82 -63
- package/studio/server/handoff/copy.ts +2 -2
- package/studio/server/index.ts +1 -1
- package/studio/server/instances/probe.test.ts +1 -1
- package/studio/server/introspect/anatomy/views/routes.ts +46 -37
- package/studio/server/introspect/anatomy/views.ts +10 -16
- package/studio/server/introspect/anatomy-extras.test.ts +29 -56
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +6 -9
- package/studio/server/introspect/bundle.ts +0 -3
- package/studio/server/introspect/canonical-schema.test.ts +155 -424
- package/studio/server/introspect/canonical-schema.ts +248 -601
- package/studio/server/introspect/core.ts +14 -22
- package/studio/server/introspect/diff.test.ts +21 -9
- package/studio/server/introspect/diff.ts +19 -64
- package/studio/server/introspect/extractor.ts +23 -122
- package/studio/server/introspect/overlay-tsmorph.test.ts +116 -241
- package/studio/server/introspect/overlay-tsmorph.ts +0 -1
- package/studio/server/introspect/overlay.ts +3 -51
- package/studio/server/introspect/revision.test.ts +24 -28
- package/studio/server/introspect/revision.ts +11 -22
- package/studio/server/introspect/runtime.test.ts +77 -195
- package/studio/server/introspect/runtime.ts +5 -67
- package/studio/server/introspect/schema-ir-json.test.ts +81 -0
- package/studio/server/introspect/schema-ir-json.ts +39 -69
- package/studio/server/introspect/schema-refs.test.ts +43 -88
- package/studio/server/introspect/schema-refs.ts +14 -49
- package/studio/server/introspect/source-overlay/handlers.ts +116 -636
- package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
- package/studio/server/introspect/source-overlay/spans.ts +15 -45
- package/studio/server/lifecycle.ts +4 -1
- package/studio/server/state/baseline.test.ts +14 -9
- package/studio/server/state/baseline.ts +3 -5
- package/studio/server/state/documents.test.ts +69 -0
- package/studio/server/state/documents.ts +57 -10
- package/studio/server/state/visibility.ts +1 -5
- package/studio/server/views/model.ts +3 -3
- package/studio/server/views/target.test.ts +18 -76
- package/studio/server/views/target.ts +24 -50
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +8 -4
- package/studio/server/workspace/updates.ts +23 -1
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/runtime.ts +4 -0
- package/studio/shared/contracts/schema.ts +44 -131
- package/studio/shared/contracts/surface.test.ts +15 -17
- package/studio/shared/contracts/workspace.ts +5 -11
- package/studio/shared/schema/identity.test.ts +20 -44
- package/studio/shared/schema/identity.ts +9 -24
- package/studio/tsconfig.json +2 -1
- package/tsconfig.json +1 -1
- package/viewer/dist/index.html +21 -0
- package/viewer/dist/main.js +41 -62
- package/.check-workspace.cjs +0 -40
- package/src/admin/binding.ts +0 -168
- package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
- package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
- package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
- package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
- package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
- package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
- package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
- package/studio/server/introspect/core-extractor.ts +0 -139
- package/studio/server/introspect/overlay.test.ts +0 -72
- package/studio/server/introspect/source-overlay/annotations.ts +0 -14
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import type { AuthApi } from '@astrale-os/
|
|
2
|
-
import type { GraphApi } from '@astrale-os/
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import type { AuthApi } from '@astrale-os/sdk/auth'
|
|
2
|
+
import type { GraphApi } from '@astrale-os/sdk/client'
|
|
3
|
+
import type {
|
|
4
|
+
ClientSessionOptions,
|
|
5
|
+
SessionAuth,
|
|
6
|
+
SessionRouteStore,
|
|
7
|
+
} from '@astrale-os/sdk/client/session'
|
|
8
|
+
|
|
9
|
+
import { createAuth } from '@astrale-os/sdk/auth'
|
|
10
|
+
import { call } from '@astrale-os/sdk/client'
|
|
11
|
+
import { createGraph } from '@astrale-os/sdk/client'
|
|
12
|
+
import { ClientSession } from '@astrale-os/sdk/client/session'
|
|
9
13
|
|
|
10
14
|
import type { AstraleConfig } from '../lib/config'
|
|
11
15
|
import type { AdminConnectionOptions, ConnectionOptions, ConnectionTarget } from './target'
|
|
@@ -21,6 +25,8 @@ import { fetchWithCaFile } from '../lib/ca-fetch'
|
|
|
21
25
|
import { readConfig } from '../lib/config'
|
|
22
26
|
import { log } from '../lib/log'
|
|
23
27
|
import { isMachine } from '../lib/output'
|
|
28
|
+
import { SESSION_ROUTE_STORE } from '../state/session-routes'
|
|
29
|
+
import { bindCredentialIdentity } from './auth'
|
|
24
30
|
import { createCliCredential, validateCredentialSelection } from './credential'
|
|
25
31
|
import { resolveAdminConnectionTarget, resolveConnectionTarget } from './target'
|
|
26
32
|
|
|
@@ -38,6 +44,8 @@ export interface ConnectionContext {
|
|
|
38
44
|
readonly graph: GraphApi
|
|
39
45
|
readonly auth: AuthApi
|
|
40
46
|
readonly target: ConnectionTarget
|
|
47
|
+
/** Local identity label selected for this session; absent for raw credentials or anonymous use. */
|
|
48
|
+
readonly identity?: string
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
interface OwnedConnection {
|
|
@@ -63,6 +71,7 @@ export async function withClientSession<Value>(
|
|
|
63
71
|
const target = await resolveConnectionTarget(options, config, {
|
|
64
72
|
managed: (slug) => lookupManagedInstance(slug, options),
|
|
65
73
|
})
|
|
74
|
+
options = await bindCredentialIdentity(options, target)
|
|
66
75
|
warnMissingExplicitTarget(options, target)
|
|
67
76
|
return runResolvedClientSession(target, timeoutMs, options, config, action, openConnection)
|
|
68
77
|
}
|
|
@@ -76,6 +85,7 @@ export async function withAdminClientSession<Value>(
|
|
|
76
85
|
const timeoutMs = resolveTimeoutMs(options.timeout)
|
|
77
86
|
const config = await readConfig()
|
|
78
87
|
const target = await resolveAdminConnectionTarget(options, config)
|
|
88
|
+
options = await bindCredentialIdentity(options, target)
|
|
79
89
|
return runResolvedClientSession(target, timeoutMs, options, config, action, openConnection)
|
|
80
90
|
}
|
|
81
91
|
|
|
@@ -141,7 +151,13 @@ function openConnection(
|
|
|
141
151
|
const graph = createGraph((call, request) => session.call(call, request))
|
|
142
152
|
const authApi = createAuth((path, input, request) => session.call(call(path, input), request))
|
|
143
153
|
return {
|
|
144
|
-
context: Object.freeze({
|
|
154
|
+
context: Object.freeze({
|
|
155
|
+
session,
|
|
156
|
+
graph,
|
|
157
|
+
auth: authApi,
|
|
158
|
+
target,
|
|
159
|
+
...(options.as === undefined ? {} : { identity: options.as }),
|
|
160
|
+
}),
|
|
145
161
|
close() {
|
|
146
162
|
session.close()
|
|
147
163
|
},
|
|
@@ -154,11 +170,13 @@ export function createClientSessionOptions(
|
|
|
154
170
|
fetch: NonNullable<ClientSessionOptions['fetch']>,
|
|
155
171
|
auth: SessionAuth | undefined,
|
|
156
172
|
timeoutMs: number,
|
|
173
|
+
routeStore: SessionRouteStore = SESSION_ROUTE_STORE,
|
|
157
174
|
): ClientSessionOptions {
|
|
158
175
|
return {
|
|
159
176
|
kernel: target.kernelIssuer,
|
|
160
177
|
fetch,
|
|
161
178
|
...(auth === undefined ? {} : { auth }),
|
|
179
|
+
routeStore,
|
|
162
180
|
policy: {
|
|
163
181
|
maximumRouteAgeMs: MAXIMUM_ROUTE_AGE_MS,
|
|
164
182
|
...(new URL(target.url).protocol === 'http:' ? { allowInsecureHttp: true } : {}),
|
package/src/errors.ts
CHANGED
|
@@ -2,8 +2,9 @@ export class AstraleError extends Error {
|
|
|
2
2
|
code: string
|
|
3
3
|
hint?: string
|
|
4
4
|
|
|
5
|
-
constructor(code: string, message: string, hint?: string) {
|
|
6
|
-
|
|
5
|
+
constructor(code: string, message: string, hint?: string, options?: ErrorOptions) {
|
|
6
|
+
if (message.trim() === '') throw new TypeError('CLI error message must be non-blank.')
|
|
7
|
+
super(message, options)
|
|
7
8
|
this.name = 'AstraleError'
|
|
8
9
|
this.code = code
|
|
9
10
|
this.hint = hint
|
package/src/graph/.spec/api.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import type { ClassKey, ClassRef } from '@astrale-os/sdk/graph/class'
|
|
1
2
|
import type { MutationAST } from '@astrale-os/sdk/mutation'
|
|
2
3
|
import type { QueryAST, QueryDirection } from '@astrale-os/sdk/query'
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
+
/** Admit one canonical Domain-rooted Class Path into its DSL coordinate. */
|
|
6
|
+
export function classReference(input: string, label: string): ClassRef
|
|
7
|
+
|
|
8
|
+
/** Admit one canonical Domain-rooted Class Path into its exact string identity. */
|
|
9
|
+
export function classKey(input: string, label: string): ClassKey
|
|
10
|
+
|
|
11
|
+
/** Untrusted CLI fields used to author or admit one exact Query V6 request. */
|
|
5
12
|
export interface QueryCommandInput {
|
|
6
13
|
readonly sources: readonly string[]
|
|
7
14
|
readonly definition?: string
|
|
@@ -12,16 +19,16 @@ export interface QueryCommandInput {
|
|
|
12
19
|
readonly cursor?: string
|
|
13
20
|
}
|
|
14
21
|
|
|
15
|
-
/** Canonical query plus
|
|
22
|
+
/** Canonical query plus caller-bound pagination kept outside the AST. */
|
|
16
23
|
export interface PreparedQuery {
|
|
17
24
|
readonly ast: QueryAST
|
|
18
|
-
readonly
|
|
25
|
+
readonly page: Readonly<{ readonly size: number; readonly after?: string }>
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
/** Admit canonical Query
|
|
28
|
+
/** Admit canonical Query V6 or author its supported Path/Class/one-edge CLI subset. */
|
|
22
29
|
export function prepareQuery(input: QueryCommandInput): PreparedQuery
|
|
23
30
|
|
|
24
|
-
/** Admit canonical Mutation
|
|
31
|
+
/** Admit canonical Mutation V3 or its exact authoring input; legacy PatchData is rejected. */
|
|
25
32
|
export function prepareMutation(input: unknown): MutationAST
|
|
26
33
|
|
|
27
34
|
/** Return the user-facing leaf of one exact qualified Property key. */
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { defineLayout } from '@astrale-os/spec/authoring'
|
|
2
2
|
|
|
3
3
|
export default defineLayout({
|
|
4
|
-
entries: [
|
|
4
|
+
entries: [
|
|
5
|
+
'__tests__/',
|
|
6
|
+
'class.ts',
|
|
7
|
+
'index.ts',
|
|
8
|
+
'mutation.ts',
|
|
9
|
+
'projection.ts',
|
|
10
|
+
'query.ts',
|
|
11
|
+
'tsconfig.json',
|
|
12
|
+
],
|
|
5
13
|
exact: true,
|
|
6
14
|
})
|
|
@@ -14,7 +14,7 @@ describe('prepareMutation', () => {
|
|
|
14
14
|
{
|
|
15
15
|
op: 'node.create',
|
|
16
16
|
as: 'note',
|
|
17
|
-
class: '
|
|
17
|
+
class: 'notes.example.dev:class.Note',
|
|
18
18
|
props: {},
|
|
19
19
|
},
|
|
20
20
|
],
|
|
@@ -29,7 +29,7 @@ describe('prepareMutation', () => {
|
|
|
29
29
|
{
|
|
30
30
|
op: 'node.create',
|
|
31
31
|
as: 'note',
|
|
32
|
-
class: '
|
|
32
|
+
class: 'notes.example.dev:class.Note',
|
|
33
33
|
props: {},
|
|
34
34
|
},
|
|
35
35
|
],
|
|
@@ -38,7 +38,7 @@ describe('prepareQuery', () => {
|
|
|
38
38
|
})
|
|
39
39
|
|
|
40
40
|
/** @evidence TEST-CLI-GRAPH-AUTHORS-DEFINITION-QUERY */
|
|
41
|
-
test('authors an exact
|
|
41
|
+
test('authors an exact Class source without backend query text', () => {
|
|
42
42
|
const prepared = prepareQuery({
|
|
43
43
|
sources: [],
|
|
44
44
|
definition: '/:issues.astrale.ai:class.Issue',
|
|
@@ -52,8 +52,8 @@ describe('prepareQuery', () => {
|
|
|
52
52
|
kind: 'node',
|
|
53
53
|
terms: [
|
|
54
54
|
{
|
|
55
|
-
kind: '
|
|
56
|
-
|
|
55
|
+
kind: 'class',
|
|
56
|
+
class: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
|
|
57
57
|
},
|
|
58
58
|
],
|
|
59
59
|
binding: 'n0',
|
|
@@ -64,6 +64,25 @@ describe('prepareQuery', () => {
|
|
|
64
64
|
expect(prepared.page).toEqual({ size: 201 })
|
|
65
65
|
})
|
|
66
66
|
|
|
67
|
+
test('unions positional Paths and one Class source in authored order', () => {
|
|
68
|
+
const prepared = prepareQuery({
|
|
69
|
+
sources: ['@note'],
|
|
70
|
+
definition: '/:issues.astrale.ai:class.Issue',
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
expect(JSON.parse(JSON.stringify(prepared.ast.source))).toEqual({
|
|
74
|
+
kind: 'node',
|
|
75
|
+
terms: [
|
|
76
|
+
{ kind: 'path', path: '@note' },
|
|
77
|
+
{
|
|
78
|
+
kind: 'class',
|
|
79
|
+
class: { origin: 'issues.astrale.ai', kind: 'class', name: 'Issue' },
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
binding: 'n0',
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
67
86
|
/** @evidence TEST-CLI-GRAPH-ADMITS-QUERY-V6-ORDERING */
|
|
68
87
|
test('admits one exact Property-ordered Query V6 document', () => {
|
|
69
88
|
const ast = {
|
|
@@ -73,8 +92,8 @@ describe('prepareQuery', () => {
|
|
|
73
92
|
kind: 'node',
|
|
74
93
|
terms: [
|
|
75
94
|
{
|
|
76
|
-
kind: '
|
|
77
|
-
|
|
95
|
+
kind: 'class',
|
|
96
|
+
class: { origin: 'notes.example.dev', kind: 'class', name: 'Note' },
|
|
78
97
|
},
|
|
79
98
|
],
|
|
80
99
|
binding: 'n0',
|
|
@@ -149,6 +168,6 @@ describe('prepareQuery', () => {
|
|
|
149
168
|
).toThrow('--limit must be a positive integer')
|
|
150
169
|
expect(() =>
|
|
151
170
|
prepareQuery({ sources: [], definition: '/:notes.example.dev:view.note' }),
|
|
152
|
-
).toThrow('--definition must be one canonical Class
|
|
171
|
+
).toThrow('--definition must be one canonical Class Path')
|
|
153
172
|
})
|
|
154
173
|
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ClassKey as ClassKeyValue, ClassRef } from '@astrale-os/sdk/graph/class'
|
|
2
|
+
|
|
3
|
+
import { ClassKey } from '@astrale-os/sdk/graph/class'
|
|
4
|
+
import { Path } from '@astrale-os/sdk/graph/path'
|
|
5
|
+
|
|
6
|
+
/** Admit one canonical Domain-rooted Class Path into its DSL coordinate. */
|
|
7
|
+
export function classReference(input: string, label: string): ClassRef {
|
|
8
|
+
const path = Path.parse(input)
|
|
9
|
+
const step = path.ast.steps[0]
|
|
10
|
+
if (
|
|
11
|
+
path.ast.anchor.kind !== 'domain' ||
|
|
12
|
+
path.ast.steps.length !== 1 ||
|
|
13
|
+
step?.kind !== 'projection' ||
|
|
14
|
+
step.projection.kind !== 'class'
|
|
15
|
+
) {
|
|
16
|
+
throw new TypeError(`${label} must be one canonical Class Path`)
|
|
17
|
+
}
|
|
18
|
+
return ClassKey.ref(
|
|
19
|
+
ClassKey.of({
|
|
20
|
+
origin: path.ast.anchor.origin,
|
|
21
|
+
kind: 'class',
|
|
22
|
+
name: step.projection.name,
|
|
23
|
+
}),
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Admit one canonical Domain-rooted Class Path into its exact string identity. */
|
|
28
|
+
export function classKey(input: string, label: string): ClassKeyValue {
|
|
29
|
+
return ClassKey.of(classReference(input, label))
|
|
30
|
+
}
|
package/src/graph/index.ts
CHANGED
package/src/graph/query.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
NodeQueryBuilder,
|
|
2
3
|
QueryAST as QueryASTValue,
|
|
3
|
-
QueryDefinitionRef,
|
|
4
4
|
QueryDirection,
|
|
5
|
+
QueryNodeInput,
|
|
5
6
|
} from '@astrale-os/sdk/query'
|
|
6
7
|
|
|
7
8
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
8
9
|
import { Query, QueryAST } from '@astrale-os/sdk/query'
|
|
9
10
|
|
|
11
|
+
import { classReference } from './class'
|
|
12
|
+
|
|
10
13
|
export interface QueryCommandInput {
|
|
11
14
|
readonly sources: readonly string[]
|
|
12
15
|
readonly definition?: string
|
|
@@ -51,43 +54,29 @@ export function prepareQuery(input: QueryCommandInput): PreparedQuery {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
const paths = input.sources.map((source) => Path.parse(source))
|
|
54
|
-
const definition =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
...(definition === undefined ? {} : { definitions: [definition] }),
|
|
61
|
-
})
|
|
62
|
-
: Query.from({ kind: 'node', definitions: [definition!] })
|
|
57
|
+
const definition =
|
|
58
|
+
input.definition === undefined ? undefined : classReference(input.definition, '--definition')
|
|
59
|
+
const nodes = definition === undefined ? paths : [...paths, definition]
|
|
60
|
+
const query: NodeQueryBuilder<unknown> = Query.from({
|
|
61
|
+
nodes: nodes as [QueryNodeInput, ...QueryNodeInput[]],
|
|
62
|
+
})
|
|
63
63
|
const ast =
|
|
64
64
|
input.edge === undefined
|
|
65
|
-
? query.select({ kind: 'graph' })
|
|
66
|
-
: query
|
|
67
|
-
.expand({
|
|
68
|
-
via: [definitionRef(input.edge)],
|
|
69
|
-
direction: input.direction ?? 'outgoing',
|
|
70
|
-
})
|
|
71
|
-
.select({ kind: 'graph' })
|
|
65
|
+
? query.select({ kind: 'graph', binding: query.node })
|
|
66
|
+
: expanded(query, input.edge, input.direction)
|
|
72
67
|
return withPage(ast, limit, input.cursor)
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
) {
|
|
84
|
-
throw new TypeError('--definition must be one canonical Class or Interface Path')
|
|
85
|
-
}
|
|
86
|
-
return Object.freeze({
|
|
87
|
-
origin: path.ast.anchor.origin,
|
|
88
|
-
kind: step.projection.kind,
|
|
89
|
-
name: step.projection.name,
|
|
70
|
+
function expanded(
|
|
71
|
+
query: NodeQueryBuilder<unknown>,
|
|
72
|
+
edge: string,
|
|
73
|
+
direction: QueryDirection | undefined,
|
|
74
|
+
): QueryASTValue {
|
|
75
|
+
const result = query.expand({
|
|
76
|
+
via: [classReference(edge, '--edge')],
|
|
77
|
+
direction: direction ?? 'outgoing',
|
|
90
78
|
})
|
|
79
|
+
return result.select({ kind: 'graph', binding: result.node })
|
|
91
80
|
}
|
|
92
81
|
|
|
93
82
|
function positiveInteger(raw: string | undefined, name: string, fallback: number): number {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Call } from '@astrale-os/kernel-client'
|
|
2
1
|
import type { ProvisionRequest } from '@astrale-os/sdk/auth'
|
|
3
|
-
import type {
|
|
2
|
+
import type { Call } from '@astrale-os/sdk/client'
|
|
3
|
+
import type { LocalBinding } from '@astrale-os/sdk/graph'
|
|
4
4
|
import type { JWK } from 'jose'
|
|
5
5
|
|
|
6
6
|
import type {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
|
+
|
|
1
4
|
import { fileExists, keypairPaths } from '../../../keys/index'
|
|
2
|
-
import { KEYS_DIR } from '../../../state/index'
|
|
5
|
+
import { EXCHANGE_CREDENTIALS_PATH, KEYS_DIR, SESSION_ROUTES_PATH } from '../../../state/index'
|
|
3
6
|
import {
|
|
4
7
|
createIdentity,
|
|
5
8
|
exportIdentity,
|
|
@@ -30,6 +33,13 @@ try {
|
|
|
30
33
|
} catch (error) {
|
|
31
34
|
selectedDeletion = error instanceof Error ? error.message : String(error)
|
|
32
35
|
}
|
|
36
|
+
await mkdir(dirname(SESSION_ROUTES_PATH), { recursive: true })
|
|
37
|
+
await mkdir(dirname(EXCHANGE_CREDENTIALS_PATH), { recursive: true })
|
|
38
|
+
await writeFile(SESSION_ROUTES_PATH, '{"version":1,"entries":{"confidential":{}}}\n')
|
|
39
|
+
await writeFile(
|
|
40
|
+
EXCHANGE_CREDENTIALS_PATH,
|
|
41
|
+
'{"version":2,"entries":{"confidential":{"credential":"bearer"}}}\n',
|
|
42
|
+
)
|
|
33
43
|
await deleteIdentity('bob')
|
|
34
44
|
await deleteIdentity('alice-alias')
|
|
35
45
|
await upsertIdpIdentity('workos', {
|
|
@@ -49,5 +59,7 @@ console.log(
|
|
|
49
59
|
selectedDeletion,
|
|
50
60
|
aliceKey: await fileExists(keypairPaths('alice', KEYS_DIR).privatePath),
|
|
51
61
|
bobKey: await fileExists(keypairPaths('bob', KEYS_DIR).privatePath),
|
|
62
|
+
routeCache: await fileExists(SESSION_ROUTES_PATH),
|
|
63
|
+
exchangeCache: JSON.parse(await readFile(EXCHANGE_CREDENTIALS_PATH, 'utf8')),
|
|
52
64
|
}),
|
|
53
65
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ProvisionRequest } from '@astrale-os/sdk/auth'
|
|
2
2
|
|
|
3
|
-
import { LocalBinding } from '@astrale-os/sdk/graph
|
|
3
|
+
import { LocalBinding } from '@astrale-os/sdk/graph'
|
|
4
4
|
import { expect, test } from 'bun:test'
|
|
5
5
|
|
|
6
6
|
import { submitIdentityProvision } from '../registration'
|
|
@@ -47,6 +47,8 @@ describe('identity registry journey', () => {
|
|
|
47
47
|
selectedDeletion: string
|
|
48
48
|
aliceKey: boolean
|
|
49
49
|
bobKey: boolean
|
|
50
|
+
routeCache: boolean
|
|
51
|
+
exchangeCache: unknown
|
|
50
52
|
}
|
|
51
53
|
expect(result.store.default).toBe('alice')
|
|
52
54
|
expect(Object.keys(result.store.identities).sort()).toEqual(['alice', 'workos'])
|
|
@@ -60,6 +62,8 @@ describe('identity registry journey', () => {
|
|
|
60
62
|
expect(result.selectedDeletion).toContain('Cannot delete the default identity')
|
|
61
63
|
expect(result.aliceKey).toBe(true)
|
|
62
64
|
expect(result.bobKey).toBe(false)
|
|
65
|
+
expect(result.routeCache).toBe(false)
|
|
66
|
+
expect(result.exchangeCache).toEqual({ version: 2, entries: {} })
|
|
63
67
|
|
|
64
68
|
const persisted = JSON.parse(await readFile(join(root, 'identities.json'), 'utf-8')) as {
|
|
65
69
|
version?: unknown
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { Call } from '@astrale-os/kernel-client'
|
|
2
1
|
import type { ProvisionRequest } from '@astrale-os/sdk/auth'
|
|
3
|
-
import type {
|
|
2
|
+
import type { Call } from '@astrale-os/sdk/client'
|
|
3
|
+
import type { LocalBinding } from '@astrale-os/sdk/graph'
|
|
4
4
|
|
|
5
|
-
import { call } from '@astrale-os/kernel-client'
|
|
6
5
|
import { issuer } from '@astrale-os/sdk/auth'
|
|
6
|
+
import { call } from '@astrale-os/sdk/client'
|
|
7
7
|
import { NodeId } from '@astrale-os/sdk/graph/node'
|
|
8
8
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
9
9
|
|
package/src/identity/registry.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type IdentityStore,
|
|
11
11
|
type Registration,
|
|
12
12
|
ExchangeCredentialCache,
|
|
13
|
+
SESSION_ROUTE_STORE,
|
|
13
14
|
} from '../state/index'
|
|
14
15
|
|
|
15
16
|
export type { Identity, IdentityStore, Registration } from '../state/index'
|
|
@@ -68,6 +69,7 @@ export async function deleteIdentity(name: string): Promise<void> {
|
|
|
68
69
|
return { next: { ...store, identities }, value: undefined }
|
|
69
70
|
})
|
|
70
71
|
await new ExchangeCredentialCache().clear()
|
|
72
|
+
SESSION_ROUTE_STORE.clear()
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
function hasAnotherKeyIdentity(store: IdentityStore, name: string, subject: string): boolean {
|
|
@@ -62,6 +62,10 @@ describe('DESIGN — per-identity keys', () => {
|
|
|
62
62
|
await persistKeypair('alice', { keysDir: tmp })
|
|
63
63
|
const jwt = await signAs('alice', tmp)
|
|
64
64
|
expect(jwt.split('.').length).toBe(3)
|
|
65
|
+
const claims = decodeJwt(jwt)
|
|
66
|
+
expect(claims.iat).toBeUndefined()
|
|
67
|
+
expect(claims.exp).toBeNumber()
|
|
68
|
+
expect((claims.exp as number) * 1_000).toBeGreaterThan(Date.now() + 299_000)
|
|
65
69
|
})
|
|
66
70
|
|
|
67
71
|
/** @evidence TEST-CLI-KEYS-DISTINGUISHES-KERNEL-ROOT-GRANT */
|
package/src/keys/credential.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
DEFAULT_ADMIN_TARGET_NAME,
|
|
7
7
|
DEFAULT_ADMIN_DOMAIN_ISSUER,
|
|
8
8
|
DEFAULT_ADMIN_TARGET_URL,
|
|
9
|
+
defaultAdminTargetForChannel,
|
|
9
10
|
resolveAdminTargetFromStore,
|
|
10
11
|
} from '../admin-target'
|
|
11
12
|
import { DEFAULT_CONFIG, type AstraleConfig } from '../config'
|
|
@@ -32,6 +33,17 @@ const instances: InstanceStore = {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
describe('resolveAdminTargetFromStore', () => {
|
|
36
|
+
test('derives hosted Admin defaults from the release channel', () => {
|
|
37
|
+
expect(defaultAdminTargetForChannel('beta')).toEqual({
|
|
38
|
+
url: 'https://admin.eu.beta.astrale.ai/api',
|
|
39
|
+
domainIssuer: 'https://admin.beta.astrale.ai',
|
|
40
|
+
})
|
|
41
|
+
expect(defaultAdminTargetForChannel('stable')).toEqual({
|
|
42
|
+
url: 'https://admin.eu.astrale.ai/api',
|
|
43
|
+
domainIssuer: 'https://admin.astrale.ai',
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
35
47
|
test('uses hosted admin default without active instance coupling', () => {
|
|
36
48
|
expect(resolveAdminTargetFromStore({}, DEFAULT_CONFIG, instances)).toEqual({
|
|
37
49
|
name: DEFAULT_ADMIN_TARGET_NAME,
|
|
@@ -39,11 +39,26 @@ describe('presentBinary', () => {
|
|
|
39
39
|
|
|
40
40
|
test('--json inlines text for text-like content types', async () => {
|
|
41
41
|
await presentBinary(
|
|
42
|
-
{ mediaType: 'text/plain', body: new TextEncoder().encode('hi') },
|
|
42
|
+
{ mediaType: 'text/plain', status: 202, body: new TextEncoder().encode('hi') },
|
|
43
43
|
{ json: true },
|
|
44
44
|
)
|
|
45
45
|
const obj = JSON.parse(Buffer.concat(chunks).toString())
|
|
46
46
|
expect(obj.body).toBe('hi')
|
|
47
|
+
expect(obj.status).toBe(202)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('--raw drains streaming binary chunks in exact order', async () => {
|
|
51
|
+
await presentBinary(
|
|
52
|
+
{
|
|
53
|
+
mediaType: 'application/octet-stream',
|
|
54
|
+
body: (async function* () {
|
|
55
|
+
yield new Uint8Array([1, 2])
|
|
56
|
+
yield new Uint8Array([3, 4])
|
|
57
|
+
})(),
|
|
58
|
+
},
|
|
59
|
+
{ raw: true },
|
|
60
|
+
)
|
|
61
|
+
expect(Array.from(Buffer.concat(chunks))).toEqual([1, 2, 3, 4])
|
|
47
62
|
})
|
|
48
63
|
|
|
49
64
|
test('-o writes raw bytes to a file', async () => {
|