@astrale-os/cli 1.0.0-beta.32 → 1.0.0-beta.43
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/CHANGELOG.md +928 -0
- package/README.md +53 -43
- package/THIRD-PARTY-NOTICES.md +37 -1
- package/dist/astrale.js +349631 -83669
- package/dist/public/connect-core.js +16 -47
- package/dist/public/keys/index.js +1 -1
- package/dist/types/admin/contract.d.ts +11 -0
- package/dist/types/admin/graph/index.d.ts +1 -0
- package/dist/types/admin/graph/nodes.d.ts +12 -0
- package/dist/types/admin/instance/client.d.ts +2 -0
- package/dist/types/connection/lifetime.d.ts +1 -1
- package/dist/types/errors.d.ts +3 -0
- package/dist/types/lib/proc.d.ts +1 -0
- package/dist/types/lib/update.d.ts +15 -7
- package/package.json +8 -6
- package/studio/client/dist/assets/index-5C1uT6ML.css +2 -0
- package/studio/client/dist/assets/index-C4kNlvwF.js +80 -0
- package/studio/client/dist/assets/schema-studio-BR9d0faB.js +8 -0
- package/studio/client/dist/index.html +13 -2
- package/studio/package.json +2 -1
- package/studio/server/agent/ask.test.ts +7 -1
- package/studio/server/agent/bridge/grant.ts +15 -3
- package/studio/server/agent/prompts/system.test.ts +5 -0
- package/studio/server/agent/prompts/system.ts +19 -4
- package/studio/server/agent/routes.test.ts +39 -1
- package/studio/server/agent/routes.ts +3 -0
- package/studio/server/agent/run/coordinator.test.ts +7 -1
- package/studio/server/agent/run/preparation.ts +1 -0
- package/studio/server/agent/run/transcript.test.ts +47 -2
- package/studio/server/agent/run/transcript.ts +32 -3
- package/studio/server/api/documents.test.ts +31 -0
- package/studio/server/api/documents.ts +35 -1
- package/studio/server/api-agent-loadout.test.ts +7 -1
- package/studio/server/api.test.ts +7 -1
- package/studio/server/cache.ts +4 -4
- package/studio/server/detect.ts +9 -12
- package/studio/server/domain.test.ts +83 -3
- package/studio/server/domain.ts +205 -27
- package/studio/server/handoff/service.ts +1 -1
- package/studio/server/index.ts +5 -8
- package/studio/server/introspect/extractor.ts +111 -17
- package/studio/server/introspect/overlay-tsmorph.test.ts +9 -9
- package/studio/server/introspect/overlay.ts +1 -1
- package/studio/server/introspect/runtime.ts +9 -1
- package/studio/server/introspect/source-overlay/spans.ts +2 -5
- package/studio/server/lifecycle.ts +1 -1
- package/studio/server/state/baseline.test.ts +14 -0
- package/studio/server/state/baseline.ts +16 -2
- package/studio/server/watch.test.ts +2 -1
- package/studio/server/watch.ts +5 -2
- package/studio/server/workspace/create.ts +9 -8
- package/studio/server/workspace-state.ts +4 -6
- package/studio/server/workspace-watch.ts +33 -22
- package/studio/shared/contracts/agent.ts +3 -0
- package/viewer/dist/main.js +12 -12
- package/src/__tests__/fixtures/publication.ts +0 -20
- package/src/admin/.spec/api.d.ts +0 -3
- package/src/admin/.spec/architecture.md +0 -22
- package/src/admin/.spec/icon.svg +0 -3
- package/src/admin/.spec/layout.ts +0 -6
- package/src/admin/__tests__/fixture.ts +0 -25
- package/src/admin/catalog/.spec/api.d.ts +0 -63
- package/src/admin/catalog/.spec/architecture.md +0 -7
- package/src/admin/catalog/.spec/icon.svg +0 -3
- package/src/admin/catalog/.spec/layout.ts +0 -6
- package/src/admin/catalog/__tests__/client.test.ts +0 -245
- package/src/admin/catalog/client.ts +0 -195
- package/src/admin/catalog/index.ts +0 -13
- package/src/admin/catalog/model.ts +0 -41
- package/src/admin/catalog/tsconfig.json +0 -8
- package/src/admin/contract.ts +0 -47
- package/src/admin/graph/.spec/api.d.ts +0 -22
- package/src/admin/graph/.spec/architecture.md +0 -5
- package/src/admin/graph/.spec/icon.svg +0 -3
- package/src/admin/graph/.spec/layout.ts +0 -3
- package/src/admin/graph/index.ts +0 -6
- package/src/admin/graph/nodes.ts +0 -56
- package/src/admin/index.ts +0 -2
- package/src/admin/instance/.spec/api.d.ts +0 -62
- package/src/admin/instance/.spec/architecture.md +0 -10
- package/src/admin/instance/.spec/icon.svg +0 -3
- package/src/admin/instance/.spec/layout.ts +0 -6
- package/src/admin/instance/__tests__/client.test.ts +0 -273
- package/src/admin/instance/client.ts +0 -191
- package/src/admin/instance/index.ts +0 -15
- package/src/admin/instance/model.ts +0 -59
- package/src/admin/instance/tsconfig.json +0 -8
- package/src/commands/__tests__/admin-instance.test.ts +0 -91
- package/src/commands/__tests__/auth-login.test.ts +0 -231
- package/src/commands/__tests__/auth-token.test.ts +0 -223
- package/src/commands/__tests__/call-describe.test.ts +0 -80
- package/src/commands/__tests__/call.test.ts +0 -63
- package/src/commands/__tests__/domain-install-operation.test.ts +0 -145
- package/src/commands/__tests__/domain-install-owned.test.ts +0 -191
- package/src/commands/__tests__/domain-list.test.ts +0 -133
- package/src/commands/__tests__/domain-uninstall.test.ts +0 -53
- package/src/commands/__tests__/install-direct.test.ts +0 -128
- package/src/commands/__tests__/install-identity-override.test.ts +0 -76
- package/src/commands/__tests__/instance-bookmark.test.ts +0 -166
- package/src/commands/__tests__/instance-list-admin.test.ts +0 -26
- package/src/commands/__tests__/instance-list-rows.test.ts +0 -64
- package/src/commands/__tests__/instance-status.test.ts +0 -139
- package/src/commands/__tests__/instance-use.test.ts +0 -67
- package/src/commands/__tests__/introspect-parse.test.ts +0 -100
- package/src/commands/__tests__/logs.test.ts +0 -345
- package/src/commands/__tests__/read-commands.test.ts +0 -377
- package/src/commands/__tests__/setup-plan.test.ts +0 -61
- package/src/commands/__tests__/studio-descriptor.test.ts +0 -22
- package/src/commands/__tests__/token-ttl.test.ts +0 -66
- package/src/commands/__tests__/update.test.ts +0 -95
- package/src/commands/__tests__/view-build.test.ts +0 -58
- package/src/commands/__tests__/view.test.ts +0 -197
- package/src/commands/admin/status.ts +0 -61
- package/src/commands/admin/use.ts +0 -100
- package/src/commands/auth/login.ts +0 -82
- package/src/commands/auth/logout.ts +0 -53
- package/src/commands/auth/status.ts +0 -86
- package/src/commands/auth/token.ts +0 -174
- package/src/commands/browser.ts +0 -236
- package/src/commands/call-describe.ts +0 -123
- package/src/commands/call.ts +0 -230
- package/src/commands/domain/install.ts +0 -575
- package/src/commands/domain/list.ts +0 -155
- package/src/commands/domain/publish.ts +0 -136
- package/src/commands/domain/uninstall.ts +0 -128
- package/src/commands/get.ts +0 -69
- package/src/commands/identity/__tests__/register.test.ts +0 -93
- package/src/commands/identity/create.ts +0 -35
- package/src/commands/identity/delete.ts +0 -24
- package/src/commands/identity/export.ts +0 -46
- package/src/commands/identity/import.ts +0 -61
- package/src/commands/identity/list.ts +0 -56
- package/src/commands/identity/register.ts +0 -195
- package/src/commands/identity/sync.ts +0 -38
- package/src/commands/identity/unsync.ts +0 -34
- package/src/commands/identity/use.ts +0 -24
- package/src/commands/identity/whoami.ts +0 -34
- package/src/commands/idp/add.ts +0 -150
- package/src/commands/idp/list.ts +0 -57
- package/src/commands/idp/refresh.ts +0 -38
- package/src/commands/idp/remove.ts +0 -36
- package/src/commands/idp/show.ts +0 -29
- package/src/commands/instance/active.ts +0 -73
- package/src/commands/instance/bookmark.ts +0 -101
- package/src/commands/instance/create.ts +0 -61
- package/src/commands/instance/delete.ts +0 -69
- package/src/commands/instance/forget.ts +0 -26
- package/src/commands/instance/list.ts +0 -197
- package/src/commands/instance/status.ts +0 -92
- package/src/commands/instance/use.ts +0 -263
- package/src/commands/introspect.ts +0 -129
- package/src/commands/logs.ts +0 -371
- package/src/commands/mutate.ts +0 -122
- package/src/commands/query.ts +0 -142
- package/src/commands/session/analyze.ts +0 -72
- package/src/commands/session/list.ts +0 -36
- package/src/commands/setup.ts +0 -54
- package/src/commands/status.ts +0 -60
- package/src/commands/studio.ts +0 -434
- package/src/commands/token.ts +0 -121
- package/src/commands/ui/__tests__/commands.test.ts +0 -152
- package/src/commands/ui/add.ts +0 -51
- package/src/commands/ui/doctor.ts +0 -21
- package/src/commands/ui/init.ts +0 -38
- package/src/commands/ui/list.ts +0 -26
- package/src/commands/ui/preset-apply.ts +0 -19
- package/src/commands/ui/preset-list.ts +0 -12
- package/src/commands/ui/shared.ts +0 -25
- package/src/commands/update.ts +0 -356
- package/src/commands/use.ts +0 -86
- package/src/commands/view-serve.ts +0 -26
- package/src/commands/view.ts +0 -658
- package/src/connect-core.test.ts +0 -38
- package/src/connect-core.ts +0 -42
- package/src/connection/.spec/api.d.ts +0 -110
- package/src/connection/.spec/architecture.md +0 -58
- package/src/connection/.spec/capabilities/connection.ts +0 -13
- package/src/connection/.spec/examples/call.ts +0 -7
- package/src/connection/.spec/flows/command.ts +0 -20
- package/src/connection/.spec/flows/session.ts +0 -62
- package/src/connection/.spec/laws/connection.ts +0 -213
- package/src/connection/.spec/layout.ts +0 -22
- package/src/connection/__tests__/auth.test.ts +0 -129
- package/src/connection/__tests__/ca-fetch.test.ts +0 -60
- package/src/connection/__tests__/call.test.ts +0 -17
- package/src/connection/__tests__/credential.test.ts +0 -254
- package/src/connection/__tests__/errors.test.ts +0 -577
- package/src/connection/__tests__/exchange.test.ts +0 -404
- package/src/connection/__tests__/failure-fixtures.ts +0 -39
- package/src/connection/__tests__/failure-safety.test.ts +0 -75
- package/src/connection/__tests__/fixtures/localhost-cert.pem +0 -19
- package/src/connection/__tests__/fixtures/localhost-key.base64 +0 -1
- package/src/connection/__tests__/managed.test.ts +0 -39
- package/src/connection/__tests__/reasons.test.ts +0 -78
- package/src/connection/__tests__/self.test.ts +0 -71
- package/src/connection/__tests__/session.test.ts +0 -144
- package/src/connection/__tests__/target.test.ts +0 -95
- package/src/connection/auth.ts +0 -265
- package/src/connection/call.ts +0 -7
- package/src/connection/command.ts +0 -67
- package/src/connection/credential.ts +0 -138
- package/src/connection/errors.ts +0 -1
- package/src/connection/exchange.ts +0 -394
- package/src/connection/failure/classify.ts +0 -110
- package/src/connection/failure/index.ts +0 -18
- package/src/connection/failure/model.ts +0 -28
- package/src/connection/failure/render.ts +0 -116
- package/src/connection/index.ts +0 -8
- package/src/connection/lifetime.ts +0 -25
- package/src/connection/reasons.ts +0 -209
- package/src/connection/self.ts +0 -138
- package/src/connection/session.ts +0 -203
- package/src/connection/target.ts +0 -112
- package/src/connection/tsconfig.json +0 -6
- package/src/errors.ts +0 -66
- package/src/graph/.spec/api.d.ts +0 -41
- package/src/graph/.spec/laws/documents.ts +0 -24
- package/src/graph/.spec/laws/projection.ts +0 -13
- package/src/graph/.spec/layout.ts +0 -14
- package/src/graph/__tests__/mutation.test.ts +0 -51
- package/src/graph/__tests__/projection.test.ts +0 -22
- package/src/graph/__tests__/query.test.ts +0 -180
- package/src/graph/class.ts +0 -30
- package/src/graph/index.ts +0 -4
- package/src/graph/mutation.ts +0 -46
- package/src/graph/projection.ts +0 -21
- package/src/graph/query.ts +0 -95
- package/src/graph/tsconfig.json +0 -6
- package/src/identity/.history/ard/bootstrap-root-transfer.md +0 -57
- package/src/identity/.history/ard/domain-mediated-registration.md +0 -36
- package/src/identity/.spec/api.d.ts +0 -124
- package/src/identity/.spec/architecture.md +0 -33
- package/src/identity/.spec/capabilities/identity.ts +0 -7
- package/src/identity/.spec/flows/bootstrap-root.ts +0 -79
- package/src/identity/.spec/flows/domain-mediated-registration.ts +0 -51
- package/src/identity/.spec/flows/import.ts +0 -29
- package/src/identity/.spec/laws/identity.ts +0 -56
- package/src/identity/.spec/layout.ts +0 -13
- package/src/identity/__tests__/fixtures/registry-journey.ts +0 -65
- package/src/identity/__tests__/registration.test.ts +0 -64
- package/src/identity/__tests__/registry.test.ts +0 -74
- package/src/identity/__tests__/transfer.test.ts +0 -198
- package/src/identity/index.ts +0 -23
- package/src/identity/registration.ts +0 -72
- package/src/identity/registry.ts +0 -181
- package/src/identity/transfer.ts +0 -187
- package/src/identity/tsconfig.json +0 -6
- package/src/index.ts +0 -1
- package/src/keys/.spec/api.d.ts +0 -74
- package/src/keys/.spec/architecture.md +0 -13
- package/src/keys/.spec/laws/keys.ts +0 -43
- package/src/keys/.spec/layout.ts +0 -6
- package/src/keys/__tests__/algorithm.test.ts +0 -39
- package/src/keys/__tests__/keys.test.ts +0 -172
- package/src/keys/algorithm.ts +0 -33
- package/src/keys/credential.ts +0 -113
- package/src/keys/index.ts +0 -14
- package/src/keys/pair.ts +0 -219
- package/src/keys/tsconfig.json +0 -6
- package/src/lib/__tests__/admin-target.test.ts +0 -184
- package/src/lib/__tests__/binary.test.ts +0 -70
- package/src/lib/__tests__/browser-retention.test.ts +0 -212
- package/src/lib/__tests__/command-dx.test.ts +0 -102
- package/src/lib/__tests__/concurrency.test.ts +0 -62
- package/src/lib/__tests__/config.test.ts +0 -81
- package/src/lib/__tests__/design.test.ts +0 -99
- package/src/lib/__tests__/domain-publication.test.ts +0 -138
- package/src/lib/__tests__/format.test.ts +0 -22
- package/src/lib/__tests__/idp-session.driver.ts +0 -60
- package/src/lib/__tests__/idp-session.test.ts +0 -404
- package/src/lib/__tests__/idp.test.ts +0 -415
- package/src/lib/__tests__/instance-candidates.test.ts +0 -146
- package/src/lib/__tests__/instance-target.test.ts +0 -213
- package/src/lib/__tests__/instance.test.ts +0 -192
- package/src/lib/__tests__/local-status.test.ts +0 -202
- package/src/lib/__tests__/log-errors.test.ts +0 -64
- package/src/lib/__tests__/log.test.ts +0 -31
- package/src/lib/__tests__/meta.test.ts +0 -55
- package/src/lib/__tests__/output.test.ts +0 -162
- package/src/lib/__tests__/panel.test.ts +0 -40
- package/src/lib/__tests__/port.test.ts +0 -44
- package/src/lib/__tests__/prompt.test.ts +0 -25
- package/src/lib/__tests__/sdk-deps.test.ts +0 -68
- package/src/lib/__tests__/self.test.ts +0 -50
- package/src/lib/__tests__/skills.test.ts +0 -834
- package/src/lib/__tests__/studio-server-deps.test.ts +0 -111
- package/src/lib/__tests__/table.test.ts +0 -53
- package/src/lib/__tests__/update.test.ts +0 -511
- package/src/lib/__tests__/use-target.test.ts +0 -56
- package/src/lib/__tests__/validation.test.ts +0 -34
- package/src/lib/__tests__/view-assets.test.ts +0 -171
- package/src/lib/__tests__/view-external-open-intent.test.ts +0 -150
- package/src/lib/__tests__/view-external-open-origins.test.ts +0 -27
- package/src/lib/__tests__/view-open-intent.test.ts +0 -280
- package/src/lib/__tests__/view-port-allocation.test.ts +0 -82
- package/src/lib/__tests__/view-server.test.ts +0 -91
- package/src/lib/__tests__/view-session.test.ts +0 -100
- package/src/lib/__tests__/view-snapshot.test.ts +0 -77
- package/src/lib/admin-domain.ts +0 -43
- package/src/lib/admin-instance.ts +0 -56
- package/src/lib/admin-target.ts +0 -290
- package/src/lib/binary.ts +0 -127
- package/src/lib/browser-retention.ts +0 -210
- package/src/lib/browser.ts +0 -150
- package/src/lib/ca-fetch.ts +0 -133
- package/src/lib/command-dx.ts +0 -191
- package/src/lib/concurrency.ts +0 -31
- package/src/lib/config.ts +0 -57
- package/src/lib/credential-lifetime.ts +0 -24
- package/src/lib/domain-publication.ts +0 -104
- package/src/lib/failure-debug.ts +0 -26
- package/src/lib/format.ts +0 -4
- package/src/lib/idp-session.ts +0 -156
- package/src/lib/idp.ts +0 -926
- package/src/lib/instance-candidates.ts +0 -49
- package/src/lib/instance-target.ts +0 -197
- package/src/lib/instance.ts +0 -468
- package/src/lib/invocation.ts +0 -11
- package/src/lib/local-status.ts +0 -152
- package/src/lib/log.ts +0 -143
- package/src/lib/login-flow.ts +0 -201
- package/src/lib/meta.ts +0 -90
- package/src/lib/output.ts +0 -238
- package/src/lib/panel.ts +0 -61
- package/src/lib/port.ts +0 -41
- package/src/lib/proc.ts +0 -87
- package/src/lib/prompt.ts +0 -136
- package/src/lib/provision-instance.ts +0 -139
- package/src/lib/sdk-deps.ts +0 -104
- package/src/lib/self.ts +0 -22
- package/src/lib/skills/lock.ts +0 -117
- package/src/lib/skills/sync.ts +0 -878
- package/src/lib/skills.ts +0 -120
- package/src/lib/table.ts +0 -62
- package/src/lib/update.ts +0 -568
- package/src/lib/use-target.ts +0 -24
- package/src/lib/validation.ts +0 -59
- package/src/lib/view/assets.ts +0 -87
- package/src/lib/view/external-open-intent.ts +0 -41
- package/src/lib/view/external-open-origins.ts +0 -37
- package/src/lib/view/host-capabilities.ts +0 -17
- package/src/lib/view/open-intent.ts +0 -67
- package/src/lib/view/port-allocation.ts +0 -19
- package/src/lib/view/resolve.ts +0 -111
- package/src/lib/view/server.ts +0 -259
- package/src/lib/view/session.ts +0 -154
- package/src/lib/view/snapshot.ts +0 -101
- package/src/paths/.spec/api.d.ts +0 -13
- package/src/paths/.spec/architecture.md +0 -5
- package/src/paths/.spec/layout.ts +0 -3
- package/src/paths/index.ts +0 -13
- package/src/program/.spec/api.d.ts +0 -43
- package/src/program/.spec/architecture.md +0 -18
- package/src/program/.spec/laws/program.ts +0 -29
- package/src/program/.spec/layout.ts +0 -14
- package/src/program/__tests__/program.test.ts +0 -556
- package/src/program/argv.ts +0 -14
- package/src/program/build.ts +0 -205
- package/src/program/command.ts +0 -43
- package/src/program/index.ts +0 -3
- package/src/program/options.ts +0 -41
- package/src/program/registry.ts +0 -64
- package/src/program/tsconfig.json +0 -6
- package/src/setup/__tests__/instance-step.test.ts +0 -256
- package/src/setup/__tests__/util.test.ts +0 -29
- package/src/setup/engine.ts +0 -83
- package/src/setup/render.ts +0 -107
- package/src/setup/steps/admin.ts +0 -93
- package/src/setup/steps/agent-browser.ts +0 -81
- package/src/setup/steps/auth.ts +0 -54
- package/src/setup/steps/domain.ts +0 -68
- package/src/setup/steps/index.ts +0 -18
- package/src/setup/steps/instance.ts +0 -206
- package/src/setup/steps/skills-bridge.ts +0 -70
- package/src/setup/steps/skills.ts +0 -61
- package/src/setup/types.ts +0 -61
- package/src/setup/util.ts +0 -34
- package/src/state/.spec/api.d.ts +0 -152
- package/src/state/.spec/architecture.md +0 -45
- package/src/state/.spec/capabilities/state.ts +0 -7
- package/src/state/.spec/flows/identity-update.ts +0 -22
- package/src/state/.spec/laws/state.ts +0 -73
- package/src/state/.spec/layout.ts +0 -15
- package/src/state/__tests__/exchange-credentials.test.ts +0 -341
- package/src/state/__tests__/files.test.ts +0 -136
- package/src/state/__tests__/fixtures/identity-transition.ts +0 -37
- package/src/state/__tests__/fixtures/session-route-process.ts +0 -93
- package/src/state/__tests__/identities.test.ts +0 -142
- package/src/state/__tests__/paths.test.ts +0 -28
- package/src/state/__tests__/session-routes.test.ts +0 -138
- package/src/state/exchange-credentials.ts +0 -250
- package/src/state/files.ts +0 -170
- package/src/state/identities.ts +0 -229
- package/src/state/index.ts +0 -31
- package/src/state/paths.ts +0 -64
- package/src/state/session-routes.ts +0 -34
- package/src/state/tsconfig.json +0 -6
- package/src/telemetry/__tests__/analyze-log.test.ts +0 -38
- package/src/telemetry/__tests__/gate.test.ts +0 -63
- package/src/telemetry/__tests__/recorder.test.ts +0 -111
- package/src/telemetry/__tests__/redact.test.ts +0 -79
- package/src/telemetry/__tests__/retention.test.ts +0 -267
- package/src/telemetry/__tests__/session.test.ts +0 -166
- package/src/telemetry/__tests__/settings.test.ts +0 -102
- package/src/telemetry/__tests__/store-scan.test.ts +0 -128
- package/src/telemetry/__tests__/trigger.test.ts +0 -78
- package/src/telemetry/adapters/__tests__/claude-code.test.ts +0 -89
- package/src/telemetry/adapters/__tests__/codex.test.ts +0 -138
- package/src/telemetry/adapters/__tests__/index.test.ts +0 -88
- package/src/telemetry/adapters/claude-code.ts +0 -90
- package/src/telemetry/adapters/codex.ts +0 -160
- package/src/telemetry/adapters/index.ts +0 -45
- package/src/telemetry/adapters/types.ts +0 -21
- package/src/telemetry/analyze.ts +0 -249
- package/src/telemetry/gate.ts +0 -79
- package/src/telemetry/recorder.ts +0 -68
- package/src/telemetry/redact.ts +0 -53
- package/src/telemetry/retention.ts +0 -176
- package/src/telemetry/session.ts +0 -94
- package/src/telemetry/settings.ts +0 -79
- package/src/telemetry/store.ts +0 -174
- package/src/telemetry/trigger.ts +0 -107
- package/src/telemetry/types.ts +0 -64
- package/src/test-utils.ts +0 -18
- package/src/ui/.spec/api.d.ts +0 -14
- package/src/ui/.spec/architecture.md +0 -14
- package/src/ui/.spec/laws.ts +0 -50
- package/src/ui/.spec/layout.ts +0 -16
- package/src/ui/__tests__/ui.test.ts +0 -1742
- package/src/ui/index.ts +0 -13
- package/src/ui/lock.ts +0 -89
- package/src/ui/model.ts +0 -83
- package/src/ui/operations.ts +0 -1044
- package/src/ui/project.ts +0 -333
- package/src/ui/release.ts +0 -398
- package/src/ui/runner.ts +0 -18
- package/studio/client/dist/assets/index-BFVFs_8x.js +0 -81
- package/studio/client/dist/assets/index-DuB9iUdu.css +0 -2
- package/studio/client/dist/assets/schema-studio-DH6oEWME.js +0 -8
- package/studio/tsconfig.json +0 -24
- package/viewer/tsconfig.json +0 -13
package/src/lib/view/assets.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { existsSync, statSync } from 'node:fs'
|
|
2
|
-
import { copyFile } from 'node:fs/promises'
|
|
3
|
-
import { dirname, join } from 'node:path'
|
|
4
|
-
import { fileURLToPath } from 'node:url'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* The prebuilt host-page bundle, shipped next to the CLI module
|
|
8
|
-
* (`<pkg>/viewer/dist`). The module URL is authoritative because npm global
|
|
9
|
-
* installs expose the CLI through a bin symlink outside the package root.
|
|
10
|
-
*/
|
|
11
|
-
export function viewerDistDir(
|
|
12
|
-
moduleUrl = import.meta.url,
|
|
13
|
-
entry = process.argv[1] ?? '.',
|
|
14
|
-
executable = process.execPath,
|
|
15
|
-
): string {
|
|
16
|
-
const override = process.env.ASTRALE_VIEWER_DIR
|
|
17
|
-
if (override) return override
|
|
18
|
-
|
|
19
|
-
const moduleDirectory = dirname(fileURLToPath(moduleUrl))
|
|
20
|
-
const published = join(moduleDirectory, '..', 'viewer', 'dist')
|
|
21
|
-
const source = join(moduleDirectory, '..', '..', '..', 'viewer', 'dist')
|
|
22
|
-
const legacy = join(dirname(entry), '..', 'viewer', 'dist')
|
|
23
|
-
const standalone = entry.startsWith('/$bunfs/')
|
|
24
|
-
? join(dirname(executable), 'viewer', 'dist')
|
|
25
|
-
: undefined
|
|
26
|
-
const complete = [standalone, published, source, legacy].find(
|
|
27
|
-
(candidate): candidate is string => candidate !== undefined && hasViewerBundle(candidate),
|
|
28
|
-
)
|
|
29
|
-
if (complete) return complete
|
|
30
|
-
|
|
31
|
-
// A source checkout may intentionally omit generated dist assets. Keep the
|
|
32
|
-
// source-derived destination so ensureViewerAssets can build it on demand.
|
|
33
|
-
if (hasViewerSource(join(source, '..'))) return source
|
|
34
|
-
|
|
35
|
-
// Published installs cannot rebuild: preserve the package-relative path in
|
|
36
|
-
// the diagnostic instead of pointing at the npm prefix beside the bin link.
|
|
37
|
-
return published
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Ensure the host bundle exists; on a dev checkout, build it with Bun. */
|
|
41
|
-
export async function ensureViewerAssets(
|
|
42
|
-
moduleUrl = import.meta.url,
|
|
43
|
-
entry = process.argv[1] ?? '.',
|
|
44
|
-
): Promise<string> {
|
|
45
|
-
const dist = viewerDistDir(moduleUrl, entry)
|
|
46
|
-
const srcDir = join(dist, '..')
|
|
47
|
-
if (hasViewerBundle(dist) && !viewerSourceIsNewer(srcDir, dist)) return dist
|
|
48
|
-
const bun = (
|
|
49
|
-
globalThis as { Bun?: { build: (o: object) => Promise<{ success: boolean; logs: unknown[] }> } }
|
|
50
|
-
).Bun
|
|
51
|
-
if (bun && hasViewerSource(srcDir)) {
|
|
52
|
-
const result = await bun.build({
|
|
53
|
-
entrypoints: [join(srcDir, 'main.ts')],
|
|
54
|
-
outdir: dist,
|
|
55
|
-
target: 'browser',
|
|
56
|
-
minify: false,
|
|
57
|
-
})
|
|
58
|
-
if (!result.success) throw new Error(`viewer build failed: ${result.logs.join('\n')}`)
|
|
59
|
-
await copyFile(join(srcDir, 'index.html'), join(dist, 'index.html'))
|
|
60
|
-
return dist
|
|
61
|
-
}
|
|
62
|
-
throw new Error(
|
|
63
|
-
`viewer bundle missing at ${dist} — reinstall the CLI (or run \`bun scripts/build.ts\` in a dev checkout)`,
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function hasViewerBundle(directory: string): boolean {
|
|
68
|
-
return existsSync(join(directory, 'main.js')) && existsSync(join(directory, 'index.html'))
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function hasViewerSource(directory: string): boolean {
|
|
72
|
-
return existsSync(join(directory, 'main.ts')) && existsSync(join(directory, 'index.html'))
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function viewerSourceIsNewer(source: string, dist: string): boolean {
|
|
76
|
-
if (!hasViewerSource(source)) return false
|
|
77
|
-
if (!hasViewerBundle(dist)) return true
|
|
78
|
-
const newestSource = Math.max(
|
|
79
|
-
statSync(join(source, 'main.ts')).mtimeMs,
|
|
80
|
-
statSync(join(source, 'index.html')).mtimeMs,
|
|
81
|
-
)
|
|
82
|
-
const oldestOutput = Math.min(
|
|
83
|
-
statSync(join(dist, 'main.js')).mtimeMs,
|
|
84
|
-
statSync(join(dist, 'index.html')).mtimeMs,
|
|
85
|
-
)
|
|
86
|
-
return newestSource > oldestOutput
|
|
87
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { ExternalOpenRequest, IntentMessage, Shell } from '@astrale-os/shell'
|
|
2
|
-
|
|
3
|
-
import { replyToIntent } from '@astrale-os/shell'
|
|
4
|
-
|
|
5
|
-
export interface ExternalOpenIntentHost {
|
|
6
|
-
open(request: ExternalOpenRequest): boolean
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/** Register the root host's browser-owned external navigation effect. */
|
|
10
|
-
export function installExternalOpenIntentHandler(
|
|
11
|
-
shell: Shell,
|
|
12
|
-
host: ExternalOpenIntentHost,
|
|
13
|
-
): () => void {
|
|
14
|
-
return shell.onIntent('browser.openExternal', (message) => {
|
|
15
|
-
const opened = host.open(message.envelope.payload)
|
|
16
|
-
replyToIntent(shell.children, message.envelope.sender.windowId, message, {
|
|
17
|
-
outcome: opened ? 'opened' : 'blocked',
|
|
18
|
-
})
|
|
19
|
-
})
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Open an external document without retaining a cross-origin opener capability. */
|
|
23
|
-
export function openExternalBrowserWindow(
|
|
24
|
-
browser: Pick<Window, 'open'>,
|
|
25
|
-
request: ExternalOpenRequest,
|
|
26
|
-
): boolean {
|
|
27
|
-
const popup = request.mode === 'popup'
|
|
28
|
-
const opened = browser.open('', '_blank', popup ? 'popup,width=720,height=760' : undefined)
|
|
29
|
-
if (opened === null) return false
|
|
30
|
-
try {
|
|
31
|
-
opened.opener = null
|
|
32
|
-
if (opened.opener !== null) throw new Error('Browser retained the opener capability.')
|
|
33
|
-
opened.location.replace(request.url)
|
|
34
|
-
} catch {
|
|
35
|
-
opened.close()
|
|
36
|
-
return false
|
|
37
|
-
}
|
|
38
|
-
return true
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type ExternalOpenIntentMessage = IntentMessage<'browser.openExternal'>
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { AstraleError } from '../../errors.js'
|
|
2
|
-
|
|
3
|
-
/** Admit exact HTTPS origins explicitly granted by the CLI operator. */
|
|
4
|
-
export function admitExternalOpenOrigins(input: readonly string[] | undefined): readonly string[] {
|
|
5
|
-
if (input === undefined) return Object.freeze([])
|
|
6
|
-
const admitted = new Set<string>()
|
|
7
|
-
for (const candidate of input) {
|
|
8
|
-
let url: URL
|
|
9
|
-
try {
|
|
10
|
-
url = new URL(candidate)
|
|
11
|
-
} catch (cause) {
|
|
12
|
-
throw invalidExternalOrigin(candidate, cause)
|
|
13
|
-
}
|
|
14
|
-
if (
|
|
15
|
-
url.protocol !== 'https:' ||
|
|
16
|
-
url.hostname.includes('*') ||
|
|
17
|
-
url.username !== '' ||
|
|
18
|
-
url.password !== '' ||
|
|
19
|
-
url.pathname !== '/' ||
|
|
20
|
-
url.search !== '' ||
|
|
21
|
-
url.hash !== ''
|
|
22
|
-
) {
|
|
23
|
-
throw invalidExternalOrigin(candidate)
|
|
24
|
-
}
|
|
25
|
-
admitted.add(url.origin)
|
|
26
|
-
}
|
|
27
|
-
return Object.freeze([...admitted])
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function invalidExternalOrigin(candidate: string, cause?: unknown): AstraleError {
|
|
31
|
-
return new AstraleError(
|
|
32
|
-
'INVALID_EXTERNAL_ORIGIN',
|
|
33
|
-
`External navigation grant "${candidate}" is not an exact HTTPS origin.`,
|
|
34
|
-
'Pass an origin such as https://connect.example.com with no credentials, path, query, or fragment.',
|
|
35
|
-
cause === undefined ? undefined : { cause },
|
|
36
|
-
)
|
|
37
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { HostCapabilities } from '@astrale-os/shell'
|
|
2
|
-
|
|
3
|
-
import { admitHostCapabilities } from '@astrale-os/shell'
|
|
4
|
-
|
|
5
|
-
/** Build the exact host grant handed to a CLI-hosted View session. */
|
|
6
|
-
export function viewHostCapabilities(externalOrigins: readonly string[]): HostCapabilities {
|
|
7
|
-
return admitHostCapabilities({
|
|
8
|
-
version: 1,
|
|
9
|
-
navigation: {
|
|
10
|
-
openView: {},
|
|
11
|
-
...(externalOrigins.length === 0 ? {} : { external: { origins: externalOrigins } }),
|
|
12
|
-
},
|
|
13
|
-
actions: {},
|
|
14
|
-
browser: {},
|
|
15
|
-
access: {},
|
|
16
|
-
})
|
|
17
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { IntentMessage, MountedWindow, ResolvedView, Shell } from '@astrale-os/shell'
|
|
2
|
-
|
|
3
|
-
export interface OpenIntentHost {
|
|
4
|
-
current(): MountedWindow | null
|
|
5
|
-
setCurrent(window: MountedWindow): void
|
|
6
|
-
mount(view: ResolvedView): Promise<MountedWindow>
|
|
7
|
-
opened(view: ResolvedView): void
|
|
8
|
-
failed(error: unknown): void
|
|
9
|
-
reply(message: IntentMessage<'open'>, windowId: string): void
|
|
10
|
-
reject(message: IntentMessage<'open'>, error: unknown): void
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Register the root host's serialized node-to-View navigation handler. */
|
|
14
|
-
export function installOpenIntentHandler(shell: Shell, host: OpenIntentHost): () => void {
|
|
15
|
-
let queue = Promise.resolve()
|
|
16
|
-
return shell.onIntent('open', (message) => {
|
|
17
|
-
const run = queue.then(() => handleOpenIntent(shell, host, message))
|
|
18
|
-
queue = run.catch(() => {})
|
|
19
|
-
return run
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function handleOpenIntent(
|
|
24
|
-
shell: Pick<Shell, 'views'>,
|
|
25
|
-
host: OpenIntentHost,
|
|
26
|
-
message: IntentMessage<'open'>,
|
|
27
|
-
): Promise<void> {
|
|
28
|
-
const { nodeId, viewId } = message.envelope.payload
|
|
29
|
-
try {
|
|
30
|
-
const selected = selectResolvedView(await shell.views.resolve(nodeId), viewId)
|
|
31
|
-
const previous = host.current()
|
|
32
|
-
const next = await host.mount(selected)
|
|
33
|
-
|
|
34
|
-
host.setCurrent(next)
|
|
35
|
-
host.opened(selected)
|
|
36
|
-
// A correlated requester is normally `previous`; answer while its channel
|
|
37
|
-
// still exists, then retire the old mount.
|
|
38
|
-
host.reply(message, next.windowId)
|
|
39
|
-
|
|
40
|
-
if (previous && previous.windowId !== next.windowId) {
|
|
41
|
-
try {
|
|
42
|
-
const closed = await previous.close({ force: true })
|
|
43
|
-
if (closed.kind === 'refused') {
|
|
44
|
-
host.failed(new Error(closed.reason ?? `Window ${previous.windowId} refused to close`))
|
|
45
|
-
}
|
|
46
|
-
} catch (error) {
|
|
47
|
-
host.failed(error)
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
} catch (error) {
|
|
51
|
-
host.reject(message, error)
|
|
52
|
-
host.failed(error)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function selectResolvedView(
|
|
57
|
-
views: readonly ResolvedView[],
|
|
58
|
-
viewId: string | undefined,
|
|
59
|
-
): ResolvedView {
|
|
60
|
-
const selected = viewId
|
|
61
|
-
? views.find((view) => view.route.key === viewId || `/:${view.route.key}` === viewId)
|
|
62
|
-
: views[0]
|
|
63
|
-
if (selected) return selected
|
|
64
|
-
throw new Error(
|
|
65
|
-
viewId ? `View ${viewId} does not resolve for this node` : 'No view resolves for this node',
|
|
66
|
-
)
|
|
67
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { join } from 'node:path'
|
|
2
|
-
|
|
3
|
-
import { withFileLock } from '../../state/index'
|
|
4
|
-
import { VIEW_DIR } from './session'
|
|
5
|
-
|
|
6
|
-
const VIEW_PORT_LOCK = join(VIEW_DIR, 'ports.lock')
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Keep the free-port probe and detached server startup in one cross-process
|
|
10
|
-
* critical section. A probe alone is only advisory: without this lock, two
|
|
11
|
-
* concurrent `astrale view` processes can both observe 4419 as free before
|
|
12
|
-
* either child binds it.
|
|
13
|
-
*/
|
|
14
|
-
export function withViewPortAllocationLock<T>(
|
|
15
|
-
fn: () => Promise<T>,
|
|
16
|
-
lockPath = VIEW_PORT_LOCK,
|
|
17
|
-
): Promise<T> {
|
|
18
|
-
return withFileLock(lockPath, fn)
|
|
19
|
-
}
|
package/src/lib/view/resolve.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import type { ResolvedView as SessionResolvedView } from '@astrale-os/sdk/client/session'
|
|
2
|
-
import type { ResolvedView } from '@astrale-os/shell'
|
|
3
|
-
|
|
4
|
-
import { Path } from '@astrale-os/sdk/graph/path'
|
|
5
|
-
|
|
6
|
-
import type { ConnectionContext } from '../../connection'
|
|
7
|
-
|
|
8
|
-
import { AstraleError } from '../../errors'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* View + target resolution for `astrale view`. Both spec shapes funnel through
|
|
12
|
-
* the kernel's `View:resolve` syscall: a ViewPath resolves to the view node
|
|
13
|
-
* itself (its class implements UI), a target path lists the views attached to
|
|
14
|
-
* it via `view_for`.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const VIEW_PATH_RE = /^\/:[^\s/:@]+:view\.[a-z][a-z0-9-]*$/
|
|
18
|
-
|
|
19
|
-
export type ViewSpec = { kind: 'view' | 'target'; path: string }
|
|
20
|
-
|
|
21
|
-
export function parseViewSpec(spec: string): ViewSpec {
|
|
22
|
-
if (VIEW_PATH_RE.test(spec)) return { kind: 'view', path: spec }
|
|
23
|
-
if (spec.startsWith('/') || spec.startsWith('@')) return { kind: 'target', path: spec }
|
|
24
|
-
throw new AstraleError(
|
|
25
|
-
'INVALID_ARGUMENT',
|
|
26
|
-
`"${spec}" is neither a ViewPath (/:origin:view.slug) nor a node path (/… or @id)`,
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** The owning Domain principal used when an explicit ViewPath has no target override. */
|
|
31
|
-
export function viewOwnerTarget(viewPath: string): string {
|
|
32
|
-
const parsed = Path.parse(viewPath)
|
|
33
|
-
if (parsed.ast.anchor.kind !== 'domain') {
|
|
34
|
-
throw new AstraleError('INVALID_ARGUMENT', `ViewPath has no Domain owner: ${viewPath}`)
|
|
35
|
-
}
|
|
36
|
-
return `/:${parsed.ast.anchor.origin}`
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** One exact Shell selection plus stable presentation fields for CLI output. */
|
|
40
|
-
export type ViewCandidate = ResolvedView & {
|
|
41
|
-
id: string
|
|
42
|
-
path: string
|
|
43
|
-
url: string
|
|
44
|
-
name?: string
|
|
45
|
-
handshake: 'shell' | 'none'
|
|
46
|
-
origin: 'self' | 'class'
|
|
47
|
-
issuer: string
|
|
48
|
-
etag: string
|
|
49
|
-
revision: string
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function resolveViewCandidates(
|
|
53
|
-
ctx: ConnectionContext,
|
|
54
|
-
nodePath: string,
|
|
55
|
-
): Promise<ViewCandidate[]> {
|
|
56
|
-
const target = Path.parse(nodePath).raw
|
|
57
|
-
const catalog = await ctx.session.viewsFor(target)
|
|
58
|
-
return catalog.views.map((route) => toCandidate(target, route))
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function toCandidate(target: ResolvedView['target'], route: SessionResolvedView): ViewCandidate {
|
|
62
|
-
const key = String(route.key)
|
|
63
|
-
return Object.freeze({
|
|
64
|
-
target,
|
|
65
|
-
route,
|
|
66
|
-
id: key,
|
|
67
|
-
path: `/:${key}`,
|
|
68
|
-
url: route.href,
|
|
69
|
-
name: key.slice(key.lastIndexOf(':view.') + ':view.'.length),
|
|
70
|
-
handshake: route.handshake,
|
|
71
|
-
origin: route.declaration.target.kind === 'domain' ? 'self' : 'class',
|
|
72
|
-
issuer: route.issuer,
|
|
73
|
-
etag: route.etag,
|
|
74
|
-
revision: route.revision,
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Strip presentation aliases before crossing the Shell mount boundary. */
|
|
79
|
-
export function selectedView(candidate: ViewCandidate): ResolvedView {
|
|
80
|
-
return Object.freeze({ target: candidate.target, route: candidate.route })
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** The slug tail of a candidate: `view.dashboard` → `dashboard`. */
|
|
84
|
-
export function candidateSlug(candidate: ViewCandidate): string {
|
|
85
|
-
return candidate.name ?? candidate.id.slice(candidate.id.lastIndexOf(':view.') + ':view.'.length)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function pickCandidate(
|
|
89
|
-
candidates: ViewCandidate[],
|
|
90
|
-
nodePath: string,
|
|
91
|
-
slug?: string,
|
|
92
|
-
): ViewCandidate | 'ambiguous' {
|
|
93
|
-
if (slug) {
|
|
94
|
-
const match = candidates.find(
|
|
95
|
-
(candidate) =>
|
|
96
|
-
candidate.id === slug || candidate.path === slug || candidateSlug(candidate) === slug,
|
|
97
|
-
)
|
|
98
|
-
if (!match) {
|
|
99
|
-
throw new AstraleError(
|
|
100
|
-
'VIEW_NOT_FOUND',
|
|
101
|
-
`No view "${slug}" on ${nodePath} — available: ${candidates.map(candidateSlug).join(', ') || '(none)'}`,
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
return match
|
|
105
|
-
}
|
|
106
|
-
if (candidates.length === 0) {
|
|
107
|
-
throw new AstraleError('VIEW_NOT_FOUND', `No views resolve on ${nodePath}`)
|
|
108
|
-
}
|
|
109
|
-
if (candidates.length === 1) return candidates[0]
|
|
110
|
-
return 'ambiguous'
|
|
111
|
-
}
|
package/src/lib/view/server.ts
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import type { AuthApi } from '@astrale-os/sdk/auth'
|
|
2
|
-
import type { IssuerId } from '@astrale-os/sdk/auth'
|
|
3
|
-
import type { ReadableStream as WebReadableStream } from 'node:stream/web'
|
|
4
|
-
|
|
5
|
-
import { readFile } from 'node:fs/promises'
|
|
6
|
-
import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'
|
|
7
|
-
import { join } from 'node:path'
|
|
8
|
-
import { Readable } from 'node:stream'
|
|
9
|
-
|
|
10
|
-
import type { ViewServeConfig } from './session'
|
|
11
|
-
|
|
12
|
-
import { withClientSession } from '../../connection'
|
|
13
|
-
import { fetchWithCaFile } from '../ca-fetch'
|
|
14
|
-
import { viewerDistDir } from './assets'
|
|
15
|
-
import { removeSessionFiles } from './session'
|
|
16
|
-
|
|
17
|
-
export { ensureViewerAssets, viewerDistDir } from './assets'
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* The view-session server: serves the host page, hands it config and fresh
|
|
21
|
-
* credentials, receives its lifecycle reports, and proxies the view's kernel
|
|
22
|
-
* calls (one mechanism for CORS, self-signed local CAs, and keeping the kernel
|
|
23
|
-
* origin out of the page). Everything is nonce-scoped under `/s/<nonce>/`;
|
|
24
|
-
* loopback-only. Exits after `idleMs` without a request — the host page's
|
|
25
|
-
* heartbeat keeps a live session alive.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
const VIEW_TOKEN_TTL_SECONDS = 4 * 60
|
|
29
|
-
const TOKEN_REFRESH_MARGIN_MS = 60_000
|
|
30
|
-
const FALLBACK_TOKEN_TTL_MS = VIEW_TOKEN_TTL_SECONDS * 1_000
|
|
31
|
-
const IDLE_SWEEP_MS = 60_000
|
|
32
|
-
|
|
33
|
-
export type PageStatus = { state: string; error?: string; at: string }
|
|
34
|
-
|
|
35
|
-
type TokenGrant = { token: string; expiresAt: number; kind: 'minted' }
|
|
36
|
-
|
|
37
|
-
export function startViewServer(config: ViewServeConfig): Server {
|
|
38
|
-
const { session, proxy } = config
|
|
39
|
-
const base = `/s/${session.nonce}`
|
|
40
|
-
const hostDir = viewerDistDir()
|
|
41
|
-
const proxyFetch = proxy.caFile ? fetchWithCaFile(proxy.caFile) : globalThis.fetch
|
|
42
|
-
let status: PageStatus = { state: 'waiting', at: new Date().toISOString() }
|
|
43
|
-
let grant: TokenGrant | null = null
|
|
44
|
-
let lastActivity = Date.now()
|
|
45
|
-
|
|
46
|
-
/** Mint one TTL-bound credential; raw CLI credentials never enter the browser session. */
|
|
47
|
-
async function freshGrant(): Promise<TokenGrant> {
|
|
48
|
-
if (grant && grant.expiresAt - Date.now() > TOKEN_REFRESH_MARGIN_MS) return grant
|
|
49
|
-
grant = await withClientSession(config.kernel, async ({ auth, target }) => {
|
|
50
|
-
const token = await mintViewCredential(auth, target.kernelIssuer)
|
|
51
|
-
return {
|
|
52
|
-
token,
|
|
53
|
-
expiresAt: jwtExpiry(token) ?? Date.now() + FALLBACK_TOKEN_TTL_MS,
|
|
54
|
-
kind: 'minted' as const,
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
return grant
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const server = createServer((req, res) => {
|
|
61
|
-
lastActivity = Date.now()
|
|
62
|
-
void route(req, res).catch((error: unknown) => {
|
|
63
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
64
|
-
// CORS headers even on failures — the caller may be the cross-origin
|
|
65
|
-
// view iframe, and an opaque error reads as a network failure there.
|
|
66
|
-
if (!res.headersSent) {
|
|
67
|
-
res.writeHead(502, {
|
|
68
|
-
'content-type': 'application/json',
|
|
69
|
-
...corsHeaders(req.headers.origin),
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
res.end(JSON.stringify({ error: message }))
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
async function route(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
77
|
-
const url = new URL(req.url ?? '/', 'http://localhost')
|
|
78
|
-
if (!url.pathname.startsWith(`${base}/`) && url.pathname !== base) {
|
|
79
|
-
res.writeHead(404).end()
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
const sub = url.pathname.slice(base.length) || '/'
|
|
83
|
-
|
|
84
|
-
if (sub === '/k' || sub.startsWith('/k/')) {
|
|
85
|
-
await proxyKernel(req, res, sub.slice('/k'.length), url.search)
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
if (sub === '/' || sub === '/index.html') {
|
|
89
|
-
await serveAsset(res, join(hostDir, 'index.html'), 'text/html; charset=utf-8')
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
if (sub === '/main.js') {
|
|
93
|
-
await serveAsset(res, join(hostDir, 'main.js'), 'text/javascript; charset=utf-8')
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
if (sub === '/config.json' && req.method === 'GET') {
|
|
97
|
-
json(res, 200, {
|
|
98
|
-
view: session.view,
|
|
99
|
-
kernelUrl: proxy.direct ? proxy.kernelUrl : `${base}/k`,
|
|
100
|
-
kernelIssuer: proxy.issuer,
|
|
101
|
-
identity: session.identity ?? null,
|
|
102
|
-
instance: session.instance ?? null,
|
|
103
|
-
sessionId: session.id,
|
|
104
|
-
externalOrigins: config.externalOrigins,
|
|
105
|
-
})
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
if (sub === '/token' && req.method === 'POST') {
|
|
109
|
-
if (session.view.route.handshake !== 'shell') {
|
|
110
|
-
json(res, 403, { error: 'plain views have no Astrale credential privilege' })
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
|
-
const fresh = await freshGrant()
|
|
114
|
-
json(res, 200, { token: fresh.token, expiresAt: fresh.expiresAt, kind: fresh.kind })
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
if (sub === '/status' && req.method === 'POST') {
|
|
118
|
-
const body = await readJson(req)
|
|
119
|
-
if (body && typeof body.state === 'string' && body.state !== 'alive') {
|
|
120
|
-
status = { state: body.state, error: asString(body.error), at: new Date().toISOString() }
|
|
121
|
-
}
|
|
122
|
-
res.writeHead(204).end()
|
|
123
|
-
return
|
|
124
|
-
}
|
|
125
|
-
if (sub === '/state' && req.method === 'GET') {
|
|
126
|
-
json(res, 200, status)
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
res.writeHead(404).end()
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** Forward a kernel call: verbatim headers/body both ways, CORS added. */
|
|
133
|
-
async function proxyKernel(
|
|
134
|
-
req: IncomingMessage,
|
|
135
|
-
res: ServerResponse,
|
|
136
|
-
suffix: string,
|
|
137
|
-
search: string,
|
|
138
|
-
): Promise<void> {
|
|
139
|
-
const origin = req.headers.origin
|
|
140
|
-
if (req.method === 'OPTIONS') {
|
|
141
|
-
res.writeHead(204, corsHeaders(origin)).end()
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
const target = joinUrl(proxy.kernelUrl, suffix) + search
|
|
145
|
-
const headers: Record<string, string> = {}
|
|
146
|
-
for (const name of ['authorization', 'content-type', 'accept']) {
|
|
147
|
-
const value = req.headers[name]
|
|
148
|
-
if (typeof value === 'string') headers[name] = value
|
|
149
|
-
}
|
|
150
|
-
// Buffer the request body (envelope requests are small JSON): the caFile
|
|
151
|
-
// fetch path speaks node https and cannot consume a web-stream body.
|
|
152
|
-
// Response bodies still stream through.
|
|
153
|
-
const hasBody = req.method !== 'GET' && req.method !== 'HEAD'
|
|
154
|
-
const body = hasBody ? Buffer.concat(await collect(req)) : undefined
|
|
155
|
-
const upstream = await proxyFetch(target, {
|
|
156
|
-
method: req.method,
|
|
157
|
-
headers,
|
|
158
|
-
...(body !== undefined ? { body } : {}),
|
|
159
|
-
} as RequestInit)
|
|
160
|
-
const responseHeaders: Record<string, string> = corsHeaders(origin)
|
|
161
|
-
const contentType = upstream.headers.get('content-type')
|
|
162
|
-
if (contentType) responseHeaders['content-type'] = contentType
|
|
163
|
-
res.writeHead(upstream.status, responseHeaders)
|
|
164
|
-
if (upstream.body) {
|
|
165
|
-
Readable.fromWeb(upstream.body as unknown as WebReadableStream).pipe(res)
|
|
166
|
-
} else {
|
|
167
|
-
res.end()
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const idleTimer = setInterval(() => {
|
|
172
|
-
if (Date.now() - lastActivity > config.idleMs) void shutdown(0)
|
|
173
|
-
}, IDLE_SWEEP_MS)
|
|
174
|
-
idleTimer.unref()
|
|
175
|
-
|
|
176
|
-
async function shutdown(code: number): Promise<void> {
|
|
177
|
-
clearInterval(idleTimer)
|
|
178
|
-
server.close()
|
|
179
|
-
await removeSessionFiles(session.id)
|
|
180
|
-
process.exit(code)
|
|
181
|
-
}
|
|
182
|
-
process.on('SIGTERM', () => void shutdown(0))
|
|
183
|
-
process.on('SIGINT', () => void shutdown(0))
|
|
184
|
-
|
|
185
|
-
server.listen(session.port, '127.0.0.1')
|
|
186
|
-
return server
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Mint a short-lived credential from the selected CLI identity proof. */
|
|
190
|
-
export async function mintViewCredential(
|
|
191
|
-
auth: Pick<AuthApi, 'mint'>,
|
|
192
|
-
audience: IssuerId,
|
|
193
|
-
): Promise<string> {
|
|
194
|
-
return auth.mint({
|
|
195
|
-
audience,
|
|
196
|
-
ttlSeconds: VIEW_TOKEN_TTL_SECONDS,
|
|
197
|
-
})
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function jwtExpiry(token: string): number | null {
|
|
201
|
-
try {
|
|
202
|
-
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf8')) as {
|
|
203
|
-
exp?: number
|
|
204
|
-
}
|
|
205
|
-
return typeof payload.exp === 'number' ? payload.exp * 1000 : null
|
|
206
|
-
} catch {
|
|
207
|
-
return null
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function corsHeaders(origin: string | undefined): Record<string, string> {
|
|
212
|
-
return {
|
|
213
|
-
'access-control-allow-origin': origin ?? '*',
|
|
214
|
-
'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
|
|
215
|
-
'access-control-allow-headers': 'authorization, content-type, accept',
|
|
216
|
-
// Chrome private-network-access preflight (public page → loopback proxy).
|
|
217
|
-
'access-control-allow-private-network': 'true',
|
|
218
|
-
'access-control-max-age': '600',
|
|
219
|
-
vary: 'origin',
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function joinUrl(baseUrl: string, suffix: string): string {
|
|
224
|
-
if (!suffix) return baseUrl
|
|
225
|
-
return baseUrl.replace(/\/$/, '') + suffix
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function json(res: ServerResponse, code: number, body: unknown): void {
|
|
229
|
-
res.writeHead(code, { 'content-type': 'application/json' })
|
|
230
|
-
res.end(JSON.stringify(body))
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async function serveAsset(res: ServerResponse, file: string, contentType: string): Promise<void> {
|
|
234
|
-
try {
|
|
235
|
-
const content = await readFile(file)
|
|
236
|
-
res.writeHead(200, { 'content-type': contentType, 'cache-control': 'no-store' })
|
|
237
|
-
res.end(content)
|
|
238
|
-
} catch {
|
|
239
|
-
res.writeHead(404).end()
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async function collect(req: IncomingMessage): Promise<Buffer[]> {
|
|
244
|
-
const chunks: Buffer[] = []
|
|
245
|
-
for await (const chunk of req) chunks.push(chunk as Buffer)
|
|
246
|
-
return chunks
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async function readJson(req: IncomingMessage): Promise<Record<string, unknown> | null> {
|
|
250
|
-
try {
|
|
251
|
-
return JSON.parse(Buffer.concat(await collect(req)).toString('utf8')) as Record<string, unknown>
|
|
252
|
-
} catch {
|
|
253
|
-
return null
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function asString(value: unknown): string | undefined {
|
|
258
|
-
return typeof value === 'string' ? value : undefined
|
|
259
|
-
}
|