@wjarka/cezarion 0.10.0-pr30.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +838 -0
- package/dist/agent-config/account-identity.d.ts +44 -0
- package/dist/agent-config/account-identity.js +128 -0
- package/dist/agent-config/account-identity.js.map +1 -0
- package/dist/agent-config/catalog.d.ts +72 -0
- package/dist/agent-config/catalog.js +223 -0
- package/dist/agent-config/catalog.js.map +1 -0
- package/dist/agent-config/files.d.ts +46 -0
- package/dist/agent-config/files.js +119 -0
- package/dist/agent-config/files.js.map +1 -0
- package/dist/agent-config/model-settings/claude.d.ts +2 -0
- package/dist/agent-config/model-settings/claude.js +11 -0
- package/dist/agent-config/model-settings/claude.js.map +1 -0
- package/dist/agent-config/model-settings/codex.d.ts +2 -0
- package/dist/agent-config/model-settings/codex.js +16 -0
- package/dist/agent-config/model-settings/codex.js.map +1 -0
- package/dist/agent-config/model-settings/opencode.d.ts +2 -0
- package/dist/agent-config/model-settings/opencode.js +8 -0
- package/dist/agent-config/model-settings/opencode.js.map +1 -0
- package/dist/agent-config/model-settings/pi.d.ts +12 -0
- package/dist/agent-config/model-settings/pi.js +18 -0
- package/dist/agent-config/model-settings/pi.js.map +1 -0
- package/dist/agent-config/model-settings/shared.d.ts +9 -0
- package/dist/agent-config/model-settings/shared.js +93 -0
- package/dist/agent-config/model-settings/shared.js.map +1 -0
- package/dist/agent-config/model-settings/types.d.ts +9 -0
- package/dist/agent-config/model-settings/types.js +2 -0
- package/dist/agent-config/model-settings/types.js.map +1 -0
- package/dist/agent-config/models.d.ts +9 -0
- package/dist/agent-config/models.js +32 -0
- package/dist/agent-config/models.js.map +1 -0
- package/dist/agent-config/seed.d.ts +7 -0
- package/dist/agent-config/seed.js +90 -0
- package/dist/agent-config/seed.js.map +1 -0
- package/dist/agent-config/service.d.ts +44 -0
- package/dist/agent-config/service.js +71 -0
- package/dist/agent-config/service.js.map +1 -0
- package/dist/agent-config/validate.d.ts +21 -0
- package/dist/agent-config/validate.js +76 -0
- package/dist/agent-config/validate.js.map +1 -0
- package/dist/automations/coordinator.d.ts +26 -0
- package/dist/automations/coordinator.js +66 -0
- package/dist/automations/coordinator.js.map +1 -0
- package/dist/automations/github-poller.d.ts +74 -0
- package/dist/automations/github-poller.js +234 -0
- package/dist/automations/github-poller.js.map +1 -0
- package/dist/automations/scheduler.d.ts +42 -0
- package/dist/automations/scheduler.js +192 -0
- package/dist/automations/scheduler.js.map +1 -0
- package/dist/automations/store.d.ts +65 -0
- package/dist/automations/store.js +298 -0
- package/dist/automations/store.js.map +1 -0
- package/dist/automations/task-template.d.ts +18 -0
- package/dist/automations/task-template.js +95 -0
- package/dist/automations/task-template.js.map +1 -0
- package/dist/automations/types.d.ts +255 -0
- package/dist/automations/types.js +159 -0
- package/dist/automations/types.js.map +1 -0
- package/dist/config.d.ts +77 -0
- package/dist/config.js +242 -0
- package/dist/config.js.map +1 -0
- package/dist/contract/agent-config.d.ts +153 -0
- package/dist/contract/agent-profiles.d.ts +347 -0
- package/dist/contract/automations.d.ts +935 -0
- package/dist/contract/events.d.ts +104 -0
- package/dist/contract/github.d.ts +556 -0
- package/dist/contract/health.d.ts +102 -0
- package/dist/contract/index.d.ts +16 -0
- package/dist/contract/index.js +1895 -0
- package/dist/contract/projects.d.ts +210 -0
- package/dist/contract/repo.d.ts +234 -0
- package/dist/contract/runs.d.ts +1473 -0
- package/dist/contract/skills.d.ts +232 -0
- package/dist/contract/workflows.d.ts +218 -0
- package/dist/contract/workspace.d.ts +579 -0
- package/dist/core/agent-env.d.ts +31 -0
- package/dist/core/agent-env.js +371 -0
- package/dist/core/agent-env.js.map +1 -0
- package/dist/core/agent-model-policy.d.ts +10 -0
- package/dist/core/agent-model-policy.js +36 -0
- package/dist/core/agent-model-policy.js.map +1 -0
- package/dist/core/agent-profiles.d.ts +62 -0
- package/dist/core/agent-profiles.js +90 -0
- package/dist/core/agent-profiles.js.map +1 -0
- package/dist/core/agent-runner.d.ts +211 -0
- package/dist/core/agent-runner.js +68 -0
- package/dist/core/agent-runner.js.map +1 -0
- package/dist/core/ask.d.ts +105 -0
- package/dist/core/ask.js +187 -0
- package/dist/core/ask.js.map +1 -0
- package/dist/core/backend-detect.d.ts +16 -0
- package/dist/core/backend-detect.js +155 -0
- package/dist/core/backend-detect.js.map +1 -0
- package/dist/core/claude-cli-runner.d.ts +59 -0
- package/dist/core/claude-cli-runner.js +483 -0
- package/dist/core/claude-cli-runner.js.map +1 -0
- package/dist/core/claude-ui-mapper.d.ts +80 -0
- package/dist/core/claude-ui-mapper.js +598 -0
- package/dist/core/claude-ui-mapper.js.map +1 -0
- package/dist/core/codex-app-server-runner.d.ts +41 -0
- package/dist/core/codex-app-server-runner.js +584 -0
- package/dist/core/codex-app-server-runner.js.map +1 -0
- package/dist/core/codex-app-server-transport.d.ts +35 -0
- package/dist/core/codex-app-server-transport.js +150 -0
- package/dist/core/codex-app-server-transport.js.map +1 -0
- package/dist/core/codex-model-catalog.d.ts +10 -0
- package/dist/core/codex-model-catalog.js +110 -0
- package/dist/core/codex-model-catalog.js.map +1 -0
- package/dist/core/codex-ui-mapper.d.ts +112 -0
- package/dist/core/codex-ui-mapper.js +766 -0
- package/dist/core/codex-ui-mapper.js.map +1 -0
- package/dist/core/model-identity.d.ts +125 -0
- package/dist/core/model-identity.js +132 -0
- package/dist/core/model-identity.js.map +1 -0
- package/dist/core/model-presets.d.ts +21 -0
- package/dist/core/model-presets.js +81 -0
- package/dist/core/model-presets.js.map +1 -0
- package/dist/core/ndjson.d.ts +6 -0
- package/dist/core/ndjson.js +23 -0
- package/dist/core/ndjson.js.map +1 -0
- package/dist/core/opencode-model-catalog.d.ts +32 -0
- package/dist/core/opencode-model-catalog.js +153 -0
- package/dist/core/opencode-model-catalog.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +32 -0
- package/dist/core/opencode-server-runner.js +882 -0
- package/dist/core/opencode-server-runner.js.map +1 -0
- package/dist/core/opencode-ui-mapper.d.ts +137 -0
- package/dist/core/opencode-ui-mapper.js +800 -0
- package/dist/core/opencode-ui-mapper.js.map +1 -0
- package/dist/core/pi-model-catalog.d.ts +31 -0
- package/dist/core/pi-model-catalog.js +152 -0
- package/dist/core/pi-model-catalog.js.map +1 -0
- package/dist/core/pi-runner.d.ts +24 -0
- package/dist/core/pi-runner.js +403 -0
- package/dist/core/pi-runner.js.map +1 -0
- package/dist/core/pi-ui-mapper.d.ts +36 -0
- package/dist/core/pi-ui-mapper.js +304 -0
- package/dist/core/pi-ui-mapper.js.map +1 -0
- package/dist/core/process-usage.d.ts +66 -0
- package/dist/core/process-usage.js +193 -0
- package/dist/core/process-usage.js.map +1 -0
- package/dist/core/provider-auth.d.ts +154 -0
- package/dist/core/provider-auth.js +509 -0
- package/dist/core/provider-auth.js.map +1 -0
- package/dist/core/provider-availability.d.ts +3 -0
- package/dist/core/provider-availability.js +14 -0
- package/dist/core/provider-availability.js.map +1 -0
- package/dist/core/runner-factory.d.ts +8 -0
- package/dist/core/runner-factory.js +25 -0
- package/dist/core/runner-factory.js.map +1 -0
- package/dist/core/runner-model-catalog.d.ts +29 -0
- package/dist/core/runner-model-catalog.js +78 -0
- package/dist/core/runner-model-catalog.js.map +1 -0
- package/dist/core/secret-redaction.d.ts +37 -0
- package/dist/core/secret-redaction.js +115 -0
- package/dist/core/secret-redaction.js.map +1 -0
- package/dist/core/shell-env.d.ts +28 -0
- package/dist/core/shell-env.js +56 -0
- package/dist/core/shell-env.js.map +1 -0
- package/dist/core/tool-display.d.ts +31 -0
- package/dist/core/tool-display.js +179 -0
- package/dist/core/tool-display.js.map +1 -0
- package/dist/core/ui-events.d.ts +335 -0
- package/dist/core/ui-events.js +25 -0
- package/dist/core/ui-events.js.map +1 -0
- package/dist/core/usage-limit.d.ts +42 -0
- package/dist/core/usage-limit.js +209 -0
- package/dist/core/usage-limit.js.map +1 -0
- package/dist/core/usage.d.ts +11 -0
- package/dist/core/usage.js +15 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/core/v1-text-coalescer.d.ts +36 -0
- package/dist/core/v1-text-coalescer.js +72 -0
- package/dist/core/v1-text-coalescer.js.map +1 -0
- package/dist/git-diff-base.d.ts +85 -0
- package/dist/git-diff-base.js +180 -0
- package/dist/git-diff-base.js.map +1 -0
- package/dist/git-refs.d.ts +11 -0
- package/dist/git-refs.js +14 -0
- package/dist/git-refs.js.map +1 -0
- package/dist/git-worktree.d.ts +161 -0
- package/dist/git-worktree.js +508 -0
- package/dist/git-worktree.js.map +1 -0
- package/dist/handoff.d.ts +62 -0
- package/dist/handoff.js +154 -0
- package/dist/handoff.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +680 -0
- package/dist/index.js.map +1 -0
- package/dist/install-as-command.d.ts +40 -0
- package/dist/install-as-command.js +52 -0
- package/dist/install-as-command.js.map +1 -0
- package/dist/pack-check.d.ts +17 -0
- package/dist/pack-check.js +27 -0
- package/dist/pack-check.js.map +1 -0
- package/dist/paths.d.ts +140 -0
- package/dist/paths.js +192 -0
- package/dist/paths.js.map +1 -0
- package/dist/planner.d.ts +27 -0
- package/dist/planner.js +268 -0
- package/dist/planner.js.map +1 -0
- package/dist/release/manifests.d.ts +81 -0
- package/dist/release/manifests.js +74 -0
- package/dist/release/manifests.js.map +1 -0
- package/dist/release/snapshot.d.ts +67 -0
- package/dist/release/snapshot.js +97 -0
- package/dist/release/snapshot.js.map +1 -0
- package/dist/release/stable.d.ts +38 -0
- package/dist/release/stable.js +55 -0
- package/dist/release/stable.js.map +1 -0
- package/dist/runs/agent-tmpdir.d.ts +41 -0
- package/dist/runs/agent-tmpdir.js +183 -0
- package/dist/runs/agent-tmpdir.js.map +1 -0
- package/dist/runs/auto-name.d.ts +75 -0
- package/dist/runs/auto-name.js +179 -0
- package/dist/runs/auto-name.js.map +1 -0
- package/dist/runs/event-history.d.ts +50 -0
- package/dist/runs/event-history.js +613 -0
- package/dist/runs/event-history.js.map +1 -0
- package/dist/runs/retention.d.ts +60 -0
- package/dist/runs/retention.js +86 -0
- package/dist/runs/retention.js.map +1 -0
- package/dist/runs/review-gate.d.ts +18 -0
- package/dist/runs/review-gate.js +21 -0
- package/dist/runs/review-gate.js.map +1 -0
- package/dist/runs/run-index.d.ts +19 -0
- package/dist/runs/run-index.js +43 -0
- package/dist/runs/run-index.js.map +1 -0
- package/dist/runs/store.d.ts +446 -0
- package/dist/runs/store.js +1118 -0
- package/dist/runs/store.js.map +1 -0
- package/dist/runs/task-markers.d.ts +28 -0
- package/dist/runs/task-markers.js +74 -0
- package/dist/runs/task-markers.js.map +1 -0
- package/dist/runs/task-refs.d.ts +25 -0
- package/dist/runs/task-refs.js +74 -0
- package/dist/runs/task-refs.js.map +1 -0
- package/dist/runs/ui-event-sink.d.ts +81 -0
- package/dist/runs/ui-event-sink.js +210 -0
- package/dist/runs/ui-event-sink.js.map +1 -0
- package/dist/server/app-type.d.ts +21 -0
- package/dist/server/app-type.js +2 -0
- package/dist/server/app-type.js.map +1 -0
- package/dist/server/capabilities.d.ts +70 -0
- package/dist/server/capabilities.js +152 -0
- package/dist/server/capabilities.js.map +1 -0
- package/dist/server/checkout.d.ts +128 -0
- package/dist/server/checkout.js +296 -0
- package/dist/server/checkout.js.map +1 -0
- package/dist/server/forge/github.d.ts +513 -0
- package/dist/server/forge/github.js +2364 -0
- package/dist/server/forge/github.js.map +1 -0
- package/dist/server/forge/index.d.ts +40 -0
- package/dist/server/forge/index.js +72 -0
- package/dist/server/forge/index.js.map +1 -0
- package/dist/server/forge/types.d.ts +242 -0
- package/dist/server/forge/types.js +2 -0
- package/dist/server/forge/types.js.map +1 -0
- package/dist/server/fs-browse.d.ts +105 -0
- package/dist/server/fs-browse.js +182 -0
- package/dist/server/fs-browse.js.map +1 -0
- package/dist/server/git-changes.d.ts +213 -0
- package/dist/server/git-changes.js +555 -0
- package/dist/server/git-changes.js.map +1 -0
- package/dist/server/git.d.ts +28 -0
- package/dist/server/git.js +112 -0
- package/dist/server/git.js.map +1 -0
- package/dist/server/github.d.ts +9 -0
- package/dist/server/github.js +8 -0
- package/dist/server/github.js.map +1 -0
- package/dist/server/launch-key.d.ts +7 -0
- package/dist/server/launch-key.js +33 -0
- package/dist/server/launch-key.js.map +1 -0
- package/dist/server/open-in-app.d.ts +66 -0
- package/dist/server/open-in-app.js +211 -0
- package/dist/server/open-in-app.js.map +1 -0
- package/dist/server/open-in-terminal.d.ts +70 -0
- package/dist/server/open-in-terminal.js +179 -0
- package/dist/server/open-in-terminal.js.map +1 -0
- package/dist/server/pr.d.ts +7 -0
- package/dist/server/pr.js +7 -0
- package/dist/server/pr.js.map +1 -0
- package/dist/server/project-context.d.ts +120 -0
- package/dist/server/project-context.js +182 -0
- package/dist/server/project-context.js.map +1 -0
- package/dist/server/provider-action-gate.d.ts +8 -0
- package/dist/server/provider-action-gate.js +56 -0
- package/dist/server/provider-action-gate.js.map +1 -0
- package/dist/server/provider-auth-runtime.d.ts +21 -0
- package/dist/server/provider-auth-runtime.js +66 -0
- package/dist/server/provider-auth-runtime.js.map +1 -0
- package/dist/server/server.d.ts +13138 -0
- package/dist/server/server.js +5263 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/static-ui.d.ts +49 -0
- package/dist/server/static-ui.js +92 -0
- package/dist/server/static-ui.js.map +1 -0
- package/dist/server/validators.d.ts +97 -0
- package/dist/server/validators.js +86 -0
- package/dist/server/validators.js.map +1 -0
- package/dist/server/ws.d.ts +85 -0
- package/dist/server/ws.js +218 -0
- package/dist/server/ws.js.map +1 -0
- package/dist/server/wsl.d.ts +25 -0
- package/dist/server/wsl.js +92 -0
- package/dist/server/wsl.js.map +1 -0
- package/dist/server-install/engine.d.ts +50 -0
- package/dist/server-install/engine.js +324 -0
- package/dist/server-install/engine.js.map +1 -0
- package/dist/server-install/platforms/macosx-ngrok.d.ts +6 -0
- package/dist/server-install/platforms/macosx-ngrok.js +345 -0
- package/dist/server-install/platforms/macosx-ngrok.js.map +1 -0
- package/dist/server-install/platforms/ubuntu-vps.d.ts +78 -0
- package/dist/server-install/platforms/ubuntu-vps.js +1016 -0
- package/dist/server-install/platforms/ubuntu-vps.js.map +1 -0
- package/dist/server-install/state.d.ts +52 -0
- package/dist/server-install/state.js +182 -0
- package/dist/server-install/state.js.map +1 -0
- package/dist/server-install/steps.d.ts +109 -0
- package/dist/server-install/steps.js +334 -0
- package/dist/server-install/steps.js.map +1 -0
- package/dist/server-install/strategies.d.ts +4 -0
- package/dist/server-install/strategies.js +20 -0
- package/dist/server-install/strategies.js.map +1 -0
- package/dist/server-install/types.d.ts +287 -0
- package/dist/server-install/types.js +121 -0
- package/dist/server-install/types.js.map +1 -0
- package/dist/server-install/ui.d.ts +42 -0
- package/dist/server-install/ui.js +130 -0
- package/dist/server-install/ui.js.map +1 -0
- package/dist/skills-banner.d.ts +22 -0
- package/dist/skills-banner.js +41 -0
- package/dist/skills-banner.js.map +1 -0
- package/dist/skills-remote.d.ts +84 -0
- package/dist/skills-remote.js +468 -0
- package/dist/skills-remote.js.map +1 -0
- package/dist/skills-update.d.ts +88 -0
- package/dist/skills-update.js +392 -0
- package/dist/skills-update.js.map +1 -0
- package/dist/skills.d.ts +82 -0
- package/dist/skills.js +251 -0
- package/dist/skills.js.map +1 -0
- package/dist/todos.d.ts +48 -0
- package/dist/todos.js +201 -0
- package/dist/todos.js.map +1 -0
- package/dist/ui-state.d.ts +26 -0
- package/dist/ui-state.js +38 -0
- package/dist/ui-state.js.map +1 -0
- package/dist/update-check.d.ts +12 -0
- package/dist/update-check.js +41 -0
- package/dist/update-check.js.map +1 -0
- package/dist/workflows/load.d.ts +15 -0
- package/dist/workflows/load.js +56 -0
- package/dist/workflows/load.js.map +1 -0
- package/dist/workflows/run.d.ts +758 -0
- package/dist/workflows/run.js +3389 -0
- package/dist/workflows/run.js.map +1 -0
- package/dist/workflows/types.d.ts +153 -0
- package/dist/workflows/types.js +212 -0
- package/dist/workflows/types.js.map +1 -0
- package/dist/workspace/agent-accounts.d.ts +153 -0
- package/dist/workspace/agent-accounts.js +304 -0
- package/dist/workspace/agent-accounts.js.map +1 -0
- package/dist/workspace/agent-profiles.d.ts +78 -0
- package/dist/workspace/agent-profiles.js +115 -0
- package/dist/workspace/agent-profiles.js.map +1 -0
- package/dist/workspace/config.d.ts +169 -0
- package/dist/workspace/config.js +379 -0
- package/dist/workspace/config.js.map +1 -0
- package/dist/workspace/migrations.d.ts +45 -0
- package/dist/workspace/migrations.js +147 -0
- package/dist/workspace/migrations.js.map +1 -0
- package/dist/workspace/projects-cli.d.ts +25 -0
- package/dist/workspace/projects-cli.js +172 -0
- package/dist/workspace/projects-cli.js.map +1 -0
- package/dist/workspace/projects.d.ts +109 -0
- package/dist/workspace/projects.js +250 -0
- package/dist/workspace/projects.js.map +1 -0
- package/dist/workspace/semaphore.d.ts +179 -0
- package/dist/workspace/semaphore.js +195 -0
- package/dist/workspace/semaphore.js.map +1 -0
- package/dist/workspace/ui-state.d.ts +34 -0
- package/dist/workspace/ui-state.js +53 -0
- package/dist/workspace/ui-state.js.map +1 -0
- package/package.json +70 -0
- package/scripts/check-pack.mjs +46 -0
- package/scripts/inline-contract.mjs +112 -0
- package/scripts/install-as-command.mjs +120 -0
- package/scripts/mock-claude.mjs +520 -0
- package/scripts/mock-pi-rpc.mjs +83 -0
- package/scripts/sync-readme.mjs +20 -0
- package/scripts/test-process-usage.mjs +47 -0
- package/web/dist/assets/alert-dialog-B4Ofsgv7.js +1 -0
- package/web/dist/assets/arrow-down-Dy8dup7j.js +1 -0
- package/web/dist/assets/arrow-left-BJaucbhn.js +1 -0
- package/web/dist/assets/bundle-mjs-BT31bpU6.js +1 -0
- package/web/dist/assets/centered-state-duPW0phQ.js +43 -0
- package/web/dist/assets/chevron-right-Be4A5TCS.js +1 -0
- package/web/dist/assets/chunk-BO2N2NFS-DE6qKn3r.js +131 -0
- package/web/dist/assets/collapsible-DdcNBdSk.js +1 -0
- package/web/dist/assets/commit-list-CgvN9vec.js +1 -0
- package/web/dist/assets/compare-variants-CQA2nd65.js +1 -0
- package/web/dist/assets/core-BCsw8oQw.js +12 -0
- package/web/dist/assets/css-BsVw1vtW.js +1 -0
- package/web/dist/assets/diff-B6Agp5Hl.js +3 -0
- package/web/dist/assets/diff-stat-BoXuGkgq.js +1 -0
- package/web/dist/assets/diff-view-CD8XI-fj.js +6 -0
- package/web/dist/assets/diff-woXpYk--.js +1 -0
- package/web/dist/assets/dropdown-menu-B3SYw-k5.js +1 -0
- package/web/dist/assets/editable-title-B-hjp5wl.js +1 -0
- package/web/dist/assets/ellipsis-vertical-BYwzw0xl.js +1 -0
- package/web/dist/assets/engine-javascript-FQvzRocB.js +141 -0
- package/web/dist/assets/file-BRj5eISx.js +1 -0
- package/web/dist/assets/folder-z4oT5981.js +1 -0
- package/web/dist/assets/git-toolbar-owSEkYld.js +1 -0
- package/web/dist/assets/github-Bz2VgRX3.js +1 -0
- package/web/dist/assets/go-rLFTqkRN.js +1 -0
- package/web/dist/assets/highlighted-body-OFNGDK62-BL-MfFgl.js +1 -0
- package/web/dist/assets/highlighter-CdHqIwFr.js +3 -0
- package/web/dist/assets/html-BY76lXLH.js +1 -0
- package/web/dist/assets/image-preview-h3WFluuy.js +1 -0
- package/web/dist/assets/index-BbcQNT92.css +2 -0
- package/web/dist/assets/index-EtQQQ6Kj.js +7 -0
- package/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/web/dist/assets/javascript-BgS3c2Ky.js +1 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/web/dist/assets/json-qhed-kSA.js +1 -0
- package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
- package/web/dist/assets/jsx-Bz0zcwM4.js +1 -0
- package/web/dist/assets/lib-BQXq3kEf.js +1 -0
- package/web/dist/assets/lib-BxQXEXDF.js +1 -0
- package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
- package/web/dist/assets/markdown-DqBKYPiB.js +2 -0
- package/web/dist/assets/mermaid-GHXKKRXX-C4jxqUUb.js +1 -0
- package/web/dist/assets/new-task-form-BdJKRBJX.js +1 -0
- package/web/dist/assets/pill-BQfmfpYp.js +1 -0
- package/web/dist/assets/project-router-DxgFZY1l.js +1 -0
- package/web/dist/assets/prompt-templates-B6m16gq-.js +15 -0
- package/web/dist/assets/python-gzcpVVnB.js +1 -0
- package/web/dist/assets/react-runtime-CCIEwYL0.js +9 -0
- package/web/dist/assets/refresh-cw-mhSpxf2a.js +1 -0
- package/web/dist/assets/repo-git-CUNRxR3_.js +1 -0
- package/web/dist/assets/rolldown-runtime-QTnfLwEv.js +1 -0
- package/web/dist/assets/run-diff-DsmmDVfx.js +3 -0
- package/web/dist/assets/run-header-BkhrseLJ.js +1 -0
- package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
- package/web/dist/assets/search-x-BqDMB2g8.js +1 -0
- package/web/dist/assets/shellscript-CLZ0U2zV.js +1 -0
- package/web/dist/assets/skill-empty-hint-BWEvTggc.js +1 -0
- package/web/dist/assets/skills-9oR_oHse.js +1 -0
- package/web/dist/assets/skills-DwIxnnWO.js +1 -0
- package/web/dist/assets/sparkles-DIoTQpyx.js +1 -0
- package/web/dist/assets/sql-BsFa4tDR.js +1 -0
- package/web/dist/assets/square-terminal-Cc7CuQxc.js +1 -0
- package/web/dist/assets/tab-link-D1fQgpmO.js +1 -0
- package/web/dist/assets/task-changes-BKeLlDpK.js +1 -0
- package/web/dist/assets/task-commits-gcEFZIa5.js +1 -0
- package/web/dist/assets/task-files-BGAqoXTW.js +2 -0
- package/web/dist/assets/task-thread-DT8aUYTS.js +9 -0
- package/web/dist/assets/textarea-BOVbfy5C.js +1 -0
- package/web/dist/assets/thread-loading-CypW73KD.js +1 -0
- package/web/dist/assets/toml-CcmNWLt0.js +1 -0
- package/web/dist/assets/trash-2-CJq6LHWn.js +1 -0
- package/web/dist/assets/triangle-alert-CjDFvU27.js +1 -0
- package/web/dist/assets/tsx-udAQXfEw.js +1 -0
- package/web/dist/assets/typescript-bsJCZSQ-.js +1 -0
- package/web/dist/assets/upload-CPDZlJeK.js +1 -0
- package/web/dist/assets/use-desktop-5JsLmi0a.js +1 -0
- package/web/dist/assets/use-submit-shortcut-CHVFevty.js +1 -0
- package/web/dist/assets/utils-DiO6zyJd.js +64 -0
- package/web/dist/assets/workflows-rQDFzWRt.js +11 -0
- package/web/dist/assets/yaml-rwi0_p6S.js +1 -0
- package/web/dist/assets/zoomable-image-COWqb11v.js +1 -0
- package/web/dist/index.html +80 -0
- package/web/dist/open-mercato.svg +11 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** Pick the response for `/`, given whether the build exists.
|
|
2
|
+
*
|
|
3
|
+
* The published tarball always ships `web/dist`, so `build-hint` is a dev-only
|
|
4
|
+
* state (a fresh checkout that never ran `npm run build:web`) — the spec's
|
|
5
|
+
* degradation matrix answers it with a plain "run the build" page.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveIndexHtml(opts) {
|
|
8
|
+
return opts.distExists ? 'dist' : 'build-hint';
|
|
9
|
+
}
|
|
10
|
+
/** Paths owned by routes registered before the catch-all: the built app's
|
|
11
|
+
* hashed bundles and the favicon. */
|
|
12
|
+
function isStaticAsset(path) {
|
|
13
|
+
return path.startsWith('/assets/') || path === '/open-mercato.svg';
|
|
14
|
+
}
|
|
15
|
+
/** Decide what any GET gets, so every route in the spec's map (`/tasks/:id/changes`,
|
|
16
|
+
* `/settings/skills`, …) cold-loads and survives a refresh — that is what makes a
|
|
17
|
+
* cockpit URL pasteable.
|
|
18
|
+
*
|
|
19
|
+
* Unknown paths deliberately resolve to the shell, not a 404: react-router owns
|
|
20
|
+
* the 404 (it is the only side that knows the route map). Everything the server
|
|
21
|
+
* itself owns — `/api/*` and the static files above — passes through untouched.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveGetRequest(opts) {
|
|
24
|
+
const { path, distExists } = opts;
|
|
25
|
+
if (path === '/api' || path.startsWith('/api/') || isStaticAsset(path)) {
|
|
26
|
+
return 'passthrough';
|
|
27
|
+
}
|
|
28
|
+
return resolveIndexHtml({ distExists });
|
|
29
|
+
}
|
|
30
|
+
/** The dev fallback page served for every shell route when `web/dist` is
|
|
31
|
+
* missing (spec degradation matrix: "run `npm run dev:web` or
|
|
32
|
+
* `npm run build:web`"). Built into the server so it needs no files on disk. */
|
|
33
|
+
export const BUILD_HINT_HTML = `<!doctype html>
|
|
34
|
+
<html lang="en">
|
|
35
|
+
<head>
|
|
36
|
+
<meta charset="utf-8">
|
|
37
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
38
|
+
<title>cezar — build the cockpit</title>
|
|
39
|
+
<style>
|
|
40
|
+
body { margin: 0; display: grid; place-items: center; min-height: 100dvh;
|
|
41
|
+
font: 15px/1.6 system-ui, sans-serif; background: #101014; color: #e8e8ea; }
|
|
42
|
+
main { max-width: 34rem; padding: 2rem; }
|
|
43
|
+
code { font-family: ui-monospace, monospace; background: #1c1c22; border-radius: 6px; padding: 2px 6px; }
|
|
44
|
+
p { color: #a0a0aa; }
|
|
45
|
+
</style>
|
|
46
|
+
</head>
|
|
47
|
+
<body>
|
|
48
|
+
<main>
|
|
49
|
+
<h1>The cockpit isn’t built yet</h1>
|
|
50
|
+
<p>This checkout has no <code>web/dist</code>. Run <code>npm run build:web</code>
|
|
51
|
+
and reload — or use <code>npm run dev:web</code> for the live dev server.</p>
|
|
52
|
+
</main>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
55
|
+
`;
|
|
56
|
+
const ASSET_TYPES = {
|
|
57
|
+
js: 'text/javascript; charset=utf-8',
|
|
58
|
+
css: 'text/css; charset=utf-8',
|
|
59
|
+
svg: 'image/svg+xml',
|
|
60
|
+
woff2: 'font/woff2',
|
|
61
|
+
woff: 'font/woff',
|
|
62
|
+
png: 'image/png',
|
|
63
|
+
jpg: 'image/jpeg',
|
|
64
|
+
jpeg: 'image/jpeg',
|
|
65
|
+
gif: 'image/gif',
|
|
66
|
+
webp: 'image/webp',
|
|
67
|
+
ico: 'image/x-icon',
|
|
68
|
+
json: 'application/json; charset=utf-8',
|
|
69
|
+
map: 'application/json; charset=utf-8',
|
|
70
|
+
};
|
|
71
|
+
/** True only for a plain filename the `/assets/:file` route may serve.
|
|
72
|
+
*
|
|
73
|
+
* `basename` alone is not a guard here: `basename('..')` is `'..'`, which
|
|
74
|
+
* joins back to the assets dir itself and turns the route's `readFileSync`
|
|
75
|
+
* into an EISDIR crash (a 500) instead of a 404. Dot-segments, separators,
|
|
76
|
+
* and NUL all mean "not a file we ship" — the caller answers 404. */
|
|
77
|
+
export function isSafeAssetFilename(file) {
|
|
78
|
+
return (file.length > 0 &&
|
|
79
|
+
file !== '.' &&
|
|
80
|
+
file !== '..' &&
|
|
81
|
+
!file.includes('/') &&
|
|
82
|
+
!file.includes('\\') &&
|
|
83
|
+
!file.includes('\0'));
|
|
84
|
+
}
|
|
85
|
+
/** Content type for a hashed file under `web/dist/assets/`. */
|
|
86
|
+
export function assetContentType(file) {
|
|
87
|
+
return ASSET_TYPES[file.split('.').pop()?.toLowerCase() ?? ''] ?? 'application/octet-stream';
|
|
88
|
+
}
|
|
89
|
+
/** Vite fingerprints every filename under `assets/`, so the bytes behind a URL
|
|
90
|
+
* can never change — cache them for a year. */
|
|
91
|
+
export const ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable';
|
|
92
|
+
//# sourceMappingURL=static-ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-ui.js","sourceRoot":"","sources":["../../src/server/static-ui.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAA6B;IAC5D,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC;AACjD,CAAC;AAOD;sCACsC;AACtC,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,IAAI,KAAK,mBAAmB,CAAC;AACrE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAA2C;IAC3E,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAClC,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QACvE,OAAO,aAAa,CAAC;IACvB,CAAC;IACD,OAAO,gBAAgB,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED;;iFAEiF;AACjF,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;CAsB9B,CAAC;AAEF,MAAM,WAAW,GAA2B;IAC1C,EAAE,EAAE,gCAAgC;IACpC,GAAG,EAAE,yBAAyB;IAC9B,GAAG,EAAE,eAAe;IACpB,KAAK,EAAE,YAAY;IACnB,IAAI,EAAE,WAAW;IACjB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,YAAY;IACjB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,cAAc;IACnB,IAAI,EAAE,iCAAiC;IACvC,GAAG,EAAE,iCAAiC;CACvC,CAAC;AAEF;;;;;sEAKsE;AACtE,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,CACL,IAAI,CAAC,MAAM,GAAG,CAAC;QACf,IAAI,KAAK,GAAG;QACZ,IAAI,KAAK,IAAI;QACb,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QACnB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CACrB,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,IAAI,0BAA0B,CAAC;AAC/F,CAAC;AAED;gDACgD;AAChD,MAAM,CAAC,MAAM,mBAAmB,GAAG,qCAAqC,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Env, MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* Request validation as ROUTE MIDDLEWARE — the `json` / `param` / `query` trio every mutating
|
|
5
|
+
* route reaches for.
|
|
6
|
+
*
|
|
7
|
+
* The point is not that handlers stop restating six lines of `safeParse`, though they do. It is
|
|
8
|
+
* that Hono only records a validated shape in the ROUTE TYPE when validation happens as
|
|
9
|
+
* middleware. Parsing inside a handler is invisible to it, which is why `POST /runs` used to
|
|
10
|
+
* accept `{ totalNonsense: 12345 }` from `hc` without a murmur. Everything typed-client-side
|
|
11
|
+
* flows from validating here instead of there.
|
|
12
|
+
*
|
|
13
|
+
* ## Why not `@hono/zod-validator`
|
|
14
|
+
*
|
|
15
|
+
* It is a thin wrapper over the same `hono/validator` used below, so it inherits Hono's two
|
|
16
|
+
* behaviours at the JSON boundary — and both are wrong for this server:
|
|
17
|
+
*
|
|
18
|
+
* - a malformed body answers Hono's PLAIN-TEXT `400 Malformed JSON in request body`, and the
|
|
19
|
+
* error hook never runs, so the `{error}` shape BACKWARD_COMPATIBILITY.md §2 protects (and
|
|
20
|
+
* the cockpit renders verbatim in a toast) is bypassed entirely;
|
|
21
|
+
* - a body sent WITHOUT a JSON content-type is silently discarded and the handler runs against
|
|
22
|
+
* `{}` — a 200 that applied an empty update, which is worse than a rejection.
|
|
23
|
+
*
|
|
24
|
+
* Both were verified against `@hono/zod-validator@0.9.0`, not inferred. `jsonZodValidator` settles
|
|
25
|
+
* those cases itself and only delegates to Hono on the well-formed path, so the wire matches what
|
|
26
|
+
* these handlers did when they parsed inline (`await c.req.json().catch(() => absent)`).
|
|
27
|
+
*/
|
|
28
|
+
type ErrorOptions = {
|
|
29
|
+
/** Fixed 400 text, for routes that answer one instead of the zod issues. */
|
|
30
|
+
message?: string;
|
|
31
|
+
};
|
|
32
|
+
type JsonOptions = ErrorOptions & {
|
|
33
|
+
/**
|
|
34
|
+
* What a request with NO body parses as — the old `.catch(() => …)` fallback. Hono hands a
|
|
35
|
+
* bodyless request `{}`, which is not the same as a request that really sent `{}`: routes that
|
|
36
|
+
* tolerate no body at all pass `{}` here, and the rest keep rejecting it exactly as they did.
|
|
37
|
+
*/
|
|
38
|
+
absent?: unknown;
|
|
39
|
+
/**
|
|
40
|
+
* What a body that is PRESENT but not parseable as JSON becomes. Defaults to `absent`, which is
|
|
41
|
+
* what every route wants when both cases are rejected anyway.
|
|
42
|
+
*
|
|
43
|
+
* `POST /todos/:id/start` is the one route that must tell them apart: no body at all is the
|
|
44
|
+
* pre-#401 bodyless POST and has to succeed (`absent: undefined`, which its optional schema
|
|
45
|
+
* accepts), while a truncated payload must 400 rather than pass as "no body" and silently 201
|
|
46
|
+
* (`malformed: null`, which it does not). That distinction lived in the handler's own
|
|
47
|
+
* `JSON.parse` before the route moved its body onto a validator; it is expressed here so the
|
|
48
|
+
* behaviour moved with it rather than being lost in the move.
|
|
49
|
+
*/
|
|
50
|
+
malformed?: unknown;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The request/response split, spelled out rather than inferred.
|
|
54
|
+
*
|
|
55
|
+
* Hono derives a validator's request type from its validation function's first parameter, but
|
|
56
|
+
* declares that parameter as a CONDITIONAL type — which is not an inference site, so annotating
|
|
57
|
+
* it achieves nothing and the request silently falls back to the schema's OUTPUT. That makes any
|
|
58
|
+
* `.optional().transform(…)` or `.default(…)` field REQUIRED on the wire: `POST /runs` demanded a
|
|
59
|
+
* `systemPrompt` no caller should send, and `POST …/merge` demanded `overrideRules`. Naming both
|
|
60
|
+
* sides here is the same thing `@hono/zod-validator` does, and for the same reason.
|
|
61
|
+
*/
|
|
62
|
+
type JsonValidator<S extends z.ZodType, E extends Env, P extends string> = MiddlewareHandler<E, P, {
|
|
63
|
+
in: {
|
|
64
|
+
json: z.input<S>;
|
|
65
|
+
};
|
|
66
|
+
out: {
|
|
67
|
+
json: z.output<S>;
|
|
68
|
+
};
|
|
69
|
+
}>;
|
|
70
|
+
export declare function jsonZodValidator<S extends z.ZodType, E extends Env = Env, P extends string = string>(schema: S | (() => S), options?: JsonOptions): JsonValidator<S, E, P>;
|
|
71
|
+
/**
|
|
72
|
+
* Path params. No guard needed: these come off the matched URL, so there is no body to parse, no
|
|
73
|
+
* content-type to gate on and no malformed-input path — Hono's own behaviour is already right.
|
|
74
|
+
* The schema receives the whole param object (`{ provider: 'codex' }`), not a single value.
|
|
75
|
+
*/
|
|
76
|
+
export declare function paramZodValidator<S extends z.ZodType>(schema: S, { message }?: ErrorOptions): MiddlewareHandler<any, string, {
|
|
77
|
+
in: {
|
|
78
|
+
param: import("hono/validator").InferInput<z.TypeOf<S> extends infer T ? T extends z.TypeOf<S> ? T extends Promise<infer PR> ? PR extends Response | import("hono").TypedResponse<any, any, any> ? never : PR : T extends Response | import("hono").TypedResponse<any, any, any> ? never : T : never : never, "param", import("hono/types").FormValue>;
|
|
79
|
+
};
|
|
80
|
+
out: {
|
|
81
|
+
param: z.TypeOf<S> extends infer T ? T extends z.TypeOf<S> ? T extends Promise<infer PR> ? PR extends Response | import("hono").TypedResponse<any, any, any> ? never : PR : T extends Response | import("hono").TypedResponse<any, any, any> ? never : T : never : never;
|
|
82
|
+
};
|
|
83
|
+
}, import("hono").TypedResponse<{
|
|
84
|
+
error: string;
|
|
85
|
+
}, 400, "json"> | (z.TypeOf<S> extends infer T_1 ? T_1 extends z.TypeOf<S> ? T_1 extends Promise<infer PR_1> ? PR_1 extends import("hono").TypedResponse<infer T_2, infer S_1 extends import("hono/utils/http-status").StatusCode, infer F extends string> ? import("hono").TypedResponse<T_2, S_1, F> : PR_1 extends Response ? PR_1 : PR_1 extends undefined ? never : never : T_1 extends import("hono").TypedResponse<infer T_3, infer S_2 extends import("hono/utils/http-status").StatusCode, infer F_1 extends string> ? import("hono").TypedResponse<T_3, S_2, F_1> : T_1 extends Response ? T_1 : T_1 extends undefined ? never : never : never : never)>;
|
|
86
|
+
/** Query string, on the same terms as {@link paramZodValidator}. */
|
|
87
|
+
export declare function queryZodValidator<S extends z.ZodType>(schema: S, { message }?: ErrorOptions): MiddlewareHandler<any, string, {
|
|
88
|
+
in: {
|
|
89
|
+
query: import("hono/validator").InferInput<z.TypeOf<S> extends infer T ? T extends z.TypeOf<S> ? T extends Promise<infer PR> ? PR extends Response | import("hono").TypedResponse<any, any, any> ? never : PR : T extends Response | import("hono").TypedResponse<any, any, any> ? never : T : never : never, "query", import("hono/types").FormValue>;
|
|
90
|
+
};
|
|
91
|
+
out: {
|
|
92
|
+
query: z.TypeOf<S> extends infer T ? T extends z.TypeOf<S> ? T extends Promise<infer PR> ? PR extends Response | import("hono").TypedResponse<any, any, any> ? never : PR : T extends Response | import("hono").TypedResponse<any, any, any> ? never : T : never : never;
|
|
93
|
+
};
|
|
94
|
+
}, import("hono").TypedResponse<{
|
|
95
|
+
error: string;
|
|
96
|
+
}, 400, "json"> | (z.TypeOf<S> extends infer T_1 ? T_1 extends z.TypeOf<S> ? T_1 extends Promise<infer PR_1> ? PR_1 extends import("hono").TypedResponse<infer T_2, infer S_1 extends import("hono/utils/http-status").StatusCode, infer F extends string> ? import("hono").TypedResponse<T_2, S_1, F> : PR_1 extends Response ? PR_1 : PR_1 extends undefined ? never : never : T_1 extends import("hono").TypedResponse<infer T_3, infer S_2 extends import("hono/utils/http-status").StatusCode, infer F_1 extends string> ? import("hono").TypedResponse<T_3, S_2, F_1> : T_1 extends Response ? T_1 : T_1 extends undefined ? never : never : never : never)>;
|
|
97
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { validator } from 'hono/validator';
|
|
2
|
+
/**
|
|
3
|
+
* `{ error }`, the one 400 shape this API answers (BACKWARD_COMPATIBILITY.md §2).
|
|
4
|
+
*
|
|
5
|
+
* Each issue is prefixed with the field path, because zod's own message never names the field:
|
|
6
|
+
* `"Invalid input: expected array, received undefined"` on its own does not tell the cockpit user
|
|
7
|
+
* WHICH field to fix. That is the same information `z.prettifyError` adds, minus its multi-line
|
|
8
|
+
* `✖ …\n → at task` layout — this string is rendered verbatim in a toast, so it stays one line.
|
|
9
|
+
*/
|
|
10
|
+
function reject(c, error, override) {
|
|
11
|
+
const detail = error.issues
|
|
12
|
+
.map((issue) => (issue.path.length > 0 ? `${issue.path.join('.')}: ${issue.message}` : issue.message))
|
|
13
|
+
.join('; ');
|
|
14
|
+
return c.json({ error: override ?? detail }, 400);
|
|
15
|
+
}
|
|
16
|
+
export function jsonZodValidator(
|
|
17
|
+
// A thunk is accepted because several schemas are declared next to the route family they
|
|
18
|
+
// belong to, which is BELOW the route using them; resolving per request keeps those
|
|
19
|
+
// declarations where they read best. Pass the schema directly whenever it is already in scope —
|
|
20
|
+
// a GENERIC thunk is worse than either, since an unresolved schema type makes Hono drop the
|
|
21
|
+
// route from the app type silently (see typed-bodies.test.ts).
|
|
22
|
+
schema, options = {}) {
|
|
23
|
+
// Key presence, not a destructuring default: `undefined` IS a meaningful value for both of
|
|
24
|
+
// these (it is what `POST /todos/:id/start` wants a bodyless request to parse as), and a
|
|
25
|
+
// `= null` default would silently overwrite exactly that case.
|
|
26
|
+
const absent = 'absent' in options ? options.absent : null;
|
|
27
|
+
const malformed = 'malformed' in options ? options.malformed : absent;
|
|
28
|
+
const { message } = options;
|
|
29
|
+
const check = (input, c) => {
|
|
30
|
+
const resolved = typeof schema === 'function' ? schema() : schema;
|
|
31
|
+
const parsed = resolved.safeParse(input);
|
|
32
|
+
return parsed.success
|
|
33
|
+
? { ok: true, data: parsed.data }
|
|
34
|
+
: { ok: false, response: reject(c, parsed.error, message) };
|
|
35
|
+
};
|
|
36
|
+
const validate = validator('json', (value, c) => {
|
|
37
|
+
const result = check(value, c);
|
|
38
|
+
return result.ok ? result.data : result.response;
|
|
39
|
+
});
|
|
40
|
+
// Anything Hono would treat differently than the old inline parse is settled here and published
|
|
41
|
+
// straight to `c.req.valid('json')`; only well-formed JSON under a JSON content-type — what
|
|
42
|
+
// every real client sends — takes Hono's own path. See the header note.
|
|
43
|
+
const guard = async (c, next) => {
|
|
44
|
+
const text = await c.req.text().catch(() => '');
|
|
45
|
+
let body = absent;
|
|
46
|
+
let parseable = false;
|
|
47
|
+
if (text.trim() !== '') {
|
|
48
|
+
try {
|
|
49
|
+
body = JSON.parse(text);
|
|
50
|
+
parseable = true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
body = malformed;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const contentType = c.req.header('content-type');
|
|
57
|
+
if (parseable && contentType !== undefined && /^application\/([a-z\-.]+\+)?json/.test(contentType)) {
|
|
58
|
+
return validate(c, next);
|
|
59
|
+
}
|
|
60
|
+
const result = check(body, c);
|
|
61
|
+
if (!result.ok)
|
|
62
|
+
return result.response;
|
|
63
|
+
c.req.addValidatedData('json', result.data);
|
|
64
|
+
return next();
|
|
65
|
+
};
|
|
66
|
+
return guard;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Path params. No guard needed: these come off the matched URL, so there is no body to parse, no
|
|
70
|
+
* content-type to gate on and no malformed-input path — Hono's own behaviour is already right.
|
|
71
|
+
* The schema receives the whole param object (`{ provider: 'codex' }`), not a single value.
|
|
72
|
+
*/
|
|
73
|
+
export function paramZodValidator(schema, { message } = {}) {
|
|
74
|
+
return validator('param', (value, c) => {
|
|
75
|
+
const parsed = schema.safeParse(value);
|
|
76
|
+
return parsed.success ? parsed.data : reject(c, parsed.error, message);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Query string, on the same terms as {@link paramZodValidator}. */
|
|
80
|
+
export function queryZodValidator(schema, { message } = {}) {
|
|
81
|
+
return validator('query', (value, c) => {
|
|
82
|
+
const parsed = schema.safeParse(value);
|
|
83
|
+
return parsed.success ? parsed.data : reject(c, parsed.error, message);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=validators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.js","sourceRoot":"","sources":["../../src/server/validators.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { Duplex } from 'node:stream';
|
|
3
|
+
/**
|
|
4
|
+
* The cockpit's WebSocket subscription bus (`GET /api/v1/ws`, upgrade-only).
|
|
5
|
+
* Design + the subscribe/unsubscribe discipline: spec
|
|
6
|
+
* `.ai/specs/2026-07-23-websocket-subscriptions.md`.
|
|
7
|
+
*
|
|
8
|
+
* One socket per cockpit, many topics per socket: the client sends
|
|
9
|
+
* `{type:'subscribe'|'unsubscribe', topic}` frames and receives
|
|
10
|
+
* `{type:'event', topic, data}` frames for the topics it holds. The point of
|
|
11
|
+
* the topic layer is that server-side work is DEMAND-DRIVEN: a topic's
|
|
12
|
+
* publisher starts when its subscriber count goes 0→1 and is stopped at 1→0,
|
|
13
|
+
* so a workspace with no cockpit open (or a cockpit that navigated away from
|
|
14
|
+
* everything using a topic) costs zero timers, zero shell-outs, zero anything.
|
|
15
|
+
* That is what replaced the per-tab `GET /api/health` poll (#369): N tabs used
|
|
16
|
+
* to mean N polls every 5 s forever; now they mean one publisher while at
|
|
17
|
+
* least one tab subscribes, and none afterwards.
|
|
18
|
+
*
|
|
19
|
+
* Security: the hub itself never decides who may connect — `attach` takes the
|
|
20
|
+
* upgrade guard as a function, and server.ts supplies the WebSocket twin of
|
|
21
|
+
* the `/api/*` request-origin guard (#426). See `verifyWsUpgrade` there for
|
|
22
|
+
* what it admits, and the caveat about which topics may live on this hub.
|
|
23
|
+
*/
|
|
24
|
+
/** The one upgrade path. Workspace-level like `/api/workspace/events` — never
|
|
25
|
+
* mirrored under `/api/p/:projectId` (topics carry workspace data). */
|
|
26
|
+
export declare const WS_PATH = "/api/v1/ws";
|
|
27
|
+
export interface TopicPublisher {
|
|
28
|
+
/** Fresh payload for a subscriber that just arrived — sent immediately so a
|
|
29
|
+
* (re)connecting cockpit never waits out a publisher interval to render. */
|
|
30
|
+
snapshot(): Promise<unknown>;
|
|
31
|
+
/** Subscriber count went 0→1: start producing. Call `publish` whenever the
|
|
32
|
+
* topic has news (every call is broadcast verbatim to all subscribers).
|
|
33
|
+
* Returns the stop function, called at 1→0. */
|
|
34
|
+
start(publish: (data: unknown) => void): () => void;
|
|
35
|
+
}
|
|
36
|
+
export interface TopicOptions {
|
|
37
|
+
/** Whether an ADMITTED-BUT-UNTRUSTED connection may subscribe (see
|
|
38
|
+
* `WsUpgradeVerdict`). Defaults to `false` — the safe default that makes the
|
|
39
|
+
* topic-safety invariant mechanical: a topic carrying run/repo/PR content is
|
|
40
|
+
* legible only to the cockpit itself unless a publisher explicitly opts in.
|
|
41
|
+
* Set `true` only for data already safe for any local page (e.g. `health`,
|
|
42
|
+
* the CORS-open discovery payload). */
|
|
43
|
+
loopbackReadable?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The upgrade guard's verdict: reject, or admit at a trust level.
|
|
47
|
+
*
|
|
48
|
+
* `false` — reject the handshake (403). A `trusted` connection is provably the
|
|
49
|
+
* cockpit itself — a same-authority Origin, a no-Origin native client, or a dev
|
|
50
|
+
* proxy the browser vouches for via `Sec-Fetch-Site` — and may subscribe to any
|
|
51
|
+
* topic. A NON-`trusted` connection was admitted by the loopback-origin fallback
|
|
52
|
+
* on a browser that ships no `Sec-Fetch` metadata, where the dev proxy and a
|
|
53
|
+
* foreign page on another local port are indistinguishable at the handshake: it
|
|
54
|
+
* may subscribe ONLY to topics a publisher flagged `loopbackReadable`.
|
|
55
|
+
*/
|
|
56
|
+
export type WsUpgradeVerdict = false | {
|
|
57
|
+
trusted: boolean;
|
|
58
|
+
};
|
|
59
|
+
/** The minimal server surface `attach` needs — satisfied by the `http.Server`
|
|
60
|
+
* that `@hono/node-server`'s `serve()` returns. */
|
|
61
|
+
export interface UpgradeCapableServer {
|
|
62
|
+
on(event: 'upgrade', listener: (req: IncomingMessage, socket: Duplex, head: Buffer) => void): unknown;
|
|
63
|
+
on(event: 'close', listener: () => void): unknown;
|
|
64
|
+
}
|
|
65
|
+
export interface SocketHub {
|
|
66
|
+
/** Register a topic clients may subscribe to. Registration is boot-time
|
|
67
|
+
* wiring (createApp), so a duplicate name is a programming error: throw.
|
|
68
|
+
* `options.loopbackReadable` defaults to `false` (topic legible to trusted
|
|
69
|
+
* connections only) — see `TopicOptions`. */
|
|
70
|
+
registerTopic(name: string, publisher: TopicPublisher, options?: TopicOptions): void;
|
|
71
|
+
/** Start accepting `WS_PATH` upgrades on `server`. `verifyUpgrade` is the
|
|
72
|
+
* request-origin guard — `false` answers 403 before the handshake, otherwise
|
|
73
|
+
* its `trusted` flag decides which topics the connection may read. Boot-time
|
|
74
|
+
* wiring like `registerTopic`: attaching twice throws. */
|
|
75
|
+
attach(server: UpgradeCapableServer, verifyUpgrade: (req: IncomingMessage) => WsUpgradeVerdict): void;
|
|
76
|
+
/** Stop publishers, terminate clients, clear timers. Idempotent; also runs
|
|
77
|
+
* on the attached server's own `close`. */
|
|
78
|
+
close(): void;
|
|
79
|
+
}
|
|
80
|
+
export interface SocketHubOptions {
|
|
81
|
+
/** Heartbeat interval override (ms). Defaults to `HEARTBEAT_MS`; tests set it
|
|
82
|
+
* small so reaping and the liveness beat are observable without a 30 s wait. */
|
|
83
|
+
heartbeatMs?: number;
|
|
84
|
+
}
|
|
85
|
+
export declare function createSocketHub(options?: SocketHubOptions): SocketHub;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { WebSocketServer } from 'ws';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* The cockpit's WebSocket subscription bus (`GET /api/v1/ws`, upgrade-only).
|
|
5
|
+
* Design + the subscribe/unsubscribe discipline: spec
|
|
6
|
+
* `.ai/specs/2026-07-23-websocket-subscriptions.md`.
|
|
7
|
+
*
|
|
8
|
+
* One socket per cockpit, many topics per socket: the client sends
|
|
9
|
+
* `{type:'subscribe'|'unsubscribe', topic}` frames and receives
|
|
10
|
+
* `{type:'event', topic, data}` frames for the topics it holds. The point of
|
|
11
|
+
* the topic layer is that server-side work is DEMAND-DRIVEN: a topic's
|
|
12
|
+
* publisher starts when its subscriber count goes 0→1 and is stopped at 1→0,
|
|
13
|
+
* so a workspace with no cockpit open (or a cockpit that navigated away from
|
|
14
|
+
* everything using a topic) costs zero timers, zero shell-outs, zero anything.
|
|
15
|
+
* That is what replaced the per-tab `GET /api/health` poll (#369): N tabs used
|
|
16
|
+
* to mean N polls every 5 s forever; now they mean one publisher while at
|
|
17
|
+
* least one tab subscribes, and none afterwards.
|
|
18
|
+
*
|
|
19
|
+
* Security: the hub itself never decides who may connect — `attach` takes the
|
|
20
|
+
* upgrade guard as a function, and server.ts supplies the WebSocket twin of
|
|
21
|
+
* the `/api/*` request-origin guard (#426). See `verifyWsUpgrade` there for
|
|
22
|
+
* what it admits, and the caveat about which topics may live on this hub.
|
|
23
|
+
*/
|
|
24
|
+
/** The one upgrade path. Workspace-level like `/api/workspace/events` — never
|
|
25
|
+
* mirrored under `/api/p/:projectId` (topics carry workspace data). */
|
|
26
|
+
export const WS_PATH = '/api/v1/ws';
|
|
27
|
+
/** Heartbeat cadence — the interval on which the hub both REAPS silently-dead
|
|
28
|
+
* clients (a killed browser process sends no FIN) and emits a liveness beat
|
|
29
|
+
* every client can see. A client that missed the previous beat's pong is gone;
|
|
30
|
+
* terminating it releases its topic subscriptions so publishers can stop. */
|
|
31
|
+
const HEARTBEAT_MS = 30_000;
|
|
32
|
+
/** Client frames are tiny control messages; anything bigger is not our
|
|
33
|
+
* protocol. Kills the abuse case of a page streaming megabytes at the hub. */
|
|
34
|
+
const MAX_PAYLOAD_BYTES = 4_096;
|
|
35
|
+
/** Everything a client may say. Malformed frames get an `error` frame back
|
|
36
|
+
* (not a disconnect — one buggy caller must not tear down its tab's other
|
|
37
|
+
* subscriptions). */
|
|
38
|
+
const clientFrameSchema = z.object({
|
|
39
|
+
type: z.enum(['subscribe', 'unsubscribe']),
|
|
40
|
+
topic: z.string().min(1).max(128),
|
|
41
|
+
});
|
|
42
|
+
export function createSocketHub(options = {}) {
|
|
43
|
+
const heartbeatMs = options.heartbeatMs ?? HEARTBEAT_MS;
|
|
44
|
+
const topics = new Map();
|
|
45
|
+
const clients = new Map();
|
|
46
|
+
const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD_BYTES });
|
|
47
|
+
let heartbeat;
|
|
48
|
+
let closed = false;
|
|
49
|
+
const send = (ws, frame) => {
|
|
50
|
+
// OPEN as the literal 1: the check must hold for any socket implementation.
|
|
51
|
+
if (ws.readyState === 1)
|
|
52
|
+
ws.send(JSON.stringify(frame));
|
|
53
|
+
};
|
|
54
|
+
const subscribe = (ws, client, topic) => {
|
|
55
|
+
const state = topics.get(topic);
|
|
56
|
+
if (!state) {
|
|
57
|
+
send(ws, { type: 'error', topic, error: 'unknown topic' });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!state.loopbackReadable && !client.trusted) {
|
|
61
|
+
// Admitted by the loopback-origin fallback (a browser with no Sec-Fetch,
|
|
62
|
+
// so indistinguishable from a foreign local page): only topics explicitly
|
|
63
|
+
// marked safe for any local page are legible. This is the mechanical half
|
|
64
|
+
// of the topic-safety invariant — the guard opens the door, the flag says
|
|
65
|
+
// which rooms an unproven caller may enter.
|
|
66
|
+
send(ws, { type: 'error', topic, error: 'forbidden topic' });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (client.topics.has(topic))
|
|
70
|
+
return; // idempotent — a re-sent frame must not double anything
|
|
71
|
+
client.topics.add(topic);
|
|
72
|
+
state.subscribers.add(ws);
|
|
73
|
+
if (state.stop === null) {
|
|
74
|
+
state.stop = state.publisher.start((data) => {
|
|
75
|
+
for (const subscriber of state.subscribers)
|
|
76
|
+
send(subscriber, { type: 'event', topic, data });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// The immediate snapshot — async, so the socket or the subscription may be
|
|
80
|
+
// gone by the time it resolves; both are re-checked. A failing snapshot is
|
|
81
|
+
// dropped: the publisher's next publish carries the same news.
|
|
82
|
+
void state.publisher
|
|
83
|
+
.snapshot()
|
|
84
|
+
.then((data) => {
|
|
85
|
+
if (client.topics.has(topic))
|
|
86
|
+
send(ws, { type: 'event', topic, data });
|
|
87
|
+
})
|
|
88
|
+
.catch(() => undefined);
|
|
89
|
+
};
|
|
90
|
+
const unsubscribe = (ws, client, topic) => {
|
|
91
|
+
if (!client.topics.delete(topic))
|
|
92
|
+
return;
|
|
93
|
+
const state = topics.get(topic);
|
|
94
|
+
if (!state)
|
|
95
|
+
return;
|
|
96
|
+
state.subscribers.delete(ws);
|
|
97
|
+
if (state.subscribers.size === 0 && state.stop !== null) {
|
|
98
|
+
state.stop();
|
|
99
|
+
state.stop = null;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const dropClient = (ws) => {
|
|
103
|
+
const client = clients.get(ws);
|
|
104
|
+
if (!client)
|
|
105
|
+
return;
|
|
106
|
+
for (const topic of [...client.topics])
|
|
107
|
+
unsubscribe(ws, client, topic);
|
|
108
|
+
clients.delete(ws);
|
|
109
|
+
};
|
|
110
|
+
wss.on('connection', (ws, _req, trusted) => {
|
|
111
|
+
// `trusted` is emitted by `attach` from the upgrade verdict; default false is
|
|
112
|
+
// the safe read for any path that reaches here without one.
|
|
113
|
+
const client = { alive: true, topics: new Set(), trusted: trusted === true };
|
|
114
|
+
clients.set(ws, client);
|
|
115
|
+
ws.on('pong', () => {
|
|
116
|
+
client.alive = true;
|
|
117
|
+
});
|
|
118
|
+
ws.on('message', (raw) => {
|
|
119
|
+
let parsed;
|
|
120
|
+
try {
|
|
121
|
+
parsed = JSON.parse(String(raw));
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
send(ws, { type: 'error', error: 'malformed frame: not JSON' });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const frame = clientFrameSchema.safeParse(parsed);
|
|
128
|
+
if (!frame.success) {
|
|
129
|
+
send(ws, { type: 'error', error: 'malformed frame: expected {type: subscribe|unsubscribe, topic}' });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (frame.data.type === 'subscribe')
|
|
133
|
+
subscribe(ws, client, frame.data.topic);
|
|
134
|
+
else
|
|
135
|
+
unsubscribe(ws, client, frame.data.topic);
|
|
136
|
+
});
|
|
137
|
+
ws.on('close', () => dropClient(ws));
|
|
138
|
+
// A socket error is followed by `close`; the handler exists so ws never
|
|
139
|
+
// throws it as an unhandled 'error' event.
|
|
140
|
+
ws.on('error', () => undefined);
|
|
141
|
+
});
|
|
142
|
+
const hub = {
|
|
143
|
+
registerTopic(name, publisher, options) {
|
|
144
|
+
if (topics.has(name))
|
|
145
|
+
throw new Error(`ws topic already registered: ${name}`);
|
|
146
|
+
topics.set(name, {
|
|
147
|
+
publisher,
|
|
148
|
+
subscribers: new Set(),
|
|
149
|
+
stop: null,
|
|
150
|
+
loopbackReadable: options?.loopbackReadable ?? false,
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
attach(server, verifyUpgrade) {
|
|
154
|
+
// Boot-time wiring like `registerTopic`, so a second call is a programming
|
|
155
|
+
// error rather than something to absorb: it would install a second
|
|
156
|
+
// `upgrade` listener and orphan the first heartbeat interval (`close`
|
|
157
|
+
// clears only the last one). Throw the same way registration does.
|
|
158
|
+
if (heartbeat !== undefined)
|
|
159
|
+
throw new Error('ws hub already attached');
|
|
160
|
+
server.on('upgrade', (req, socket, head) => {
|
|
161
|
+
// `req.url` on an upgrade is the request path (+query); the base is
|
|
162
|
+
// only there to satisfy URL parsing of a relative reference.
|
|
163
|
+
const pathname = new URL(req.url ?? '', 'http://localhost').pathname;
|
|
164
|
+
if (pathname !== WS_PATH) {
|
|
165
|
+
// With an `upgrade` listener installed, Node no longer auto-destroys
|
|
166
|
+
// unhandled upgrades — do it explicitly or the socket leaks.
|
|
167
|
+
socket.destroy();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const verdict = verifyUpgrade(req);
|
|
171
|
+
if (!verdict) {
|
|
172
|
+
socket.write('HTTP/1.1 403 Forbidden\r\nconnection: close\r\n\r\n');
|
|
173
|
+
socket.destroy();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// Carry the verdict's trust flag onto the connection — `subscribe` reads
|
|
177
|
+
// it to gate non-`loopbackReadable` topics.
|
|
178
|
+
wss.handleUpgrade(req, socket, head, (ws) => wss.emit('connection', ws, req, verdict.trusted));
|
|
179
|
+
});
|
|
180
|
+
server.on('close', () => hub.close());
|
|
181
|
+
heartbeat = setInterval(() => {
|
|
182
|
+
for (const [ws, client] of clients) {
|
|
183
|
+
if (!client.alive) {
|
|
184
|
+
// Missed the previous beat's pong — the peer is gone. Terminate (not
|
|
185
|
+
// a polite close, there is nobody to handshake with); 'close' fires
|
|
186
|
+
// synchronously and dropClient releases its topics.
|
|
187
|
+
ws.terminate();
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
client.alive = false;
|
|
191
|
+
ws.ping(); // protocol ping — a live browser auto-pongs, flipping `alive` back
|
|
192
|
+
// App-level beat too. The browser NEVER surfaces the protocol ping to
|
|
193
|
+
// page JS, so a client cannot tell a silent-dead socket (sleep, network
|
|
194
|
+
// partition — no FIN ever arrives) from an idle-but-healthy one on the
|
|
195
|
+
// protocol ping alone. This visible frame is what the client's own
|
|
196
|
+
// watchdog watches to decide to reconnect. Sent to every client,
|
|
197
|
+
// subscribed or not; the client ignores it beyond noting liveness.
|
|
198
|
+
send(ws, { type: 'ping' });
|
|
199
|
+
}
|
|
200
|
+
}, heartbeatMs);
|
|
201
|
+
// Never the reason the process stays up — the HTTP server owns lifetime.
|
|
202
|
+
heartbeat.unref?.();
|
|
203
|
+
},
|
|
204
|
+
close() {
|
|
205
|
+
if (closed)
|
|
206
|
+
return;
|
|
207
|
+
closed = true;
|
|
208
|
+
clearInterval(heartbeat);
|
|
209
|
+
for (const ws of [...clients.keys()]) {
|
|
210
|
+
dropClient(ws); // stops publishers even if 'close' events never get to fire
|
|
211
|
+
ws.terminate();
|
|
212
|
+
}
|
|
213
|
+
wss.close();
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
return hub;
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=ws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.js","sourceRoot":"","sources":["../../src/server/ws.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** True when this process is running inside WSL. The Linux kernel WSL ships identifies itself
|
|
2
|
+
* in `/proc/version` (contains "microsoft"); WSL also sets `WSL_DISTRO_NAME`/`WSL_INTEROP` for
|
|
3
|
+
* every process it starts, which is cheaper to check first. */
|
|
4
|
+
export declare function isWsl(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, procVersion?: string): boolean;
|
|
5
|
+
/** The distro name Windows-side tools address this environment as (`wsl.exe -d <name>`, the
|
|
6
|
+
* `\\wsl$\<name>\…` UNC prefix). Falls back to "Ubuntu" — WSL's own default — when unset or
|
|
7
|
+
* when the name fails `SAFE_DISTRO_NAME`, so no unvalidated env value is ever interpolated into
|
|
8
|
+
* a command line or a path. The launchers this feeds are shell-free anyway
|
|
9
|
+
* (`wslTerminalLaunchers`); this is the belt to their braces. */
|
|
10
|
+
export declare function wslDistroName(env?: NodeJS.ProcessEnv): string;
|
|
11
|
+
/** Pure POSIX → Windows path translation, no `wslpath` required (used as its fallback, and
|
|
12
|
+
* directly in tests). Two cases:
|
|
13
|
+
* - `/mnt/<drive>/…` (the Windows filesystem mounted into WSL) → `<DRIVE>:\…`, the native path,
|
|
14
|
+
* not a UNC round-trip through the distro.
|
|
15
|
+
* - anything else (the Linux filesystem proper) → `\\wsl$\<Distro>\…`, the UNC path Windows
|
|
16
|
+
* apps use to reach into the distro. */
|
|
17
|
+
export declare function toWindowsPath(posixPath: string, distro?: string): string;
|
|
18
|
+
/** The reverse of the UNC case above: `\\wsl$\<Distro>\…` or `\\wsl.localhost\<Distro>\…` →
|
|
19
|
+
* the POSIX path WSL-side tools expect. Anything else (a plain `C:\…`, an unrecognized UNC
|
|
20
|
+
* share) passes through unchanged — it isn't a WSL path to begin with. */
|
|
21
|
+
export declare function toPosixPath(windowsPath: string): string;
|
|
22
|
+
/** The translation actually used at launch time: prefer the real `wslpath -w` (it knows about
|
|
23
|
+
* every mount, not just `/mnt/<drive>`), falling back to the pure `toWindowsPath` above when
|
|
24
|
+
* `wslpath` is missing or errors — degrade, never throw. */
|
|
25
|
+
export declare function translateToWindowsPath(posixPath: string): string;
|