@opengsd/gsd-pi 1.8.1 → 1.9.0-dev.5c1a3ff2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/cli.js +5 -1
- package/dist/headless-events.d.ts +2 -1
- package/dist/headless-events.js +11 -3
- package/dist/headless-milestone-readiness.d.ts +29 -0
- package/dist/headless-milestone-readiness.js +161 -0
- package/dist/headless.js +25 -2
- package/dist/print-mode-exit.d.ts +12 -0
- package/dist/print-mode-exit.js +14 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +18 -49
- package/dist/resources/extensions/browser-tools/capture.js +2 -77
- package/dist/resources/extensions/browser-tools/screenshot-constraints.js +83 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +17 -2
- package/dist/resources/extensions/claude-code-cli/turn-assembler.js +30 -2
- package/dist/resources/extensions/gsd/artifact-verification.js +3 -4
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +16 -10
- package/dist/resources/extensions/gsd/auto-dispatch.js +74 -19
- package/dist/resources/extensions/gsd/auto-post-unit.js +4 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +125 -18
- package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
- package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree-teardown.js +1 -1
- package/dist/resources/extensions/gsd/auto.js +5 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +80 -4
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +31 -5
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +88 -50
- package/dist/resources/extensions/gsd/command-feedback.js +16 -12
- package/dist/resources/extensions/gsd/compat/compat-marker.js +19 -2
- package/dist/resources/extensions/gsd/constants.js +12 -13
- package/dist/resources/extensions/gsd/context-masker.js +97 -21
- package/dist/resources/extensions/gsd/db/command-queue.js +7 -2
- package/dist/resources/extensions/gsd/db/engine.js +51 -16
- package/dist/resources/extensions/gsd/db/writers/cascades.js +5 -1
- package/dist/resources/extensions/gsd/db/writers/status.js +13 -3
- package/dist/resources/extensions/gsd/db-base-schema.js +28 -0
- package/dist/resources/extensions/gsd/db-migration-steps.js +30 -0
- package/dist/resources/extensions/gsd/db-schema-metadata.js +4 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +26 -4
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +3 -6
- package/dist/resources/extensions/gsd/flat-phase-migration.js +156 -21
- package/dist/resources/extensions/gsd/git-service.js +19 -7
- package/dist/resources/extensions/gsd/gsd-db.js +187 -45
- package/dist/resources/extensions/gsd/guided-flow.js +38 -13
- package/dist/resources/extensions/gsd/markdown-renderer.js +17 -13
- package/dist/resources/extensions/gsd/md-importer.js +107 -20
- package/dist/resources/extensions/gsd/migrate/command.js +19 -162
- package/dist/resources/extensions/gsd/migrate/execution.js +98 -0
- package/dist/resources/extensions/gsd/migrate/plan.js +49 -0
- package/dist/resources/extensions/gsd/migrate/planning-writer.js +12 -17
- package/dist/resources/extensions/gsd/migrate/presentation.js +64 -0
- package/dist/resources/extensions/gsd/model-cost-table.js +2 -0
- package/dist/resources/extensions/gsd/model-router.js +11 -0
- package/dist/resources/extensions/gsd/parallel-eligibility.js +5 -4
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +28 -43
- package/dist/resources/extensions/gsd/paths.js +55 -11
- package/dist/resources/extensions/gsd/phase-anchor.js +7 -2
- package/dist/resources/extensions/gsd/planning-subagent-policy.js +37 -0
- package/dist/resources/extensions/gsd/planning-subagent-registry.js +25 -0
- package/dist/resources/extensions/gsd/post-execution-checks.js +4 -4
- package/dist/resources/extensions/gsd/preferences-types.js +5 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +48 -1
- package/dist/resources/extensions/gsd/preferences.js +17 -0
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +2 -2
- package/dist/resources/extensions/gsd/provider-payload-policy.js +16 -5
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +1 -1
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +26 -1
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +30 -5
- package/dist/resources/extensions/gsd/skill-activation.js +17 -3
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.js +27 -13
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.js +13 -3
- package/dist/resources/extensions/gsd/stop-notice.js +15 -1
- package/dist/resources/extensions/gsd/sync-lock.js +13 -7
- package/dist/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
- package/dist/resources/extensions/gsd/tools/complete-task.js +94 -6
- package/dist/resources/extensions/gsd/tools/plan-task.js +13 -15
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +18 -15
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +14 -5
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +15 -5
- package/dist/resources/extensions/gsd/tools/reopen-task.js +16 -10
- package/dist/resources/extensions/gsd/tools/replan-task.js +128 -0
- package/dist/resources/extensions/gsd/tools/rework-brief.js +74 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +81 -0
- package/dist/resources/extensions/gsd/unit-context-composer.js +28 -5
- package/dist/resources/extensions/gsd/unit-registry.js +4 -0
- package/dist/resources/extensions/gsd/uok/plan-v2.js +19 -3
- package/dist/resources/extensions/gsd/workflow-projections.js +2 -3
- package/dist/resources/extensions/gsd/workspace.js +2 -1
- package/dist/resources/extensions/gsd/worktree-state-projection.js +6 -0
- package/dist/resources/extensions/mac-tools/index.js +31 -9
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +5 -5
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/chunks/2842.js +4 -4
- package/dist/web/standalone/.next/server/chunks/8357.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +3 -3
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-a6fb1847f67f167c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/{layout-b35cbfff38aaf4cf.js → layout-4ae2d68984392bbf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-72a856634ad14c10.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{main-app-590a74400e35f685.js → main-app-90d1d8d5e5d2dc6b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-1115d46ac61b9823.js +1 -0
- package/dist/web/standalone/.next/static/css/3b1ceb7abfb9a230.css +1 -0
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/package.json +0 -1
- package/dist/web/standalone/server.js +1 -1
- package/dist/web-mode.js +31 -3
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +12 -10
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +14 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +16 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/cloud-config.d.ts +1 -1
- package/packages/daemon/dist/cloud-config.d.ts.map +1 -1
- package/packages/daemon/dist/cloud-config.js +5 -1
- package/packages/daemon/dist/cloud-config.js.map +1 -1
- package/packages/daemon/dist/cloud-config.test.js +51 -1
- package/packages/daemon/dist/cloud-config.test.js.map +1 -1
- package/packages/daemon/dist/local-tool-executor.d.ts.map +1 -1
- package/packages/daemon/dist/local-tool-executor.js +2 -0
- package/packages/daemon/dist/local-tool-executor.js.map +1 -1
- package/packages/daemon/package.json +6 -6
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js +36 -4
- package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.js +1 -0
- package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/dist/cli.d.ts +5 -0
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cli.js +160 -0
- package/packages/gsd-cloud/dist/cli.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-config.d.ts +19 -0
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-config.js +231 -0
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-config.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/cloud-config.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-config.test.js +118 -0
- package/packages/gsd-cloud/dist/cloud-config.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.js +37 -0
- package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +35 -0
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-runtime.js +258 -0
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-runtime.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/cloud-runtime.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-runtime.test.js +88 -0
- package/packages/gsd-cloud/dist/cloud-runtime.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-token.d.ts +3 -0
- package/packages/gsd-cloud/dist/cloud-token.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-token.js +37 -0
- package/packages/gsd-cloud/dist/cloud-token.js.map +1 -0
- package/packages/gsd-cloud/dist/config.d.ts +17 -0
- package/packages/gsd-cloud/dist/config.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/config.js +146 -0
- package/packages/gsd-cloud/dist/config.js.map +1 -0
- package/packages/gsd-cloud/dist/device-flow.d.ts +24 -0
- package/packages/gsd-cloud/dist/device-flow.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/device-flow.js +128 -0
- package/packages/gsd-cloud/dist/device-flow.js.map +1 -0
- package/packages/gsd-cloud/dist/device-flow.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/device-flow.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/device-flow.test.js +155 -0
- package/packages/gsd-cloud/dist/device-flow.test.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/claude-executor.d.ts +6 -0
- package/packages/gsd-cloud/dist/executors/claude-executor.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/claude-executor.js +15 -0
- package/packages/gsd-cloud/dist/executors/claude-executor.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/codex-executor.d.ts +6 -0
- package/packages/gsd-cloud/dist/executors/codex-executor.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/codex-executor.js +15 -0
- package/packages/gsd-cloud/dist/executors/codex-executor.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/executor.d.ts +28 -0
- package/packages/gsd-cloud/dist/executors/executor.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/executor.js +11 -0
- package/packages/gsd-cloud/dist/executors/executor.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.d.ts +34 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.js +179 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.js +44 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/index.d.ts +17 -0
- package/packages/gsd-cloud/dist/executors/index.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/index.js +29 -0
- package/packages/gsd-cloud/dist/executors/index.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.d.ts +36 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.js +176 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.js +34 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.js.map +1 -0
- package/packages/gsd-cloud/dist/index.d.ts +7 -0
- package/packages/gsd-cloud/dist/index.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/index.js +8 -0
- package/packages/gsd-cloud/dist/index.js.map +1 -0
- package/packages/gsd-cloud/dist/inject-gateway.d.ts +16 -0
- package/packages/gsd-cloud/dist/inject-gateway.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/inject-gateway.js +29 -0
- package/packages/gsd-cloud/dist/inject-gateway.js.map +1 -0
- package/packages/gsd-cloud/dist/inject-gateway.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/inject-gateway.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/inject-gateway.test.js +41 -0
- package/packages/gsd-cloud/dist/inject-gateway.test.js.map +1 -0
- package/packages/gsd-cloud/dist/logger.d.ts +25 -0
- package/packages/gsd-cloud/dist/logger.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/logger.js +72 -0
- package/packages/gsd-cloud/dist/logger.js.map +1 -0
- package/packages/gsd-cloud/dist/types.d.ts +51 -0
- package/packages/gsd-cloud/dist/types.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/types.js +9 -0
- package/packages/gsd-cloud/dist/types.js.map +1 -0
- package/packages/gsd-cloud/package.json +3 -3
- package/packages/mcp-server/README.md +15 -2
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +5 -5
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +4 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +64 -2
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +5 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +119 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +107 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +1 -0
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +25 -1
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +11 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +18 -49
- package/src/resources/extensions/browser-tools/capture.ts +2 -86
- package/src/resources/extensions/browser-tools/screenshot-constraints.ts +94 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +18 -1
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +108 -0
- package/src/resources/extensions/claude-code-cli/turn-assembler.ts +30 -2
- package/src/resources/extensions/gsd/artifact-verification.ts +3 -8
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +16 -9
- package/src/resources/extensions/gsd/auto-dispatch.ts +86 -19
- package/src/resources/extensions/gsd/auto-post-unit.ts +4 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +142 -16
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -8
- package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +4 -0
- package/src/resources/extensions/gsd/auto-worktree-teardown.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +5 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +92 -4
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -10
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +87 -53
- package/src/resources/extensions/gsd/command-feedback.ts +19 -12
- package/src/resources/extensions/gsd/compat/compat-marker.ts +21 -4
- package/src/resources/extensions/gsd/constants.ts +14 -13
- package/src/resources/extensions/gsd/context-masker.ts +98 -2
- package/src/resources/extensions/gsd/db/command-queue.ts +7 -1
- package/src/resources/extensions/gsd/db/engine.ts +53 -17
- package/src/resources/extensions/gsd/db/writers/cascades.ts +5 -1
- package/src/resources/extensions/gsd/db/writers/status.ts +25 -8
- package/src/resources/extensions/gsd/db-base-schema.ts +30 -0
- package/src/resources/extensions/gsd/db-migration-steps.ts +32 -0
- package/src/resources/extensions/gsd/db-schema-metadata.ts +4 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +26 -4
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +3 -5
- package/src/resources/extensions/gsd/flat-phase-migration.ts +160 -19
- package/src/resources/extensions/gsd/git-service.ts +19 -6
- package/src/resources/extensions/gsd/gsd-db.ts +255 -52
- package/src/resources/extensions/gsd/guided-flow.ts +48 -12
- package/src/resources/extensions/gsd/markdown-renderer.ts +25 -21
- package/src/resources/extensions/gsd/md-importer.ts +114 -16
- package/src/resources/extensions/gsd/migrate/command.ts +21 -227
- package/src/resources/extensions/gsd/migrate/execution.ts +136 -0
- package/src/resources/extensions/gsd/migrate/plan.ts +101 -0
- package/src/resources/extensions/gsd/migrate/planning-writer.ts +12 -20
- package/src/resources/extensions/gsd/migrate/presentation.ts +83 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +2 -0
- package/src/resources/extensions/gsd/model-router.ts +12 -0
- package/src/resources/extensions/gsd/parallel-eligibility.ts +5 -4
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +37 -40
- package/src/resources/extensions/gsd/paths.ts +62 -11
- package/src/resources/extensions/gsd/phase-anchor.ts +8 -2
- package/src/resources/extensions/gsd/planning-subagent-policy.ts +58 -0
- package/src/resources/extensions/gsd/planning-subagent-registry.ts +31 -0
- package/src/resources/extensions/gsd/post-execution-checks.ts +4 -4
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +62 -0
- package/src/resources/extensions/gsd/preferences.ts +25 -0
- package/src/resources/extensions/gsd/prompts/refine-slice.md +2 -2
- package/src/resources/extensions/gsd/provider-payload-policy.ts +18 -4
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +1 -1
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +28 -1
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +31 -5
- package/src/resources/extensions/gsd/skill-activation.ts +17 -3
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.ts +30 -13
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.ts +16 -3
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +2 -2
- package/src/resources/extensions/gsd/stop-notice.ts +18 -1
- package/src/resources/extensions/gsd/sync-lock.ts +14 -6
- package/src/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +96 -1
- package/src/resources/extensions/gsd/tests/command-feedback.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +104 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +362 -1
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +204 -8
- package/src/resources/extensions/gsd/tests/db-engine-migrate-guards.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/db-tools-alias-suppression.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/discuss-routing-fixes.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/external-edit-scoped-import.test.ts +262 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +202 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/import-slice-milestone-completion-preserve.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +22 -4
- package/src/resources/extensions/gsd/tests/integration-branch-meta-location.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/migrate-plan.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/migrate-presentation.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/migrate-safety-audit.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parallel-eligibility-file-overlap.test.ts +146 -0
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/phase-anchor.test.ts +32 -4
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +26 -23
- package/src/resources/extensions/gsd/tests/planning-writer.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/replan-task-handler.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/rework-brief.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +33 -7
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -16
- package/src/resources/extensions/gsd/tests/skip-slice-cascades-tasks.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/stop-notice.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +93 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/workflow-migration-gates.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +52 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +140 -5
- package/src/resources/extensions/gsd/tools/plan-task.ts +14 -16
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +18 -17
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +24 -5
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +25 -5
- package/src/resources/extensions/gsd/tools/reopen-task.ts +23 -9
- package/src/resources/extensions/gsd/tools/replan-task.ts +160 -0
- package/src/resources/extensions/gsd/tools/rework-brief.ts +91 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +92 -0
- package/src/resources/extensions/gsd/types.ts +11 -0
- package/src/resources/extensions/gsd/unit-context-composer.ts +34 -8
- package/src/resources/extensions/gsd/unit-registry.ts +4 -0
- package/src/resources/extensions/gsd/uok/plan-v2.ts +20 -3
- package/src/resources/extensions/gsd/workflow-projections.ts +2 -3
- package/src/resources/extensions/gsd/workspace.ts +2 -1
- package/src/resources/extensions/gsd/worktree-state-projection.ts +12 -0
- package/src/resources/extensions/mac-tools/index.ts +52 -9
- package/src/resources/extensions/mac-tools/tests/mac-screenshot-payload.test.cjs +70 -0
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-49f565245e1e4afe.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/page-bc4c723c6a19c6f0.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ec3eaffc9785ba48.js +0 -1
- package/dist/web/standalone/.next/static/css/18c2fdf261bfc1b7.css +0 -1
- /package/dist/web/standalone/.next/static/{UaXRrOYxBpG9QBINKhZkg → 6YWV7VAPg-jqjfPswrKZb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{UaXRrOYxBpG9QBINKhZkg → 6YWV7VAPg-jqjfPswrKZb}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { parse as parseYaml } from 'yaml';
|
|
5
|
+
import { unprotectCloudDeviceToken } from './cloud-token.js';
|
|
6
|
+
const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error']);
|
|
7
|
+
/** Expand leading ~ to the user's home directory. */
|
|
8
|
+
function expandTilde(p) {
|
|
9
|
+
if (p.startsWith('~/') || p === '~') {
|
|
10
|
+
return resolve(homedir(), p.slice(2) || '.');
|
|
11
|
+
}
|
|
12
|
+
return p;
|
|
13
|
+
}
|
|
14
|
+
/** Default config values when no file is present or fields are missing. */
|
|
15
|
+
function defaults() {
|
|
16
|
+
return {
|
|
17
|
+
cloud: undefined,
|
|
18
|
+
discord: undefined,
|
|
19
|
+
projects: { scan_roots: [] },
|
|
20
|
+
log: {
|
|
21
|
+
file: resolve(homedir(), '.gsd', 'daemon.log'),
|
|
22
|
+
level: 'info',
|
|
23
|
+
max_size_mb: 50,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the config file path.
|
|
29
|
+
* Priority: explicit CLI arg → GSD_DAEMON_CONFIG env → ~/.gsd/daemon.yaml
|
|
30
|
+
*/
|
|
31
|
+
export function resolveConfigPath(cliPath) {
|
|
32
|
+
if (cliPath)
|
|
33
|
+
return expandTilde(cliPath);
|
|
34
|
+
const envPath = process.env['GSD_DAEMON_CONFIG'];
|
|
35
|
+
if (envPath)
|
|
36
|
+
return expandTilde(envPath);
|
|
37
|
+
return resolve(homedir(), '.gsd', 'daemon.yaml');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Validate and normalise a raw parsed object into a DaemonConfig.
|
|
41
|
+
* Missing/invalid fields are filled with defaults. Invalid log level falls back to 'info'.
|
|
42
|
+
*/
|
|
43
|
+
export function validateConfig(raw) {
|
|
44
|
+
const def = defaults();
|
|
45
|
+
if (raw == null || typeof raw !== 'object')
|
|
46
|
+
return def;
|
|
47
|
+
const obj = raw;
|
|
48
|
+
// --- cloud ---
|
|
49
|
+
let cloud = undefined;
|
|
50
|
+
if (obj['cloud'] != null && typeof obj['cloud'] === 'object') {
|
|
51
|
+
const c = obj['cloud'];
|
|
52
|
+
const encryptedDeviceToken = typeof c['device_token_encrypted'] === 'string'
|
|
53
|
+
? unprotectCloudDeviceToken(c['device_token_encrypted'])
|
|
54
|
+
: undefined;
|
|
55
|
+
const deviceToken = typeof c['device_token'] === 'string' ? c['device_token'] : encryptedDeviceToken;
|
|
56
|
+
cloud = {
|
|
57
|
+
gateway_url: typeof c['gateway_url'] === 'string' ? c['gateway_url'] : '',
|
|
58
|
+
...(deviceToken ? { device_token: deviceToken } : {}),
|
|
59
|
+
...(typeof c['runtime_id'] === 'string' ? { runtime_id: c['runtime_id'] } : {}),
|
|
60
|
+
...(typeof c['runtime_name'] === 'string' ? { runtime_name: c['runtime_name'] } : {}),
|
|
61
|
+
...(typeof c['enabled'] === 'boolean' ? { enabled: c['enabled'] } : {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// --- discord ---
|
|
65
|
+
let discord = undefined;
|
|
66
|
+
if (obj['discord'] != null && typeof obj['discord'] === 'object') {
|
|
67
|
+
const d = obj['discord'];
|
|
68
|
+
discord = {
|
|
69
|
+
token: typeof d['token'] === 'string' ? d['token'] : '',
|
|
70
|
+
guild_id: typeof d['guild_id'] === 'string' ? d['guild_id'] : '',
|
|
71
|
+
owner_id: typeof d['owner_id'] === 'string' ? d['owner_id'] : '',
|
|
72
|
+
...(typeof d['dm_on_blocker'] === 'boolean' ? { dm_on_blocker: d['dm_on_blocker'] } : {}),
|
|
73
|
+
...(typeof d['control_channel_id'] === 'string' ? { control_channel_id: d['control_channel_id'] } : {}),
|
|
74
|
+
};
|
|
75
|
+
// Parse orchestrator sub-block
|
|
76
|
+
if (d['orchestrator'] != null && typeof d['orchestrator'] === 'object') {
|
|
77
|
+
const orc = d['orchestrator'];
|
|
78
|
+
discord.orchestrator = {
|
|
79
|
+
...(typeof orc['model'] === 'string' ? { model: orc['model'] } : {}),
|
|
80
|
+
...(typeof orc['max_tokens'] === 'number' && orc['max_tokens'] > 0 ? { max_tokens: orc['max_tokens'] } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// --- projects ---
|
|
85
|
+
let scanRoots = [];
|
|
86
|
+
if (obj['projects'] != null && typeof obj['projects'] === 'object') {
|
|
87
|
+
const p = obj['projects'];
|
|
88
|
+
if (Array.isArray(p['scan_roots'])) {
|
|
89
|
+
scanRoots = p['scan_roots']
|
|
90
|
+
.filter((s) => typeof s === 'string')
|
|
91
|
+
.map(expandTilde);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// --- log ---
|
|
95
|
+
let logFile = def.log.file;
|
|
96
|
+
let logLevel = def.log.level;
|
|
97
|
+
let maxSizeMb = def.log.max_size_mb;
|
|
98
|
+
if (obj['log'] != null && typeof obj['log'] === 'object') {
|
|
99
|
+
const l = obj['log'];
|
|
100
|
+
if (typeof l['file'] === 'string')
|
|
101
|
+
logFile = expandTilde(l['file']);
|
|
102
|
+
if (typeof l['level'] === 'string') {
|
|
103
|
+
logLevel = VALID_LOG_LEVELS.has(l['level']) ? l['level'] : 'info';
|
|
104
|
+
}
|
|
105
|
+
if (typeof l['max_size_mb'] === 'number' && l['max_size_mb'] > 0) {
|
|
106
|
+
maxSizeMb = l['max_size_mb'];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// --- env override: DISCORD_BOT_TOKEN ---
|
|
110
|
+
const envToken = process.env['DISCORD_BOT_TOKEN'];
|
|
111
|
+
if (envToken) {
|
|
112
|
+
if (!discord) {
|
|
113
|
+
discord = { token: envToken, guild_id: '', owner_id: '' };
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
discord = { ...discord, token: envToken };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
cloud,
|
|
121
|
+
discord,
|
|
122
|
+
projects: { scan_roots: scanRoots },
|
|
123
|
+
log: { file: logFile, level: logLevel, max_size_mb: maxSizeMb },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Load and validate a DaemonConfig from a YAML file.
|
|
128
|
+
* If the file doesn't exist, returns defaults. If the file is malformed YAML, throws.
|
|
129
|
+
*/
|
|
130
|
+
export function loadConfig(configPath) {
|
|
131
|
+
if (!existsSync(configPath)) {
|
|
132
|
+
// Still apply env-var overrides even when file is missing
|
|
133
|
+
return validateConfig(null);
|
|
134
|
+
}
|
|
135
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
136
|
+
let parsed;
|
|
137
|
+
try {
|
|
138
|
+
parsed = parseYaml(raw);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
142
|
+
throw new Error(`Failed to parse YAML config at ${configPath}: ${msg}`);
|
|
143
|
+
}
|
|
144
|
+
return validateConfig(parsed);
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE1F,qDAAqD;AACrD,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QACpC,OAAO,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,2EAA2E;AAC3E,SAAS,QAAQ;IACf,OAAO;QACL,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,SAAS;QAClB,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QAC5B,GAAG,EAAE;YACH,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC;YAC9C,KAAK,EAAE,MAAM;YACb,WAAW,EAAE,EAAE;SAChB;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAChD,IAAI,OAAO;QAAE,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACjD,IAAI,OAAO;QAAE,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,OAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;IAEvB,IAAI,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACvD,MAAM,GAAG,GAAG,GAA8B,CAAC;IAE3C,gBAAgB;IAChB,IAAI,KAAK,GAA0B,SAAS,CAAC;IAC7C,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC7D,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAA4B,CAAC;QAClD,MAAM,oBAAoB,GAAG,OAAO,CAAC,CAAC,wBAAwB,CAAC,KAAK,QAAQ;YAC1E,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC;YACxD,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,cAAc,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC;QACrG,KAAK,GAAG;YACN,WAAW,EAAE,OAAO,CAAC,CAAC,aAAa,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE;YACzE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/E,GAAG,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrF,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxE,CAAC;IACJ,CAAC;IAED,kBAAkB;IAClB,IAAI,OAAO,GAA4B,SAAS,CAAC;IACjD,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,SAAS,CAAC,KAAK,QAAQ,EAAE,CAAC;QACjE,MAAM,CAAC,GAAG,GAAG,CAAC,SAAS,CAA4B,CAAC;QACpD,OAAO,GAAG;YACR,KAAK,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE;YACvD,QAAQ,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE;YAChE,QAAQ,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE;YAChE,GAAG,CAAC,OAAO,CAAC,CAAC,eAAe,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzF,GAAG,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxG,CAAC;QAEF,+BAA+B;QAC/B,IAAI,CAAC,CAAC,cAAc,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,CAAC,cAAc,CAAC,KAAK,QAAQ,EAAE,CAAC;YACvE,MAAM,GAAG,GAAG,CAAC,CAAC,cAAc,CAA4B,CAAC;YACzD,OAAO,CAAC,YAAY,GAAG;gBACrB,GAAG,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpE,GAAG,CAAC,OAAO,GAAG,CAAC,YAAY,CAAC,KAAK,QAAQ,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC7G,CAAC;QACJ,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,IAAI,SAAS,GAAa,EAAE,CAAC;IAC7B,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,UAAU,CAAC,KAAK,QAAQ,EAAE,CAAC;QACnE,MAAM,CAAC,GAAG,GAAG,CAAC,UAAU,CAA4B,CAAC;QACrD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;YACnC,SAAS,GAAI,CAAC,CAAC,YAAY,CAAe;iBACvC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;iBACjD,GAAG,CAAC,WAAW,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,cAAc;IACd,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;IAC3B,IAAI,QAAQ,GAAa,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;IACvC,IAAI,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC;IAEpC,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;QACzD,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAA4B,CAAC;QAChD,IAAI,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ;YAAE,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACpE,IAAI,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YACnC,QAAQ,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,OAAO,CAAc,CAAC,CAAC,CAAC,MAAM,CAAC;QAClF,CAAC;QACD,IAAI,OAAO,CAAC,CAAC,aAAa,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;YACjE,SAAS,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,0CAA0C;IAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAClD,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAC5D,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK;QACL,OAAO;QACP,QAAQ,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE;QACnC,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE;KAChE,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,UAAkB;IAC3C,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,0DAA0D;QAC1D,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC9C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,kCAAkC,UAAU,KAAK,GAAG,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC","sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { unprotectCloudDeviceToken } from './cloud-token.js';\nimport type { DaemonConfig, LogLevel } from './types.js';\n\nconst VALID_LOG_LEVELS: ReadonlySet<string> = new Set(['debug', 'info', 'warn', 'error']);\n\n/** Expand leading ~ to the user's home directory. */\nfunction expandTilde(p: string): string {\n if (p.startsWith('~/') || p === '~') {\n return resolve(homedir(), p.slice(2) || '.');\n }\n return p;\n}\n\n/** Default config values when no file is present or fields are missing. */\nfunction defaults(): DaemonConfig {\n return {\n cloud: undefined,\n discord: undefined,\n projects: { scan_roots: [] },\n log: {\n file: resolve(homedir(), '.gsd', 'daemon.log'),\n level: 'info',\n max_size_mb: 50,\n },\n };\n}\n\n/**\n * Resolve the config file path.\n * Priority: explicit CLI arg → GSD_DAEMON_CONFIG env → ~/.gsd/daemon.yaml\n */\nexport function resolveConfigPath(cliPath?: string): string {\n if (cliPath) return expandTilde(cliPath);\n const envPath = process.env['GSD_DAEMON_CONFIG'];\n if (envPath) return expandTilde(envPath);\n return resolve(homedir(), '.gsd', 'daemon.yaml');\n}\n\n/**\n * Validate and normalise a raw parsed object into a DaemonConfig.\n * Missing/invalid fields are filled with defaults. Invalid log level falls back to 'info'.\n */\nexport function validateConfig(raw: unknown): DaemonConfig {\n const def = defaults();\n\n if (raw == null || typeof raw !== 'object') return def;\n const obj = raw as Record<string, unknown>;\n\n // --- cloud ---\n let cloud: DaemonConfig['cloud'] = undefined;\n if (obj['cloud'] != null && typeof obj['cloud'] === 'object') {\n const c = obj['cloud'] as Record<string, unknown>;\n const encryptedDeviceToken = typeof c['device_token_encrypted'] === 'string'\n ? unprotectCloudDeviceToken(c['device_token_encrypted'])\n : undefined;\n const deviceToken = typeof c['device_token'] === 'string' ? c['device_token'] : encryptedDeviceToken;\n cloud = {\n gateway_url: typeof c['gateway_url'] === 'string' ? c['gateway_url'] : '',\n ...(deviceToken ? { device_token: deviceToken } : {}),\n ...(typeof c['runtime_id'] === 'string' ? { runtime_id: c['runtime_id'] } : {}),\n ...(typeof c['runtime_name'] === 'string' ? { runtime_name: c['runtime_name'] } : {}),\n ...(typeof c['enabled'] === 'boolean' ? { enabled: c['enabled'] } : {}),\n };\n }\n\n // --- discord ---\n let discord: DaemonConfig['discord'] = undefined;\n if (obj['discord'] != null && typeof obj['discord'] === 'object') {\n const d = obj['discord'] as Record<string, unknown>;\n discord = {\n token: typeof d['token'] === 'string' ? d['token'] : '',\n guild_id: typeof d['guild_id'] === 'string' ? d['guild_id'] : '',\n owner_id: typeof d['owner_id'] === 'string' ? d['owner_id'] : '',\n ...(typeof d['dm_on_blocker'] === 'boolean' ? { dm_on_blocker: d['dm_on_blocker'] } : {}),\n ...(typeof d['control_channel_id'] === 'string' ? { control_channel_id: d['control_channel_id'] } : {}),\n };\n\n // Parse orchestrator sub-block\n if (d['orchestrator'] != null && typeof d['orchestrator'] === 'object') {\n const orc = d['orchestrator'] as Record<string, unknown>;\n discord.orchestrator = {\n ...(typeof orc['model'] === 'string' ? { model: orc['model'] } : {}),\n ...(typeof orc['max_tokens'] === 'number' && orc['max_tokens'] > 0 ? { max_tokens: orc['max_tokens'] } : {}),\n };\n }\n }\n\n // --- projects ---\n let scanRoots: string[] = [];\n if (obj['projects'] != null && typeof obj['projects'] === 'object') {\n const p = obj['projects'] as Record<string, unknown>;\n if (Array.isArray(p['scan_roots'])) {\n scanRoots = (p['scan_roots'] as unknown[])\n .filter((s): s is string => typeof s === 'string')\n .map(expandTilde);\n }\n }\n\n // --- log ---\n let logFile = def.log.file;\n let logLevel: LogLevel = def.log.level;\n let maxSizeMb = def.log.max_size_mb;\n\n if (obj['log'] != null && typeof obj['log'] === 'object') {\n const l = obj['log'] as Record<string, unknown>;\n if (typeof l['file'] === 'string') logFile = expandTilde(l['file']);\n if (typeof l['level'] === 'string') {\n logLevel = VALID_LOG_LEVELS.has(l['level']) ? (l['level'] as LogLevel) : 'info';\n }\n if (typeof l['max_size_mb'] === 'number' && l['max_size_mb'] > 0) {\n maxSizeMb = l['max_size_mb'];\n }\n }\n\n // --- env override: DISCORD_BOT_TOKEN ---\n const envToken = process.env['DISCORD_BOT_TOKEN'];\n if (envToken) {\n if (!discord) {\n discord = { token: envToken, guild_id: '', owner_id: '' };\n } else {\n discord = { ...discord, token: envToken };\n }\n }\n\n return {\n cloud,\n discord,\n projects: { scan_roots: scanRoots },\n log: { file: logFile, level: logLevel, max_size_mb: maxSizeMb },\n };\n}\n\n/**\n * Load and validate a DaemonConfig from a YAML file.\n * If the file doesn't exist, returns defaults. If the file is malformed YAML, throws.\n */\nexport function loadConfig(configPath: string): DaemonConfig {\n if (!existsSync(configPath)) {\n // Still apply env-var overrides even when file is missing\n return validateConfig(null);\n }\n\n const raw = readFileSync(configPath, 'utf-8');\n let parsed: unknown;\n try {\n parsed = parseYaml(raw);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse YAML config at ${configPath}: ${msg}`);\n }\n\n return validateConfig(parsed);\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface DeviceFlowResult {
|
|
2
|
+
deviceToken: string;
|
|
3
|
+
runtimeId: string;
|
|
4
|
+
/**
|
|
5
|
+
* Resolved relay/gateway URL: the server-returned `gateway_url` from the approved
|
|
6
|
+
* token-poll response when present AND valid, else the `gatewayUrl` the caller passed
|
|
7
|
+
* (D-02 backward-compat — existing single-host `--gateway` users are unaffected).
|
|
8
|
+
*/
|
|
9
|
+
gatewayUrl: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DeviceFlowParams {
|
|
12
|
+
gatewayUrl: string;
|
|
13
|
+
configPath: string;
|
|
14
|
+
runtimeName?: string;
|
|
15
|
+
binaryName: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Run the full RFC 8628 device authorization flow.
|
|
19
|
+
* Requests a device code, displays the verification URL and user code in the terminal,
|
|
20
|
+
* polls for approval with a spinner, and returns the device token on success.
|
|
21
|
+
* Calls process.exit(1) on denial (D-12) or timeout (D-11).
|
|
22
|
+
*/
|
|
23
|
+
export declare function runDeviceFlow(params: DeviceFlowParams): Promise<DeviceFlowResult>;
|
|
24
|
+
//# sourceMappingURL=device-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-flow.d.ts","sourceRoot":"","sources":["../src/device-flow.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA+HvF"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Device flow handler: code request, terminal display, polling loop, config save.
|
|
2
|
+
// Implements RFC 8628 device authorization grant for the `gsd-cloud login` command.
|
|
3
|
+
import { hostname as osHostname } from "node:os";
|
|
4
|
+
import { cursorTo, clearLine } from "node:readline";
|
|
5
|
+
import { parseCloudGatewayUrl, postJsonToValidatedGateway, validateGatewayNetworkTarget } from "./cloud-config.js";
|
|
6
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
7
|
+
const DEFAULT_POLL_INTERVAL_MS = 5_000;
|
|
8
|
+
const SLOW_DOWN_INCREMENT_MS = 5_000;
|
|
9
|
+
/** Simple sleep helper. */
|
|
10
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
11
|
+
/**
|
|
12
|
+
* Run the full RFC 8628 device authorization flow.
|
|
13
|
+
* Requests a device code, displays the verification URL and user code in the terminal,
|
|
14
|
+
* polls for approval with a spinner, and returns the device token on success.
|
|
15
|
+
* Calls process.exit(1) on denial (D-12) or timeout (D-11).
|
|
16
|
+
*/
|
|
17
|
+
export async function runDeviceFlow(params) {
|
|
18
|
+
const base = parseCloudGatewayUrl(params.gatewayUrl);
|
|
19
|
+
// ---- Step 1: Request device code (D-01, DEVF-01) ----
|
|
20
|
+
const codeUrl = new URL(base.pathname.replace(/\/+$/, "") + "/api/device/code", base);
|
|
21
|
+
const codeResponse = await postJsonToValidatedGateway(codeUrl, {
|
|
22
|
+
hostname: osHostname(),
|
|
23
|
+
os: process.platform,
|
|
24
|
+
...(params.runtimeName !== undefined ? { runtimeName: params.runtimeName } : {}),
|
|
25
|
+
});
|
|
26
|
+
const userCode = typeof codeResponse["userCode"] === "string" ? codeResponse["userCode"] : "";
|
|
27
|
+
const deviceCode = typeof codeResponse["deviceCode"] === "string" ? codeResponse["deviceCode"] : "";
|
|
28
|
+
const verificationUriComplete = typeof codeResponse["verificationUriComplete"] === "string"
|
|
29
|
+
? codeResponse["verificationUriComplete"]
|
|
30
|
+
: "";
|
|
31
|
+
const expiresIn = typeof codeResponse["expiresIn"] === "number" ? codeResponse["expiresIn"] : 600;
|
|
32
|
+
if (!userCode || !deviceCode) {
|
|
33
|
+
throw new Error("Device code response missing userCode or deviceCode");
|
|
34
|
+
}
|
|
35
|
+
// ---- Step 2: Display to user (D-02) ----
|
|
36
|
+
process.stdout.write(`\n${params.binaryName}: Cloud Login\n`);
|
|
37
|
+
process.stdout.write(`\nTo authorize this machine, open the following URL in your browser:\n`);
|
|
38
|
+
process.stdout.write(`\n ${verificationUriComplete}\n`);
|
|
39
|
+
process.stdout.write(`\nOr visit the gateway and enter the code manually:\n`);
|
|
40
|
+
process.stdout.write(`\n ${base.origin}\n`);
|
|
41
|
+
process.stdout.write(`\nYour code: ${userCode}\n\n`);
|
|
42
|
+
// ---- Step 3: Polling loop (D-10, D-11, D-12) ----
|
|
43
|
+
const tokenUrl = new URL(base.pathname.replace(/\/+$/, "") + "/api/device/token", base);
|
|
44
|
+
const expiresAt = Date.now() + expiresIn * 1_000;
|
|
45
|
+
let pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;
|
|
46
|
+
let spinnerIndex = 0;
|
|
47
|
+
const spinnerInterval = setInterval(() => {
|
|
48
|
+
const frame = SPINNER_FRAMES[spinnerIndex % SPINNER_FRAMES.length] ?? "⠋";
|
|
49
|
+
spinnerIndex++;
|
|
50
|
+
const remaining = Math.max(0, Math.ceil((expiresAt - Date.now()) / 1_000));
|
|
51
|
+
cursorTo(process.stdout, 0);
|
|
52
|
+
clearLine(process.stdout, 0);
|
|
53
|
+
process.stdout.write(` ${frame} Waiting for approval... (expires in ${remaining}s)`);
|
|
54
|
+
}, 100);
|
|
55
|
+
try {
|
|
56
|
+
while (Date.now() < expiresAt) {
|
|
57
|
+
await sleep(pollIntervalMs);
|
|
58
|
+
if (Date.now() >= expiresAt)
|
|
59
|
+
break;
|
|
60
|
+
let tokenResponse;
|
|
61
|
+
try {
|
|
62
|
+
tokenResponse = await postJsonToValidatedGateway(tokenUrl, { deviceCode });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Network errors during polling are transient — keep trying until expiry.
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const status = typeof tokenResponse["status"] === "string" ? tokenResponse["status"] : "";
|
|
69
|
+
if (status === "approved") {
|
|
70
|
+
clearInterval(spinnerInterval);
|
|
71
|
+
cursorTo(process.stdout, 0);
|
|
72
|
+
clearLine(process.stdout, 0);
|
|
73
|
+
process.stdout.write(`${params.binaryName}: Authorization approved!\n`);
|
|
74
|
+
const deviceToken = typeof tokenResponse["token"] === "string" ? tokenResponse["token"] : "";
|
|
75
|
+
const runtimeId = typeof tokenResponse["runtimeId"] === "string" ? tokenResponse["runtimeId"] : "";
|
|
76
|
+
if (!deviceToken || !runtimeId) {
|
|
77
|
+
throw new Error("Approval response missing token or runtimeId");
|
|
78
|
+
}
|
|
79
|
+
// Resolve the relay/gateway URL in exactly ONE place (D-02, D-03).
|
|
80
|
+
// Default to the caller-supplied gateway; if the server sent a valid
|
|
81
|
+
// `gateway_url`, use that instead. The server value is UNTRUSTED input —
|
|
82
|
+
// re-validate it through the same SSRF guards applied to `--gateway`.
|
|
83
|
+
let gatewayUrl = params.gatewayUrl;
|
|
84
|
+
const serverGatewayUrl = typeof tokenResponse["gateway_url"] === "string"
|
|
85
|
+
? tokenResponse["gateway_url"]
|
|
86
|
+
: undefined;
|
|
87
|
+
if (serverGatewayUrl) {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = parseCloudGatewayUrl(serverGatewayUrl);
|
|
90
|
+
validateGatewayNetworkTarget(parsed);
|
|
91
|
+
gatewayUrl = parsed.toString();
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// Fail loud-but-non-fatal: a bad server-supplied relay URL must not
|
|
95
|
+
// crash login. Warn and fall back to the configured `--gateway`.
|
|
96
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
97
|
+
process.stderr.write(`${params.binaryName}: ignoring invalid server-supplied relay URL (${reason}); using configured --gateway instead.\n`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { deviceToken, runtimeId, gatewayUrl };
|
|
101
|
+
}
|
|
102
|
+
if (status === "denied") {
|
|
103
|
+
clearInterval(spinnerInterval);
|
|
104
|
+
cursorTo(process.stdout, 0);
|
|
105
|
+
clearLine(process.stdout, 0);
|
|
106
|
+
process.stdout.write(`${params.binaryName}: Device request denied by user.\n`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
if (status === "expired") {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (status === "slow_down") {
|
|
113
|
+
pollIntervalMs += SLOW_DOWN_INCREMENT_MS;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// status === "pending" or unknown — keep polling
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
clearInterval(spinnerInterval);
|
|
121
|
+
}
|
|
122
|
+
// Timeout path (D-11)
|
|
123
|
+
cursorTo(process.stdout, 0);
|
|
124
|
+
clearLine(process.stdout, 0);
|
|
125
|
+
process.stdout.write(`${params.binaryName}: Authorization request timed out. Please run login again.\n`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=device-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-flow.js","sourceRoot":"","sources":["../src/device-flow.ts"],"names":[],"mappings":"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","sourcesContent":["// Device flow handler: code request, terminal display, polling loop, config save.\n// Implements RFC 8628 device authorization grant for the `gsd-cloud login` command.\nimport { hostname as osHostname } from \"node:os\";\nimport { cursorTo, clearLine } from \"node:readline\";\nimport { parseCloudGatewayUrl, postJsonToValidatedGateway, validateGatewayNetworkTarget } from \"./cloud-config.js\";\n\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\nconst SLOW_DOWN_INCREMENT_MS = 5_000;\n\nexport interface DeviceFlowResult {\n deviceToken: string;\n runtimeId: string;\n /**\n * Resolved relay/gateway URL: the server-returned `gateway_url` from the approved\n * token-poll response when present AND valid, else the `gatewayUrl` the caller passed\n * (D-02 backward-compat — existing single-host `--gateway` users are unaffected).\n */\n gatewayUrl: string;\n}\n\nexport interface DeviceFlowParams {\n gatewayUrl: string;\n configPath: string;\n runtimeName?: string;\n binaryName: string;\n}\n\n/** Simple sleep helper. */\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Run the full RFC 8628 device authorization flow.\n * Requests a device code, displays the verification URL and user code in the terminal,\n * polls for approval with a spinner, and returns the device token on success.\n * Calls process.exit(1) on denial (D-12) or timeout (D-11).\n */\nexport async function runDeviceFlow(params: DeviceFlowParams): Promise<DeviceFlowResult> {\n const base = parseCloudGatewayUrl(params.gatewayUrl);\n\n // ---- Step 1: Request device code (D-01, DEVF-01) ----\n const codeUrl = new URL(base.pathname.replace(/\\/+$/, \"\") + \"/api/device/code\", base);\n const codeResponse = await postJsonToValidatedGateway(codeUrl, {\n hostname: osHostname(),\n os: process.platform,\n ...(params.runtimeName !== undefined ? { runtimeName: params.runtimeName } : {}),\n });\n\n const userCode = typeof codeResponse[\"userCode\"] === \"string\" ? codeResponse[\"userCode\"] : \"\";\n const deviceCode = typeof codeResponse[\"deviceCode\"] === \"string\" ? codeResponse[\"deviceCode\"] : \"\";\n const verificationUriComplete = typeof codeResponse[\"verificationUriComplete\"] === \"string\"\n ? codeResponse[\"verificationUriComplete\"]\n : \"\";\n const expiresIn = typeof codeResponse[\"expiresIn\"] === \"number\" ? codeResponse[\"expiresIn\"] : 600;\n\n if (!userCode || !deviceCode) {\n throw new Error(\"Device code response missing userCode or deviceCode\");\n }\n\n // ---- Step 2: Display to user (D-02) ----\n process.stdout.write(`\\n${params.binaryName}: Cloud Login\\n`);\n process.stdout.write(`\\nTo authorize this machine, open the following URL in your browser:\\n`);\n process.stdout.write(`\\n ${verificationUriComplete}\\n`);\n process.stdout.write(`\\nOr visit the gateway and enter the code manually:\\n`);\n process.stdout.write(`\\n ${base.origin}\\n`);\n process.stdout.write(`\\nYour code: ${userCode}\\n\\n`);\n\n // ---- Step 3: Polling loop (D-10, D-11, D-12) ----\n const tokenUrl = new URL(base.pathname.replace(/\\/+$/, \"\") + \"/api/device/token\", base);\n const expiresAt = Date.now() + expiresIn * 1_000;\n let pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;\n let spinnerIndex = 0;\n\n const spinnerInterval = setInterval(() => {\n const frame = SPINNER_FRAMES[spinnerIndex % SPINNER_FRAMES.length] ?? \"⠋\";\n spinnerIndex++;\n const remaining = Math.max(0, Math.ceil((expiresAt - Date.now()) / 1_000));\n cursorTo(process.stdout, 0);\n clearLine(process.stdout, 0);\n process.stdout.write(` ${frame} Waiting for approval... (expires in ${remaining}s)`);\n }, 100);\n\n try {\n while (Date.now() < expiresAt) {\n await sleep(pollIntervalMs);\n\n if (Date.now() >= expiresAt) break;\n\n let tokenResponse: Record<string, unknown>;\n try {\n tokenResponse = await postJsonToValidatedGateway(tokenUrl, { deviceCode });\n } catch {\n // Network errors during polling are transient — keep trying until expiry.\n continue;\n }\n\n const status = typeof tokenResponse[\"status\"] === \"string\" ? tokenResponse[\"status\"] : \"\";\n\n if (status === \"approved\") {\n clearInterval(spinnerInterval);\n cursorTo(process.stdout, 0);\n clearLine(process.stdout, 0);\n process.stdout.write(`${params.binaryName}: Authorization approved!\\n`);\n\n const deviceToken = typeof tokenResponse[\"token\"] === \"string\" ? tokenResponse[\"token\"] : \"\";\n const runtimeId = typeof tokenResponse[\"runtimeId\"] === \"string\" ? tokenResponse[\"runtimeId\"] : \"\";\n if (!deviceToken || !runtimeId) {\n throw new Error(\"Approval response missing token or runtimeId\");\n }\n\n // Resolve the relay/gateway URL in exactly ONE place (D-02, D-03).\n // Default to the caller-supplied gateway; if the server sent a valid\n // `gateway_url`, use that instead. The server value is UNTRUSTED input —\n // re-validate it through the same SSRF guards applied to `--gateway`.\n let gatewayUrl = params.gatewayUrl;\n const serverGatewayUrl = typeof tokenResponse[\"gateway_url\"] === \"string\"\n ? tokenResponse[\"gateway_url\"]\n : undefined;\n if (serverGatewayUrl) {\n try {\n const parsed = parseCloudGatewayUrl(serverGatewayUrl);\n validateGatewayNetworkTarget(parsed);\n gatewayUrl = parsed.toString();\n } catch (err) {\n // Fail loud-but-non-fatal: a bad server-supplied relay URL must not\n // crash login. Warn and fall back to the configured `--gateway`.\n const reason = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `${params.binaryName}: ignoring invalid server-supplied relay URL (${reason}); using configured --gateway instead.\\n`,\n );\n }\n }\n\n return { deviceToken, runtimeId, gatewayUrl };\n }\n\n if (status === \"denied\") {\n clearInterval(spinnerInterval);\n cursorTo(process.stdout, 0);\n clearLine(process.stdout, 0);\n process.stdout.write(`${params.binaryName}: Device request denied by user.\\n`);\n process.exit(1);\n }\n\n if (status === \"expired\") {\n break;\n }\n\n if (status === \"slow_down\") {\n pollIntervalMs += SLOW_DOWN_INCREMENT_MS;\n continue;\n }\n\n // status === \"pending\" or unknown — keep polling\n }\n } finally {\n clearInterval(spinnerInterval);\n }\n\n // Timeout path (D-11)\n cursorTo(process.stdout, 0);\n clearLine(process.stdout, 0);\n process.stdout.write(`${params.binaryName}: Authorization request timed out. Please run login again.\\n`);\n process.exit(1);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-flow.test.d.ts","sourceRoot":"","sources":["../src/device-flow.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Project/App: Open GSD
|
|
2
|
+
// File Purpose: Drive runDeviceFlow against a loopback HTTP server to cover the RFC 8628
|
|
3
|
+
// poll state machine (pending→approved), the missing-token guard, and re-validation of the
|
|
4
|
+
// UNTRUSTED server-supplied gateway_url (valid wins / invalid falls back without crashing).
|
|
5
|
+
//
|
|
6
|
+
// The denied and expired paths are intentionally NOT tested here: runDeviceFlow calls
|
|
7
|
+
// process.exit(1) on both (D-11/D-12), which an in-process node:test cannot survive.
|
|
8
|
+
import { test } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { createServer } from "node:http";
|
|
11
|
+
import { runDeviceFlow } from "./device-flow.js";
|
|
12
|
+
import { parseCloudGatewayUrl } from "./cloud-config.js";
|
|
13
|
+
// runDeviceFlow requires a configPath but never reads it (saving is the caller's job),
|
|
14
|
+
// so a constant avoids pointless per-test tmp-dir I/O.
|
|
15
|
+
const CONFIG_PATH = "unused-by-runDeviceFlow.yaml";
|
|
16
|
+
/**
|
|
17
|
+
* Stand up a loopback HTTP server implementing both device-flow endpoints. Counts token
|
|
18
|
+
* POSTs so the poll loop's progression can be asserted; `tokenResponder` shapes each reply.
|
|
19
|
+
*/
|
|
20
|
+
function startDeviceServer(tokenResponder) {
|
|
21
|
+
let tokenCalls = 0;
|
|
22
|
+
const server = createServer((req, res) => {
|
|
23
|
+
req.setEncoding("utf8");
|
|
24
|
+
req.on("data", () => { });
|
|
25
|
+
req.on("end", () => {
|
|
26
|
+
if (req.url === "/api/device/code") {
|
|
27
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
28
|
+
res.end(JSON.stringify({
|
|
29
|
+
userCode: "ABCD-1234",
|
|
30
|
+
deviceCode: "device-code-fixture",
|
|
31
|
+
verificationUriComplete: "https://example.test/verify",
|
|
32
|
+
// Short-lived so the timeout path is bounded if a test ever fails to approve.
|
|
33
|
+
expiresIn: 30,
|
|
34
|
+
}));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (req.url === "/api/device/token") {
|
|
38
|
+
tokenCalls += 1;
|
|
39
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
40
|
+
res.end(JSON.stringify(tokenResponder(tokenCalls)));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
44
|
+
res.end(JSON.stringify({ error: "not found" }));
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
server.once("error", reject);
|
|
49
|
+
server.listen(0, "127.0.0.1", () => {
|
|
50
|
+
const address = server.address();
|
|
51
|
+
if (!address || typeof address !== "object") {
|
|
52
|
+
reject(new Error("failed to bind loopback test server"));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
resolve({ server, baseUrl: `http://127.0.0.1:${address.port}`, tokenCalls: () => tokenCalls });
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function closeServer(server) {
|
|
60
|
+
return new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())));
|
|
61
|
+
}
|
|
62
|
+
/** Silence spinner stdout noise and capture stderr so the invalid-URL warning can be asserted. */
|
|
63
|
+
function silenceOutput() {
|
|
64
|
+
const originalOut = process.stdout.write.bind(process.stdout);
|
|
65
|
+
const originalErr = process.stderr.write.bind(process.stderr);
|
|
66
|
+
let capturedErr = "";
|
|
67
|
+
process.stdout.write = (() => true);
|
|
68
|
+
process.stderr.write = ((chunk) => {
|
|
69
|
+
capturedErr += typeof chunk === "string" ? chunk : String(chunk);
|
|
70
|
+
return true;
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
restore: () => {
|
|
74
|
+
process.stdout.write = originalOut;
|
|
75
|
+
process.stderr.write = originalErr;
|
|
76
|
+
},
|
|
77
|
+
stderr: () => capturedErr,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Run the flow against a server, always silencing output and closing the server. */
|
|
81
|
+
async function drive(tokenResponder, assertResult, t) {
|
|
82
|
+
let started;
|
|
83
|
+
try {
|
|
84
|
+
started = await startDeviceServer(tokenResponder);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if (err.code === "EPERM") {
|
|
88
|
+
t.skip("loopback listen is blocked in this sandbox");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
const out = silenceOutput();
|
|
94
|
+
try {
|
|
95
|
+
const result = await runDeviceFlow({
|
|
96
|
+
gatewayUrl: started.baseUrl,
|
|
97
|
+
configPath: CONFIG_PATH,
|
|
98
|
+
binaryName: "gsd",
|
|
99
|
+
});
|
|
100
|
+
out.restore();
|
|
101
|
+
assertResult(result, { baseUrl: started.baseUrl, tokenCalls: started.tokenCalls, stderr: out.stderr() });
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
out.restore();
|
|
105
|
+
await closeServer(started.server);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
test("immediate approval resolves with token, runtimeId, and the passed --gateway", async (t) => {
|
|
109
|
+
await drive(() => ({ status: "approved", token: "tok", runtimeId: "rt" }), (result, ctx) => {
|
|
110
|
+
assert.equal(result.deviceToken, "tok");
|
|
111
|
+
assert.equal(result.runtimeId, "rt");
|
|
112
|
+
assert.equal(result.gatewayUrl, ctx.baseUrl);
|
|
113
|
+
}, t);
|
|
114
|
+
});
|
|
115
|
+
test("pending then approved keeps polling until approval", async (t) => {
|
|
116
|
+
await drive((call) => (call < 2 ? { status: "pending" } : { status: "approved", token: "tok", runtimeId: "rt" }), (result, ctx) => {
|
|
117
|
+
assert.equal(result.deviceToken, "tok");
|
|
118
|
+
assert.ok(ctx.tokenCalls() >= 2, `expected >=2 token polls, saw ${ctx.tokenCalls()}`);
|
|
119
|
+
}, t);
|
|
120
|
+
});
|
|
121
|
+
test("a VALID server-supplied gateway_url wins over the passed --gateway", async (t) => {
|
|
122
|
+
const serverGateway = "https://relay.opengsd.net";
|
|
123
|
+
await drive(() => ({ status: "approved", token: "tok", runtimeId: "rt", gateway_url: serverGateway }), (result) => {
|
|
124
|
+
// Mirror the code's own resolution: parse + toString normalization.
|
|
125
|
+
assert.equal(result.gatewayUrl, parseCloudGatewayUrl(serverGateway).toString());
|
|
126
|
+
}, t);
|
|
127
|
+
});
|
|
128
|
+
test("an INVALID server-supplied gateway_url falls back to --gateway without throwing", async (t) => {
|
|
129
|
+
await drive(() => ({ status: "approved", token: "tok", runtimeId: "rt", gateway_url: "https://169.254.169.254" }), (result, ctx) => {
|
|
130
|
+
assert.equal(result.gatewayUrl, ctx.baseUrl);
|
|
131
|
+
assert.match(ctx.stderr, /ignoring invalid server-supplied relay URL/);
|
|
132
|
+
}, t);
|
|
133
|
+
});
|
|
134
|
+
test("approval missing token/runtimeId rejects", async (t) => {
|
|
135
|
+
let started;
|
|
136
|
+
try {
|
|
137
|
+
started = await startDeviceServer(() => ({ status: "approved", runtimeId: "rt" }));
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
if (err.code === "EPERM") {
|
|
141
|
+
t.skip("loopback listen is blocked in this sandbox");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
const out = silenceOutput();
|
|
147
|
+
try {
|
|
148
|
+
await assert.rejects(runDeviceFlow({ gatewayUrl: started.baseUrl, configPath: CONFIG_PATH, binaryName: "gsd" }), /missing token or runtimeId/);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
out.restore();
|
|
152
|
+
await closeServer(started.server);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
//# sourceMappingURL=device-flow.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-flow.test.js","sourceRoot":"","sources":["../src/device-flow.test.ts"],"names":[],"mappings":"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","sourcesContent":["// Project/App: Open GSD\n// File Purpose: Drive runDeviceFlow against a loopback HTTP server to cover the RFC 8628\n// poll state machine (pending→approved), the missing-token guard, and re-validation of the\n// UNTRUSTED server-supplied gateway_url (valid wins / invalid falls back without crashing).\n//\n// The denied and expired paths are intentionally NOT tested here: runDeviceFlow calls\n// process.exit(1) on both (D-11/D-12), which an in-process node:test cannot survive.\nimport { test } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { createServer, type Server } from \"node:http\";\nimport { runDeviceFlow } from \"./device-flow.js\";\nimport { parseCloudGatewayUrl } from \"./cloud-config.js\";\n\n// runDeviceFlow requires a configPath but never reads it (saving is the caller's job),\n// so a constant avoids pointless per-test tmp-dir I/O.\nconst CONFIG_PATH = \"unused-by-runDeviceFlow.yaml\";\n\n/** Returns the JSON body /api/device/token should answer with on POST number `callCount` (1-based). */\ntype TokenResponder = (callCount: number) => Record<string, unknown>;\n\n/**\n * Stand up a loopback HTTP server implementing both device-flow endpoints. Counts token\n * POSTs so the poll loop's progression can be asserted; `tokenResponder` shapes each reply.\n */\nfunction startDeviceServer(tokenResponder: TokenResponder): Promise<{ server: Server; baseUrl: string; tokenCalls: () => number }> {\n let tokenCalls = 0;\n const server = createServer((req, res) => {\n req.setEncoding(\"utf8\");\n req.on(\"data\", () => { /* body drained; contents are not asserted */ });\n req.on(\"end\", () => {\n if (req.url === \"/api/device/code\") {\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({\n userCode: \"ABCD-1234\",\n deviceCode: \"device-code-fixture\",\n verificationUriComplete: \"https://example.test/verify\",\n // Short-lived so the timeout path is bounded if a test ever fails to approve.\n expiresIn: 30,\n }));\n return;\n }\n if (req.url === \"/api/device/token\") {\n tokenCalls += 1;\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(tokenResponder(tokenCalls)));\n return;\n }\n res.writeHead(404, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"not found\" }));\n });\n });\n\n return new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n if (!address || typeof address !== \"object\") {\n reject(new Error(\"failed to bind loopback test server\"));\n return;\n }\n resolve({ server, baseUrl: `http://127.0.0.1:${address.port}`, tokenCalls: () => tokenCalls });\n });\n });\n}\n\nfunction closeServer(server: Server): Promise<void> {\n return new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())));\n}\n\n/** Silence spinner stdout noise and capture stderr so the invalid-URL warning can be asserted. */\nfunction silenceOutput(): { restore: () => void; stderr: () => string } {\n const originalOut = process.stdout.write.bind(process.stdout);\n const originalErr = process.stderr.write.bind(process.stderr);\n let capturedErr = \"\";\n (process.stdout as unknown as { write: typeof process.stdout.write }).write = (() => true) as typeof process.stdout.write;\n (process.stderr as unknown as { write: typeof process.stderr.write }).write = ((chunk: unknown) => {\n capturedErr += typeof chunk === \"string\" ? chunk : String(chunk);\n return true;\n }) as typeof process.stderr.write;\n return {\n restore: () => {\n (process.stdout as unknown as { write: typeof process.stdout.write }).write = originalOut;\n (process.stderr as unknown as { write: typeof process.stderr.write }).write = originalErr;\n },\n stderr: () => capturedErr,\n };\n}\n\n/** Run the flow against a server, always silencing output and closing the server. */\nasync function drive(\n tokenResponder: TokenResponder,\n assertResult: (result: Awaited<ReturnType<typeof runDeviceFlow>>, ctx: { baseUrl: string; tokenCalls: () => number; stderr: string }) => void,\n t: { skip: (msg: string) => void },\n): Promise<void> {\n let started: { server: Server; baseUrl: string; tokenCalls: () => number };\n try {\n started = await startDeviceServer(tokenResponder);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EPERM\") {\n t.skip(\"loopback listen is blocked in this sandbox\");\n return;\n }\n throw err;\n }\n const out = silenceOutput();\n try {\n const result = await runDeviceFlow({\n gatewayUrl: started.baseUrl,\n configPath: CONFIG_PATH,\n binaryName: \"gsd\",\n });\n out.restore();\n assertResult(result, { baseUrl: started.baseUrl, tokenCalls: started.tokenCalls, stderr: out.stderr() });\n } finally {\n out.restore();\n await closeServer(started.server);\n }\n}\n\ntest(\"immediate approval resolves with token, runtimeId, and the passed --gateway\", async (t) => {\n await drive(\n () => ({ status: \"approved\", token: \"tok\", runtimeId: \"rt\" }),\n (result, ctx) => {\n assert.equal(result.deviceToken, \"tok\");\n assert.equal(result.runtimeId, \"rt\");\n assert.equal(result.gatewayUrl, ctx.baseUrl);\n },\n t,\n );\n});\n\ntest(\"pending then approved keeps polling until approval\", async (t) => {\n await drive(\n (call) => (call < 2 ? { status: \"pending\" } : { status: \"approved\", token: \"tok\", runtimeId: \"rt\" }),\n (result, ctx) => {\n assert.equal(result.deviceToken, \"tok\");\n assert.ok(ctx.tokenCalls() >= 2, `expected >=2 token polls, saw ${ctx.tokenCalls()}`);\n },\n t,\n );\n});\n\ntest(\"a VALID server-supplied gateway_url wins over the passed --gateway\", async (t) => {\n const serverGateway = \"https://relay.opengsd.net\";\n await drive(\n () => ({ status: \"approved\", token: \"tok\", runtimeId: \"rt\", gateway_url: serverGateway }),\n (result) => {\n // Mirror the code's own resolution: parse + toString normalization.\n assert.equal(result.gatewayUrl, parseCloudGatewayUrl(serverGateway).toString());\n },\n t,\n );\n});\n\ntest(\"an INVALID server-supplied gateway_url falls back to --gateway without throwing\", async (t) => {\n await drive(\n () => ({ status: \"approved\", token: \"tok\", runtimeId: \"rt\", gateway_url: \"https://169.254.169.254\" }),\n (result, ctx) => {\n assert.equal(result.gatewayUrl, ctx.baseUrl);\n assert.match(ctx.stderr, /ignoring invalid server-supplied relay URL/);\n },\n t,\n );\n});\n\ntest(\"approval missing token/runtimeId rejects\", async (t) => {\n let started: { server: Server; baseUrl: string; tokenCalls: () => number };\n try {\n started = await startDeviceServer(() => ({ status: \"approved\", runtimeId: \"rt\" }));\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EPERM\") {\n t.skip(\"loopback listen is blocked in this sandbox\");\n return;\n }\n throw err;\n }\n const out = silenceOutput();\n try {\n await assert.rejects(\n runDeviceFlow({ gatewayUrl: started.baseUrl, configPath: CONFIG_PATH, binaryName: \"gsd\" }),\n /missing token or runtimeId/,\n );\n } finally {\n out.restore();\n await closeServer(started.server);\n }\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-executor.d.ts","sourceRoot":"","sources":["../../src/executors/claude-executor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEjE,qBAAa,cAAe,YAAW,QAAQ;IAC7C,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAI3B,kBAAkB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;CAGnD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Project/App: Open GSD
|
|
2
|
+
// File Purpose: Placeholder Executor adapter for the Claude CLI (`claude -p`).
|
|
3
|
+
//
|
|
4
|
+
// TODO: Implement by driving `claude -p` headlessly. The behaviour is
|
|
5
|
+
// intentionally NOT invented here — how claude exposes GSD workflow tools, the
|
|
6
|
+
// print-mode invocation shape, and result mapping must be designed first.
|
|
7
|
+
export class ClaudeExecutor {
|
|
8
|
+
execute() {
|
|
9
|
+
throw new Error("claude executor not yet implemented");
|
|
10
|
+
}
|
|
11
|
+
advertisedProjects() {
|
|
12
|
+
throw new Error("claude executor not yet implemented");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=claude-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-executor.js","sourceRoot":"","sources":["../../src/executors/claude-executor.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,+EAA+E;AAC/E,EAAE;AACF,sEAAsE;AACtE,+EAA+E;AAC/E,0EAA0E;AAI1E,MAAM,OAAO,cAAc;IACzB,OAAO;QACL,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;IAED,kBAAkB;QAChB,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;CACF","sourcesContent":["// Project/App: Open GSD\n// File Purpose: Placeholder Executor adapter for the Claude CLI (`claude -p`).\n//\n// TODO: Implement by driving `claude -p` headlessly. The behaviour is\n// intentionally NOT invented here — how claude exposes GSD workflow tools, the\n// print-mode invocation shape, and result mapping must be designed first.\n\nimport type { AdvertisedProject, Executor } from \"./executor.js\";\n\nexport class ClaudeExecutor implements Executor {\n execute(): Promise<unknown> {\n throw new Error(\"claude executor not yet implemented\");\n }\n\n advertisedProjects(): Promise<AdvertisedProject[]> {\n throw new Error(\"claude executor not yet implemented\");\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-executor.d.ts","sourceRoot":"","sources":["../../src/executors/codex-executor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEjE,qBAAa,aAAc,YAAW,QAAQ;IAC5C,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAI3B,kBAAkB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;CAGnD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Project/App: Open GSD
|
|
2
|
+
// File Purpose: Placeholder Executor adapter for OpenAI Codex CLI.
|
|
3
|
+
//
|
|
4
|
+
// TODO: Implement by driving `codex` headlessly. The behaviour is intentionally
|
|
5
|
+
// NOT invented here — the wiring (how codex exposes GSD workflow tools, its
|
|
6
|
+
// invocation shape, and result mapping) must be designed before this ships.
|
|
7
|
+
export class CodexExecutor {
|
|
8
|
+
execute() {
|
|
9
|
+
throw new Error("codex executor not yet implemented");
|
|
10
|
+
}
|
|
11
|
+
advertisedProjects() {
|
|
12
|
+
throw new Error("codex executor not yet implemented");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=codex-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-executor.js","sourceRoot":"","sources":["../../src/executors/codex-executor.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,mEAAmE;AACnE,EAAE;AACF,gFAAgF;AAChF,4EAA4E;AAC5E,4EAA4E;AAI5E,MAAM,OAAO,aAAa;IACxB,OAAO;QACL,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,kBAAkB;QAChB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;CACF","sourcesContent":["// Project/App: Open GSD\n// File Purpose: Placeholder Executor adapter for OpenAI Codex CLI.\n//\n// TODO: Implement by driving `codex` headlessly. The behaviour is intentionally\n// NOT invented here — the wiring (how codex exposes GSD workflow tools, its\n// invocation shape, and result mapping) must be designed before this ships.\n\nimport type { AdvertisedProject, Executor } from \"./executor.js\";\n\nexport class CodexExecutor implements Executor {\n execute(): Promise<unknown> {\n throw new Error(\"codex executor not yet implemented\");\n }\n\n advertisedProjects(): Promise<AdvertisedProject[]> {\n throw new Error(\"codex executor not yet implemented\");\n }\n}\n"]}
|