@wrongstack/cli 0.295.0 → 0.296.2
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/data/providers.json +0 -24
- package/dist/acp-A5OSIU2P.js +824 -0
- package/dist/acp-A5OSIU2P.js.map +7 -0
- package/dist/acp-server-agent.d.ts.map +1 -1
- package/dist/arg-parser.d.ts.map +1 -1
- package/dist/audit-2ZSFGNIF.js +102 -0
- package/dist/audit-2ZSFGNIF.js.map +7 -0
- package/dist/auth-CJWQJVRH.js +504 -0
- package/dist/auth-CJWQJVRH.js.map +7 -0
- package/dist/auth-menu/auth-menu-audit.d.ts +1 -5
- package/dist/auth-menu/auth-menu-audit.d.ts.map +1 -1
- package/dist/auth-menu/local.d.ts +1 -1
- package/dist/auth-menu/local.d.ts.map +1 -1
- package/dist/auth-menu/shared.d.ts +1 -1
- package/dist/auth-menu/shared.d.ts.map +1 -1
- package/dist/bench-7ZDWSNEF.js +278 -0
- package/dist/bench-7ZDWSNEF.js.map +7 -0
- package/dist/boot/brain-decision-log.d.ts +7 -5
- package/dist/boot/brain-decision-log.d.ts.map +1 -1
- package/dist/boot/dispatch-webui.d.ts +4 -8
- package/dist/boot/dispatch-webui.d.ts.map +1 -1
- package/dist/boot/launch-menu.d.ts +1 -1
- package/dist/boot/launch-menu.d.ts.map +1 -1
- package/dist/boot/system-prompt-builder.d.ts +4 -1
- package/dist/boot/system-prompt-builder.d.ts.map +1 -1
- package/dist/boot/tui-coordinator-callbacks.d.ts +45 -0
- package/dist/boot/tui-coordinator-callbacks.d.ts.map +1 -0
- package/dist/boot/tui-coordinator-setup.d.ts +2 -2
- package/dist/boot/tui-coordinator-setup.d.ts.map +1 -1
- package/dist/boot/tui-project-picker-callback.d.ts +1 -1
- package/dist/boot/tui-project-picker-callback.d.ts.map +1 -1
- package/dist/boot/tui-project-switch.d.ts +3 -4
- package/dist/boot/tui-project-switch.d.ts.map +1 -1
- package/dist/boot/tui-runtime-state.d.ts +5 -6
- package/dist/boot/tui-runtime-state.d.ts.map +1 -1
- package/dist/boot/tui-session-resume.d.ts.map +1 -1
- package/dist/boot/tui-settings-adapter.d.ts +2 -3
- package/dist/boot/tui-settings-adapter.d.ts.map +1 -1
- package/dist/boot-config.d.ts +2 -2
- package/dist/boot-config.d.ts.map +1 -1
- package/dist/boot.d.ts +1 -1
- package/dist/boot.d.ts.map +1 -1
- package/dist/brain-menu/panel-service.d.ts.map +1 -1
- package/dist/chimera-review-task.d.ts +6 -0
- package/dist/chimera-review-task.d.ts.map +1 -0
- package/dist/chimera-reviewer-policy.d.ts +34 -0
- package/dist/chimera-reviewer-policy.d.ts.map +1 -0
- package/dist/chronicle-QQEQFSSE.js +256 -0
- package/dist/chronicle-QQEQFSSE.js.map +7 -0
- package/dist/chunk-5N34EWJR.js +418 -0
- package/dist/chunk-5N34EWJR.js.map +7 -0
- package/dist/chunk-66T43Q4Y.js +90 -0
- package/dist/chunk-66T43Q4Y.js.map +7 -0
- package/dist/chunk-7OCVIDC7.js +11 -0
- package/dist/chunk-7OCVIDC7.js.map +7 -0
- package/dist/chunk-ABXK4VVY.js +84 -0
- package/dist/chunk-ABXK4VVY.js.map +7 -0
- package/dist/chunk-AXVZMEWG.js +306 -0
- package/dist/chunk-AXVZMEWG.js.map +7 -0
- package/dist/chunk-AZESNNIC.js +216 -0
- package/dist/chunk-AZESNNIC.js.map +7 -0
- package/dist/chunk-BGFM3PD7.js +41 -0
- package/dist/chunk-BGFM3PD7.js.map +7 -0
- package/dist/chunk-DPJZQCVP.js +13 -0
- package/dist/chunk-DPJZQCVP.js.map +7 -0
- package/dist/chunk-F4PEGAJZ.js +176 -0
- package/dist/chunk-F4PEGAJZ.js.map +7 -0
- package/dist/chunk-IHBYBYGA.js +443 -0
- package/dist/chunk-IHBYBYGA.js.map +7 -0
- package/dist/chunk-IN4GXGYW.js +425 -0
- package/dist/chunk-IN4GXGYW.js.map +7 -0
- package/dist/chunk-JBSQ7ONN.js +1023 -0
- package/dist/chunk-JBSQ7ONN.js.map +7 -0
- package/dist/chunk-JBTFWWKV.js +1325 -0
- package/dist/chunk-JBTFWWKV.js.map +7 -0
- package/dist/chunk-JUHRI6OV.js +100 -0
- package/dist/chunk-JUHRI6OV.js.map +7 -0
- package/dist/chunk-OP47B3IX.js +142 -0
- package/dist/chunk-OP47B3IX.js.map +7 -0
- package/dist/chunk-PJHET45A.js +158 -0
- package/dist/chunk-PJHET45A.js.map +7 -0
- package/dist/chunk-Q5GTM25S.js +17 -0
- package/dist/chunk-Q5GTM25S.js.map +7 -0
- package/dist/chunk-QD544J2B.js +19 -0
- package/dist/chunk-QD544J2B.js.map +7 -0
- package/dist/chunk-QUNHS6O3.js +258 -0
- package/dist/chunk-QUNHS6O3.js.map +7 -0
- package/dist/chunk-TADVX644.js +480 -0
- package/dist/chunk-TADVX644.js.map +7 -0
- package/dist/chunk-TGDHN4LM.js +81 -0
- package/dist/chunk-TGDHN4LM.js.map +7 -0
- package/dist/chunk-TYO2OVAD.js +50 -0
- package/dist/chunk-TYO2OVAD.js.map +7 -0
- package/dist/chunk-VCYZ3WZV.js +14 -0
- package/dist/chunk-VCYZ3WZV.js.map +7 -0
- package/dist/chunk-VJUEZZYB.js +29 -0
- package/dist/chunk-VJUEZZYB.js.map +7 -0
- package/dist/chunk-WHOIR577.js +1 -0
- package/dist/chunk-WHOIR577.js.map +7 -0
- package/dist/chunk-WY7KKOUK.js +460 -0
- package/dist/chunk-WY7KKOUK.js.map +7 -0
- package/dist/chunk-X5DQTPXT.js +3059 -0
- package/dist/chunk-X5DQTPXT.js.map +7 -0
- package/dist/chunk-XJXDOF63.js +28 -0
- package/dist/chunk-XJXDOF63.js.map +7 -0
- package/dist/chunk-XWEGWYDU.js +560 -0
- package/dist/chunk-XWEGWYDU.js.map +7 -0
- package/dist/chunk-YWUPHRGB.js +346 -0
- package/dist/chunk-YWUPHRGB.js.map +7 -0
- package/dist/chunk-Z7YGASQP.js +313 -0
- package/dist/chunk-Z7YGASQP.js.map +7 -0
- package/dist/cli-entry-main.d.ts +2 -0
- package/dist/cli-entry-main.d.ts.map +1 -0
- package/dist/cli-entry-point.d.ts +3 -3
- package/dist/cli-entry-point.d.ts.map +1 -1
- package/dist/cli-eternal-flag.d.ts +2 -2
- package/dist/cli-eternal-flag.d.ts.map +1 -1
- package/dist/cli-main-6AU6E4XZ.js +29126 -0
- package/dist/cli-main-6AU6E4XZ.js.map +7 -0
- package/dist/cli-main-helpers.d.ts +21 -0
- package/dist/cli-main-helpers.d.ts.map +1 -0
- package/dist/cli-main.d.ts +25 -1
- package/dist/cli-main.d.ts.map +1 -1
- package/dist/context-limit.d.ts +48 -0
- package/dist/context-limit.d.ts.map +1 -1
- package/dist/diag-doctor-OQ3AA63O.js +170 -0
- package/dist/diag-doctor-OQ3AA63O.js.map +7 -0
- package/dist/execute-deps.d.ts +10 -12
- package/dist/execute-deps.d.ts.map +1 -1
- package/dist/execution-V26SE52F.js +4656 -0
- package/dist/execution-V26SE52F.js.map +7 -0
- package/dist/execution-chimera-ask.d.ts +10 -0
- package/dist/execution-chimera-ask.d.ts.map +1 -0
- package/dist/execution-chimera-cascade.d.ts +15 -0
- package/dist/execution-chimera-cascade.d.ts.map +1 -0
- package/dist/execution-cleanup.d.ts +23 -0
- package/dist/execution-cleanup.d.ts.map +1 -0
- package/dist/execution-kanban-dispatch.d.ts +10 -0
- package/dist/execution-kanban-dispatch.d.ts.map +1 -0
- package/dist/execution-repl-fleet-callbacks.d.ts +11 -0
- package/dist/execution-repl-fleet-callbacks.d.ts.map +1 -0
- package/dist/execution-storage-observability.d.ts +3 -0
- package/dist/execution-storage-observability.d.ts.map +1 -0
- package/dist/execution-tui-next-step-callbacks.d.ts +20 -0
- package/dist/execution-tui-next-step-callbacks.d.ts.map +1 -0
- package/dist/execution.d.ts +2 -139
- package/dist/execution.d.ts.map +1 -1
- package/dist/export-TPORYVRZ.js +64 -0
- package/dist/export-TPORYVRZ.js.map +7 -0
- package/dist/fleet/host-acp-runner-cache.d.ts +9 -0
- package/dist/fleet/host-acp-runner-cache.d.ts.map +1 -0
- package/dist/fleet/host-acp.d.ts +3 -0
- package/dist/fleet/host-acp.d.ts.map +1 -0
- package/dist/fleet/host-concurrency.d.ts +2 -0
- package/dist/fleet/host-concurrency.d.ts.map +1 -0
- package/dist/fleet/host-context.d.ts +5 -0
- package/dist/fleet/host-context.d.ts.map +1 -0
- package/dist/fleet/host-director-builder.d.ts +24 -0
- package/dist/fleet/host-director-builder.d.ts.map +1 -0
- package/dist/fleet/host-director-event-bridges.d.ts +37 -0
- package/dist/fleet/host-director-event-bridges.d.ts.map +1 -0
- package/dist/fleet/host-director-services.d.ts +20 -0
- package/dist/fleet/host-director-services.d.ts.map +1 -0
- package/dist/fleet/host-event-bridge.d.ts +17 -0
- package/dist/fleet/host-event-bridge.d.ts.map +1 -0
- package/dist/fleet/host-helpers.d.ts +22 -0
- package/dist/fleet/host-helpers.d.ts.map +1 -0
- package/dist/fleet/host-learning-tracker.d.ts +11 -0
- package/dist/fleet/host-learning-tracker.d.ts.map +1 -0
- package/dist/fleet/host-learning.d.ts +4 -0
- package/dist/fleet/host-learning.d.ts.map +1 -0
- package/dist/fleet/host-lifecycle-events.d.ts +4 -0
- package/dist/fleet/host-lifecycle-events.d.ts.map +1 -0
- package/dist/fleet/host-paths.d.ts +3 -0
- package/dist/fleet/host-paths.d.ts.map +1 -0
- package/dist/fleet/host-provider.d.ts +22 -0
- package/dist/fleet/host-provider.d.ts.map +1 -0
- package/dist/fleet/host-session-writer.d.ts +3 -0
- package/dist/fleet/host-session-writer.d.ts.map +1 -0
- package/dist/fleet/host-shadow-pass.d.ts +21 -0
- package/dist/fleet/host-shadow-pass.d.ts.map +1 -0
- package/dist/fleet/host-spawn-types.d.ts +25 -0
- package/dist/fleet/host-spawn-types.d.ts.map +1 -0
- package/dist/fleet/host-status.d.ts +48 -0
- package/dist/fleet/host-status.d.ts.map +1 -0
- package/dist/fleet/host-subagent-factory.d.ts +16 -0
- package/dist/fleet/host-subagent-factory.d.ts.map +1 -0
- package/dist/fleet/host-supervisor.d.ts +16 -0
- package/dist/fleet/host-supervisor.d.ts.map +1 -0
- package/dist/fleet/host-task-result-report.d.ts +11 -0
- package/dist/fleet/host-task-result-report.d.ts.map +1 -0
- package/dist/fleet/host-types.d.ts +89 -0
- package/dist/fleet/host-types.d.ts.map +1 -0
- package/dist/fleet/host.d.ts +10 -357
- package/dist/fleet/host.d.ts.map +1 -1
- package/dist/fleet/routing.d.ts +1 -1
- package/dist/fleet/routing.d.ts.map +1 -1
- package/dist/fleet/status-broadcast.d.ts.map +1 -1
- package/dist/goal-host.d.ts +2 -2
- package/dist/goal-host.d.ts.map +1 -1
- package/dist/hq-BECGICEZ.js +547 -0
- package/dist/hq-BECGICEZ.js.map +7 -0
- package/dist/hq-command-controller.d.ts +2 -2
- package/dist/hq-command-controller.d.ts.map +1 -1
- package/dist/hq-publisher.d.ts +2 -2
- package/dist/hq-publisher.d.ts.map +1 -1
- package/dist/hq-server/auth-state.d.ts +1 -1
- package/dist/hq-server/auth-state.d.ts.map +1 -1
- package/dist/hq-server/project-root.d.ts +5 -0
- package/dist/hq-server/project-root.d.ts.map +1 -0
- package/dist/hq-server/routes/auth-handlers.d.ts +32 -0
- package/dist/hq-server/routes/auth-handlers.d.ts.map +1 -0
- package/dist/hq-server/routes/session-handlers.d.ts +8 -0
- package/dist/hq-server/routes/session-handlers.d.ts.map +1 -0
- package/dist/hq-server/routes/system-handlers.d.ts +16 -0
- package/dist/hq-server/routes/system-handlers.d.ts.map +1 -0
- package/dist/hq-server/routes.d.ts +6 -6
- package/dist/hq-server/routes.d.ts.map +1 -1
- package/dist/hq-server/ws.d.ts +1 -1
- package/dist/hq-server/ws.d.ts.map +1 -1
- package/dist/hq-server-FJOVOERO.js +39 -0
- package/dist/hq-server-FJOVOERO.js.map +7 -0
- package/dist/hq-server.d.ts.map +1 -1
- package/dist/hq-ttl-U2FAYFAT.js +8 -0
- package/dist/hq-ttl-U2FAYFAT.js.map +7 -0
- package/dist/hq-tunnel-YZLRONPV.js +111 -0
- package/dist/hq-tunnel-YZLRONPV.js.map +7 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2507 -52632
- package/dist/index.js.map +4 -4
- package/dist/init-E2NDDOHI.js +27 -0
- package/dist/init-E2NDDOHI.js.map +7 -0
- package/dist/input-reader.d.ts +1 -1
- package/dist/input-reader.d.ts.map +1 -1
- package/dist/kanban-dispatch-route.d.ts +14 -0
- package/dist/kanban-dispatch-route.d.ts.map +1 -0
- package/dist/kanban-hq-sync.d.ts +2 -0
- package/dist/kanban-hq-sync.d.ts.map +1 -1
- package/dist/kanban-run-mirror-EKVNGXWW.js +12 -0
- package/dist/kanban-run-mirror-EKVNGXWW.js.map +7 -0
- package/dist/live-settings-input.d.ts +69 -0
- package/dist/live-settings-input.d.ts.map +1 -0
- package/dist/mailbox-serve-2UWHEN3M.js +302 -0
- package/dist/mailbox-serve-2UWHEN3M.js.map +7 -0
- package/dist/mcp-CYLPUEHC.js +413 -0
- package/dist/mcp-CYLPUEHC.js.map +7 -0
- package/dist/mcp-serve.d.ts +1 -1
- package/dist/mcp-serve.d.ts.map +1 -1
- package/dist/modeldiag-HENYJ4N7.js +1290 -0
- package/dist/modeldiag-HENYJ4N7.js.map +7 -0
- package/dist/next-task-predictor.d.ts +1 -1
- package/dist/next-task-predictor.d.ts.map +1 -1
- package/dist/permissions-FNYLZIL4.js +115 -0
- package/dist/permissions-FNYLZIL4.js.map +7 -0
- package/dist/picker-key-state.d.ts +7 -0
- package/dist/picker-key-state.d.ts.map +1 -0
- package/dist/picker-model-picker.d.ts +6 -0
- package/dist/picker-model-picker.d.ts.map +1 -0
- package/dist/picker-ui.d.ts +17 -0
- package/dist/picker-ui.d.ts.map +1 -0
- package/dist/picker.d.ts +4 -37
- package/dist/picker.d.ts.map +1 -1
- package/dist/plugin-management.d.ts +1 -1
- package/dist/plugin-management.d.ts.map +1 -1
- package/dist/plugin-usage-OU6X6LM3.js +40 -0
- package/dist/plugin-usage-OU6X6LM3.js.map +7 -0
- package/dist/plugins-4LJ5IUBI.js +24 -0
- package/dist/plugins-4LJ5IUBI.js.map +7 -0
- package/dist/pre-launch.d.ts +0 -1
- package/dist/pre-launch.d.ts.map +1 -1
- package/dist/project-BLQ3ALVL.js +76 -0
- package/dist/project-BLQ3ALVL.js.map +7 -0
- package/dist/project-manifest-GE25PW4S.js +18 -0
- package/dist/project-manifest-GE25PW4S.js.map +7 -0
- package/dist/project-picker-BOCWHBXP.js +17 -0
- package/dist/project-picker-BOCWHBXP.js.map +7 -0
- package/dist/projects-XE76NFNZ.js +38 -0
- package/dist/projects-XE76NFNZ.js.map +7 -0
- package/dist/provider-helpers.d.ts +22 -6
- package/dist/provider-helpers.d.ts.map +1 -1
- package/dist/providers-models-GDELBHZK.js +589 -0
- package/dist/providers-models-GDELBHZK.js.map +7 -0
- package/dist/quick-VIO2BEHS.js +10 -0
- package/dist/quick-VIO2BEHS.js.map +7 -0
- package/dist/repl-auto-proceed.d.ts +19 -0
- package/dist/repl-auto-proceed.d.ts.map +1 -0
- package/dist/repl-client-registration.d.ts +7 -0
- package/dist/repl-client-registration.d.ts.map +1 -0
- package/dist/repl-input.d.ts +11 -0
- package/dist/repl-input.d.ts.map +1 -0
- package/dist/repl-options.d.ts +54 -0
- package/dist/repl-options.d.ts.map +1 -0
- package/dist/repl-rendering.d.ts +4 -0
- package/dist/repl-rendering.d.ts.map +1 -0
- package/dist/repl-suggestions.d.ts +21 -0
- package/dist/repl-suggestions.d.ts.map +1 -0
- package/dist/repl.d.ts +3 -162
- package/dist/repl.d.ts.map +1 -1
- package/dist/replay-KCTXNMZQ.js +75 -0
- package/dist/replay-KCTXNMZQ.js.map +7 -0
- package/dist/rewind-EJMVFVLS.js +185 -0
- package/dist/rewind-EJMVFVLS.js.map +7 -0
- package/dist/sdd-runtime-IKPROVV2.js +54 -0
- package/dist/sdd-runtime-IKPROVV2.js.map +7 -0
- package/dist/services/dispatch-classifier.d.ts +1 -1
- package/dist/services/dispatch-classifier.d.ts.map +1 -1
- package/dist/services/project-facts.d.ts.map +1 -1
- package/dist/services/sdd/project-context.d.ts +1 -1
- package/dist/services/sdd/project-context.d.ts.map +1 -1
- package/dist/services/sdd/task-manager.d.ts +1 -1
- package/dist/services/sdd/task-manager.d.ts.map +1 -1
- package/dist/services/statusline-config.d.ts +1 -1
- package/dist/services/statusline-config.d.ts.map +1 -1
- package/dist/session-event-wiring.d.ts +5 -2
- package/dist/session-event-wiring.d.ts.map +1 -1
- package/dist/sessions-config-NLCMK3BY.js +409 -0
- package/dist/sessions-config-NLCMK3BY.js.map +7 -0
- package/dist/settings-menu.d.ts +4 -19
- package/dist/settings-menu.d.ts.map +1 -1
- package/dist/simpleui-dist-JIFGUJO7.js +93 -0
- package/dist/simpleui-dist-JIFGUJO7.js.map +7 -0
- package/dist/slash-commands/agent-improve.d.ts +6 -5
- package/dist/slash-commands/agent-improve.d.ts.map +1 -1
- package/dist/slash-commands/brain.d.ts.map +1 -1
- package/dist/slash-commands/btw.d.ts +1 -1
- package/dist/slash-commands/btw.d.ts.map +1 -1
- package/dist/slash-commands/clear.d.ts +1 -1
- package/dist/slash-commands/clear.d.ts.map +1 -1
- package/dist/slash-commands/fallback.d.ts.map +1 -1
- package/dist/slash-commands/goal-refiner.d.ts +2 -2
- package/dist/slash-commands/goal-refiner.d.ts.map +1 -1
- package/dist/slash-commands/interrupt.d.ts +1 -1
- package/dist/slash-commands/interrupt.d.ts.map +1 -1
- package/dist/slash-commands/kanban-agent-helpers.d.ts +18 -0
- package/dist/slash-commands/kanban-agent-helpers.d.ts.map +1 -0
- package/dist/slash-commands/kanban-format.d.ts +16 -0
- package/dist/slash-commands/kanban-format.d.ts.map +1 -0
- package/dist/slash-commands/kanban-help.d.ts +2 -0
- package/dist/slash-commands/kanban-help.d.ts.map +1 -0
- package/dist/slash-commands/kanban.d.ts +1 -1
- package/dist/slash-commands/kanban.d.ts.map +1 -1
- package/dist/slash-commands/mailbox-demo.d.ts.map +1 -1
- package/dist/slash-commands/mailbox.d.ts +3 -2
- package/dist/slash-commands/mailbox.d.ts.map +1 -1
- package/dist/slash-commands/memory-compact.d.ts +5 -0
- package/dist/slash-commands/memory-compact.d.ts.map +1 -0
- package/dist/slash-commands/memory-flags.d.ts +36 -0
- package/dist/slash-commands/memory-flags.d.ts.map +1 -0
- package/dist/slash-commands/memory-formatters.d.ts +16 -0
- package/dist/slash-commands/memory-formatters.d.ts.map +1 -0
- package/dist/slash-commands/memory.d.ts.map +1 -1
- package/dist/slash-commands/mouse.d.ts +2 -2
- package/dist/slash-commands/refiner.d.ts +1 -1
- package/dist/slash-commands/refiner.d.ts.map +1 -1
- package/dist/slash-commands/review.d.ts.map +1 -1
- package/dist/slash-commands/sdd-command-helpers.d.ts +23 -0
- package/dist/slash-commands/sdd-command-helpers.d.ts.map +1 -0
- package/dist/slash-commands/sdd-format.d.ts +32 -0
- package/dist/slash-commands/sdd-format.d.ts.map +1 -0
- package/dist/slash-commands/sdd.d.ts +1 -1
- package/dist/slash-commands/sdd.d.ts.map +1 -1
- package/dist/slash-commands/tool.d.ts +1 -1
- package/dist/slash-commands/tool.d.ts.map +1 -1
- package/dist/slash-commands/working-dir.d.ts +1 -1
- package/dist/slash-commands/working-dir.d.ts.map +1 -1
- package/dist/subcommands/handlers/chronicle.d.ts.map +1 -1
- package/dist/subcommands/handlers/mailbox-serve.d.ts.map +1 -1
- package/dist/subcommands/handlers/model-smoke-test.d.ts +51 -0
- package/dist/subcommands/handlers/model-smoke-test.d.ts.map +1 -0
- package/dist/subcommands/handlers/modeldiag-profiles.d.ts +64 -0
- package/dist/subcommands/handlers/modeldiag-profiles.d.ts.map +1 -0
- package/dist/subcommands/handlers/modeldiag.d.ts.map +1 -1
- package/dist/subcommands/handlers/per-subcommand-deep-help-table.d.ts +3 -0
- package/dist/subcommands/handlers/per-subcommand-deep-help-table.d.ts.map +1 -0
- package/dist/subcommands/handlers/per-subcommand-help-data.d.ts +3 -0
- package/dist/subcommands/handlers/per-subcommand-help-data.d.ts.map +1 -0
- package/dist/subcommands/handlers/per-subcommand-help-table.d.ts +36 -0
- package/dist/subcommands/handlers/per-subcommand-help-table.d.ts.map +1 -0
- package/dist/subcommands/handlers/per-subcommand-help-types.d.ts +18 -0
- package/dist/subcommands/handlers/per-subcommand-help-types.d.ts.map +1 -0
- package/dist/subcommands/handlers/per-subcommand-help.d.ts +2 -80
- package/dist/subcommands/handlers/per-subcommand-help.d.ts.map +1 -1
- package/dist/subcommands/handlers/rewind.d.ts.map +1 -1
- package/dist/subcommands/index.d.ts +7 -0
- package/dist/subcommands/index.d.ts.map +1 -1
- package/dist/tools-skills-UNKLOB7M.js +29 -0
- package/dist/tools-skills-UNKLOB7M.js.map +7 -0
- package/dist/update-YC4TYW2Y.js +16 -0
- package/dist/update-YC4TYW2Y.js.map +7 -0
- package/dist/update-check-UMYTOH7Z.js +18 -0
- package/dist/update-check-UMYTOH7Z.js.map +7 -0
- package/dist/version-help-DROHZRW6.js +11 -0
- package/dist/version-help-DROHZRW6.js.map +7 -0
- package/dist/webui-server/connection-handler.d.ts +2 -1
- package/dist/webui-server/connection-handler.d.ts.map +1 -1
- package/dist/webui-server/kanban-host-adapter.d.ts +9 -5
- package/dist/webui-server/kanban-host-adapter.d.ts.map +1 -1
- package/dist/webui-server/listen-helpers.d.ts +18 -0
- package/dist/webui-server/listen-helpers.d.ts.map +1 -0
- package/dist/webui-server/node-pty-loader.d.ts +6 -0
- package/dist/webui-server/node-pty-loader.d.ts.map +1 -0
- package/dist/webui-server/prefs-seeding.d.ts.map +1 -1
- package/dist/webui-server/terminal-log-view.d.ts +30 -0
- package/dist/webui-server/terminal-log-view.d.ts.map +1 -0
- package/dist/webui-server-G3TISUIY.js +1459 -0
- package/dist/webui-server-G3TISUIY.js.map +7 -0
- package/dist/webui-server-options.d.ts +189 -0
- package/dist/webui-server-options.d.ts.map +1 -0
- package/dist/webui-server.d.ts +2 -178
- package/dist/webui-server.d.ts.map +1 -1
- package/dist/wiring/brain-and-orchestration.d.ts +2 -2
- package/dist/wiring/brain-and-orchestration.d.ts.map +1 -1
- package/dist/wiring/codebase-index.d.ts.map +1 -1
- package/dist/wiring/command-host-adapters.d.ts +2 -2
- package/dist/wiring/command-host-adapters.d.ts.map +1 -1
- package/dist/wiring/command-host-state.d.ts +7 -7
- package/dist/wiring/command-host-state.d.ts.map +1 -1
- package/dist/wiring/dep-watcher-bridge.d.ts +23 -0
- package/dist/wiring/dep-watcher-bridge.d.ts.map +1 -0
- package/dist/wiring/dep-watcher.d.ts +1 -1
- package/dist/wiring/dep-watcher.d.ts.map +1 -1
- package/dist/wiring/director-setup.d.ts +4 -4
- package/dist/wiring/director-setup.d.ts.map +1 -1
- package/dist/wiring/eternal-command-handlers.d.ts +5 -5
- package/dist/wiring/eternal-command-handlers.d.ts.map +1 -1
- package/dist/wiring/fleet-command-handlers.d.ts +2 -2
- package/dist/wiring/fleet-command-handlers.d.ts.map +1 -1
- package/dist/wiring/hq-telemetry.d.ts +5 -5
- package/dist/wiring/hq-telemetry.d.ts.map +1 -1
- package/dist/wiring/lifecycle-plugins.d.ts +9 -8
- package/dist/wiring/lifecycle-plugins.d.ts.map +1 -1
- package/dist/wiring/mailbox-bridge-bootstrap.d.ts +1 -1
- package/dist/wiring/management-tools.d.ts +15 -0
- package/dist/wiring/management-tools.d.ts.map +1 -0
- package/dist/wiring/pipeline.d.ts +4 -3
- package/dist/wiring/pipeline.d.ts.map +1 -1
- package/dist/wiring/plugins.d.ts +1 -1
- package/dist/wiring/plugins.d.ts.map +1 -1
- package/dist/wiring/provider-runtime-setup.d.ts +18 -5
- package/dist/wiring/provider-runtime-setup.d.ts.map +1 -1
- package/dist/wiring/provider-status.d.ts +3 -3
- package/dist/wiring/provider-status.d.ts.map +1 -1
- package/dist/wiring/provider-utility-tools.d.ts +4 -4
- package/dist/wiring/provider-utility-tools.d.ts.map +1 -1
- package/dist/wiring/provider.d.ts.map +1 -1
- package/dist/wiring/replay.d.ts +2 -2
- package/dist/wiring/replay.d.ts.map +1 -1
- package/dist/wiring/runtime-controller-deps.d.ts +4 -4
- package/dist/wiring/runtime-controller-deps.d.ts.map +1 -1
- package/dist/wiring/runtime-dispatch-state.d.ts +5 -5
- package/dist/wiring/runtime-dispatch-state.d.ts.map +1 -1
- package/dist/wiring/runtime-picker-deps.d.ts +3 -3
- package/dist/wiring/runtime-picker-deps.d.ts.map +1 -1
- package/dist/wiring/sage.d.ts.map +1 -1
- package/dist/wiring/sdd-handlers.d.ts.map +1 -1
- package/dist/wiring/session-command-handlers.d.ts +5 -5
- package/dist/wiring/session-command-handlers.d.ts.map +1 -1
- package/dist/wiring/session-registry.d.ts +38 -0
- package/dist/wiring/session-registry.d.ts.map +1 -0
- package/dist/wiring/session.d.ts +7 -5
- package/dist/wiring/session.d.ts.map +1 -1
- package/dist/wiring/tools.d.ts +2 -2
- package/dist/wiring/tools.d.ts.map +1 -1
- package/package.json +22 -22
- package/dist/cli-recovery-prompt.d.ts +0 -19
- package/dist/cli-recovery-prompt.d.ts.map +0 -1
- package/dist/first-run-auth-gate.d.ts +0 -20
- package/dist/first-run-auth-gate.d.ts.map +0 -1
- package/dist/pre-launch/indexing-question.d.ts +0 -28
- package/dist/pre-launch/indexing-question.d.ts.map +0 -1
|
@@ -0,0 +1,1325 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LOCAL_LLM_PRESETS,
|
|
3
|
+
runLiveProviderPicker
|
|
4
|
+
} from "./chunk-JBSQ7ONN.js";
|
|
5
|
+
import {
|
|
6
|
+
openBrowser,
|
|
7
|
+
runCodexOAuthLogin,
|
|
8
|
+
startLoopbackServer
|
|
9
|
+
} from "./chunk-TADVX644.js";
|
|
10
|
+
import {
|
|
11
|
+
activeLabel,
|
|
12
|
+
loadConfigProviders,
|
|
13
|
+
maskedKey,
|
|
14
|
+
mutateConfigProviders,
|
|
15
|
+
normalizeKeys,
|
|
16
|
+
nowIso,
|
|
17
|
+
writeKeysBack
|
|
18
|
+
} from "./chunk-PJHET45A.js";
|
|
19
|
+
|
|
20
|
+
// src/auth-menu/oauth-menu.ts
|
|
21
|
+
import { color as color3 } from "@wrongstack/core/utils";
|
|
22
|
+
|
|
23
|
+
// src/auth-menu/anthropic-oauth.ts
|
|
24
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
25
|
+
import { color } from "@wrongstack/core/utils";
|
|
26
|
+
import { FetchError, ParseError } from "@wrongstack/core/types";
|
|
27
|
+
var CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
28
|
+
var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
29
|
+
var TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
30
|
+
var REDIRECT_PORT = 53692;
|
|
31
|
+
var REDIRECT_HOST = "127.0.0.1";
|
|
32
|
+
var REDIRECT_PATH = "/callback";
|
|
33
|
+
var REDIRECT_URI = `http://localhost:${REDIRECT_PORT}${REDIRECT_PATH}`;
|
|
34
|
+
var SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
35
|
+
var CLAUDE_PROVIDER_ID = "anthropic-oauth";
|
|
36
|
+
var CLAUDE_BASE_URL = "https://api.anthropic.com";
|
|
37
|
+
function base64url(buf) {
|
|
38
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
39
|
+
}
|
|
40
|
+
function generatePkce() {
|
|
41
|
+
const verifier = base64url(randomBytes(32));
|
|
42
|
+
const challenge = base64url(createHash("sha256").update(verifier).digest());
|
|
43
|
+
return { verifier, challenge };
|
|
44
|
+
}
|
|
45
|
+
function buildAuthorizeUrl(challenge, verifier) {
|
|
46
|
+
const params = new URLSearchParams({
|
|
47
|
+
code: "true",
|
|
48
|
+
client_id: CLIENT_ID,
|
|
49
|
+
response_type: "code",
|
|
50
|
+
redirect_uri: REDIRECT_URI,
|
|
51
|
+
scope: SCOPES,
|
|
52
|
+
code_challenge: challenge,
|
|
53
|
+
code_challenge_method: "S256",
|
|
54
|
+
state: verifier
|
|
55
|
+
});
|
|
56
|
+
return `${AUTHORIZE_URL}?${params.toString()}`;
|
|
57
|
+
}
|
|
58
|
+
function parseAuthorizationInput(input) {
|
|
59
|
+
const value = input.trim();
|
|
60
|
+
if (!value) return {};
|
|
61
|
+
try {
|
|
62
|
+
const url = new URL(value);
|
|
63
|
+
return {
|
|
64
|
+
code: url.searchParams.get("code") ?? void 0,
|
|
65
|
+
state: url.searchParams.get("state") ?? void 0
|
|
66
|
+
};
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
if (value.includes("#")) {
|
|
70
|
+
const [code, state] = value.split("#", 2);
|
|
71
|
+
return { code, state };
|
|
72
|
+
}
|
|
73
|
+
if (value.includes("code=")) {
|
|
74
|
+
const params = new URLSearchParams(value);
|
|
75
|
+
return {
|
|
76
|
+
code: params.get("code") ?? void 0,
|
|
77
|
+
state: params.get("state") ?? void 0
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { code: value };
|
|
81
|
+
}
|
|
82
|
+
async function readTokens(res, op, url) {
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
const text = await res.text().catch(() => "");
|
|
85
|
+
throw new FetchError({
|
|
86
|
+
message: `Claude token ${op} failed (${res.status}): ${text || res.statusText}`,
|
|
87
|
+
status: res.status,
|
|
88
|
+
context: { provider: "anthropic-oauth", op, url }
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const json = await res.json();
|
|
92
|
+
if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
93
|
+
throw new ParseError({
|
|
94
|
+
message: `Claude token ${op} response missing fields`,
|
|
95
|
+
source: "anthropic-oauth-token-response",
|
|
96
|
+
context: { op }
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
access: json.access_token,
|
|
101
|
+
refresh: json.refresh_token,
|
|
102
|
+
expires: Date.now() + json.expires_in * 1e3
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function exchangeAuthorizationCode(code, state, verifier, signal) {
|
|
106
|
+
const res = await fetch(TOKEN_URL, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
grant_type: "authorization_code",
|
|
111
|
+
client_id: CLIENT_ID,
|
|
112
|
+
code,
|
|
113
|
+
state,
|
|
114
|
+
redirect_uri: REDIRECT_URI,
|
|
115
|
+
code_verifier: verifier
|
|
116
|
+
}),
|
|
117
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
|
|
118
|
+
});
|
|
119
|
+
return readTokens(res, "exchange", TOKEN_URL);
|
|
120
|
+
}
|
|
121
|
+
async function fetchClaudeModels(accessToken, signal) {
|
|
122
|
+
try {
|
|
123
|
+
const res = await fetch(`${CLAUDE_BASE_URL}/v1/models?limit=100`, {
|
|
124
|
+
headers: {
|
|
125
|
+
accept: "application/json",
|
|
126
|
+
authorization: `Bearer ${accessToken}`,
|
|
127
|
+
"anthropic-version": "2023-06-01",
|
|
128
|
+
"anthropic-beta": "claude-code-20250219,oauth-2025-04-20"
|
|
129
|
+
},
|
|
130
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(8e3)])
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok) return [];
|
|
133
|
+
const json = await res.json();
|
|
134
|
+
const ids = (json?.data ?? []).map((m) => m.id).filter((id) => typeof id === "string" && id.startsWith("claude-"));
|
|
135
|
+
return ids;
|
|
136
|
+
} catch {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function runClaudeOAuthLogin(deps, opts = {}) {
|
|
141
|
+
const providerId = opts.providerId ?? CLAUDE_PROVIDER_ID;
|
|
142
|
+
const { verifier, challenge } = generatePkce();
|
|
143
|
+
const state = verifier;
|
|
144
|
+
const authorizeUrl = buildAuthorizeUrl(challenge, verifier);
|
|
145
|
+
const osc8 = (url) => `\x1B]8;;${url}\x1B\\${url}\x1B]8;;\x1B\\`;
|
|
146
|
+
const ac = new AbortController();
|
|
147
|
+
const onSig = () => ac.abort();
|
|
148
|
+
const external = opts.signal;
|
|
149
|
+
if (external) {
|
|
150
|
+
if (external.aborted) ac.abort();
|
|
151
|
+
else external.addEventListener("abort", () => ac.abort(), { once: true });
|
|
152
|
+
} else {
|
|
153
|
+
process.on("SIGINT", onSig);
|
|
154
|
+
}
|
|
155
|
+
const server = await startLoopbackServer(state, [REDIRECT_PORT], ac.signal, REDIRECT_PATH, REDIRECT_HOST);
|
|
156
|
+
deps.renderer.write(
|
|
157
|
+
color.bold(`
|
|
158
|
+
Sign in with Claude \u2014 ${color.cyan(providerId)}
|
|
159
|
+
`) + color.dim(" Uses your Claude Pro/Max subscription (not an API key).\n") + color.amber(" \u26A0 Using a subscription outside the official Claude Code client is against\n") + color.amber(" Anthropic\u2019s Terms \u2014 your account could be rate-limited or banned.\n") + color.dim(" Sanctioned programmatic use = an API key: ") + color.bold("wstack auth anthropic") + color.dim("\n\n") + color.bold(` ${"\u2500".repeat(56)}
|
|
160
|
+
`) + color.bold(" Open this URL in your browser to sign in:\n") + color.cyan(` ${osc8(authorizeUrl)}
|
|
161
|
+
`) + color.bold(` ${"\u2500".repeat(56)}
|
|
162
|
+
|
|
163
|
+
`)
|
|
164
|
+
);
|
|
165
|
+
if (server.bound) {
|
|
166
|
+
openBrowser(authorizeUrl);
|
|
167
|
+
deps.renderer.write(
|
|
168
|
+
color.dim(" A browser window should open. Waiting for you to finish signing in...\n") + color.dim(" (Listening on http://localhost:53692 \u2014 press Ctrl+C to cancel.)\n")
|
|
169
|
+
);
|
|
170
|
+
} else {
|
|
171
|
+
deps.renderer.write(
|
|
172
|
+
color.amber(" \u26A0 Could not start the local callback listener (port 53692 in use).\n") + color.dim(" After signing in, paste the full redirect URL (or the code) below.\n")
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
let code;
|
|
176
|
+
try {
|
|
177
|
+
if (server.bound) {
|
|
178
|
+
const got = await server.waitForCode();
|
|
179
|
+
if (ac.signal.aborted) {
|
|
180
|
+
deps.renderer.write(color.dim(" Cancelled.\n"));
|
|
181
|
+
return 1;
|
|
182
|
+
}
|
|
183
|
+
if (got) code = got.code;
|
|
184
|
+
}
|
|
185
|
+
if (!code) {
|
|
186
|
+
const input = (await deps.reader.readLine(
|
|
187
|
+
`
|
|
188
|
+
${color.amber("?")} Paste the redirect URL or code ${color.dim("(or q to cancel)")}: `
|
|
189
|
+
)).trim();
|
|
190
|
+
if (input.toLowerCase() === "q" || input === "") {
|
|
191
|
+
deps.renderer.write(color.dim(" Cancelled.\n"));
|
|
192
|
+
return 1;
|
|
193
|
+
}
|
|
194
|
+
const parsed = parseAuthorizationInput(input);
|
|
195
|
+
if (parsed.state && parsed.state !== state) {
|
|
196
|
+
deps.renderer.writeError(" State mismatch \u2014 please restart the login flow.");
|
|
197
|
+
return 1;
|
|
198
|
+
}
|
|
199
|
+
code = parsed.code;
|
|
200
|
+
}
|
|
201
|
+
if (!code) {
|
|
202
|
+
deps.renderer.writeError(" No authorization code received.");
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
deps.renderer.write(color.dim("\n Exchanging authorization code for tokens...\n"));
|
|
206
|
+
const tokens = await exchangeAuthorizationCode(code, state, verifier, ac.signal);
|
|
207
|
+
const models = await fetchClaudeModels(tokens.access, ac.signal);
|
|
208
|
+
const saved = await saveClaudeTokens(deps, providerId, tokens, models);
|
|
209
|
+
if (!saved) return 1;
|
|
210
|
+
deps.renderer.write(color.green("\n \u2713 Signed in with Claude!\n"));
|
|
211
|
+
const modelHint = models[0];
|
|
212
|
+
deps.renderer.writeInfo(
|
|
213
|
+
` Saved as provider ${color.bold(providerId)}${models.length ? ` (${models.length} models)` : ""}.
|
|
214
|
+
` + (modelHint ? ` Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
|
|
215
|
+
` : ` No model list was discovered; choose a model before starting a run.
|
|
216
|
+
`) + color.dim(" Tokens refresh automatically before they expire.\n")
|
|
217
|
+
);
|
|
218
|
+
return 0;
|
|
219
|
+
} catch (err) {
|
|
220
|
+
const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
|
|
221
|
+
deps.renderer.writeError(` Login failed: ${msg}`);
|
|
222
|
+
return 1;
|
|
223
|
+
} finally {
|
|
224
|
+
server.close();
|
|
225
|
+
if (!external) process.off("SIGINT", onSig);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function saveClaudeTokens(deps, providerId, tokens, models) {
|
|
229
|
+
const entry = {
|
|
230
|
+
label: "oauth-default",
|
|
231
|
+
apiKey: tokens.access,
|
|
232
|
+
createdAt: nowIso(),
|
|
233
|
+
authMethod: "oauth",
|
|
234
|
+
expiresAt: new Date(tokens.expires).toISOString(),
|
|
235
|
+
refreshToken: tokens.refresh,
|
|
236
|
+
tokenType: "bearer",
|
|
237
|
+
scope: SCOPES
|
|
238
|
+
};
|
|
239
|
+
try {
|
|
240
|
+
await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {
|
|
241
|
+
const existing = all[providerId];
|
|
242
|
+
const p = existing ? { ...existing } : { type: providerId };
|
|
243
|
+
p.family = "anthropic-oauth";
|
|
244
|
+
if (!p.baseUrl) p.baseUrl = CLAUDE_BASE_URL;
|
|
245
|
+
if (models.length > 0) p.models = models;
|
|
246
|
+
const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
|
|
247
|
+
keys.push(entry);
|
|
248
|
+
writeKeysBack(p, keys);
|
|
249
|
+
p.activeKey = entry.label;
|
|
250
|
+
all[providerId] = p;
|
|
251
|
+
}, deps.profileConfigPath);
|
|
252
|
+
return true;
|
|
253
|
+
} catch (err) {
|
|
254
|
+
deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/auth-menu/github-copilot-oauth.ts
|
|
260
|
+
import { spawn } from "node:child_process";
|
|
261
|
+
import { color as color2 } from "@wrongstack/core/utils";
|
|
262
|
+
import { FetchError as FetchError2, ParseError as ParseError2 } from "@wrongstack/core/types";
|
|
263
|
+
import { copilotBaseUrlFromToken, refreshCopilotToken } from "@wrongstack/providers";
|
|
264
|
+
var CLIENT_ID2 = "Iv1.b507a08c87ecfe98";
|
|
265
|
+
var DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
266
|
+
var ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
267
|
+
var COPILOT_HEADERS = {
|
|
268
|
+
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
269
|
+
"Editor-Version": "vscode/1.107.0",
|
|
270
|
+
"Editor-Plugin-Version": "copilot-chat/0.35.0",
|
|
271
|
+
"Copilot-Integration-Id": "vscode-chat"
|
|
272
|
+
};
|
|
273
|
+
var COPILOT_API_VERSION = "2026-06-01";
|
|
274
|
+
var COPILOT_PROVIDER_ID = "github-copilot";
|
|
275
|
+
function openBrowser2(url) {
|
|
276
|
+
try {
|
|
277
|
+
const platform = process.platform;
|
|
278
|
+
const { command, args } = platform === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
|
|
279
|
+
const child = spawn(command, args, { stdio: "ignore", windowsHide: true });
|
|
280
|
+
child.on("error", () => {
|
|
281
|
+
});
|
|
282
|
+
child.unref();
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function sleep(ms, signal) {
|
|
287
|
+
return new Promise((resolve, reject) => {
|
|
288
|
+
if (signal.aborted) return reject(new DOMException("Aborted", "AbortError"));
|
|
289
|
+
const t = setTimeout(resolve, ms);
|
|
290
|
+
signal.addEventListener(
|
|
291
|
+
"abort",
|
|
292
|
+
() => {
|
|
293
|
+
clearTimeout(t);
|
|
294
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
295
|
+
},
|
|
296
|
+
{ once: true }
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
async function startDeviceFlow(signal) {
|
|
301
|
+
const res = await fetch(DEVICE_CODE_URL, {
|
|
302
|
+
method: "POST",
|
|
303
|
+
headers: {
|
|
304
|
+
accept: "application/json",
|
|
305
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
306
|
+
"user-agent": COPILOT_HEADERS["User-Agent"]
|
|
307
|
+
},
|
|
308
|
+
body: new URLSearchParams({ client_id: CLIENT_ID2, scope: "read:user" }).toString(),
|
|
309
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3)
|
|
310
|
+
});
|
|
311
|
+
if (!res.ok) {
|
|
312
|
+
throw new FetchError2({
|
|
313
|
+
message: `GitHub device-code request failed (${res.status})`,
|
|
314
|
+
status: res.status,
|
|
315
|
+
context: { provider: "github-copilot", op: "device-code", url: DEVICE_CODE_URL }
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const json = await res.json();
|
|
319
|
+
if (!json?.device_code || !json.user_code || !json.verification_uri || typeof json.expires_in !== "number") {
|
|
320
|
+
throw new ParseError2({
|
|
321
|
+
message: "Invalid device-code response",
|
|
322
|
+
source: "github-copilot-device-code-response"
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
device_code: json.device_code,
|
|
327
|
+
user_code: json.user_code,
|
|
328
|
+
verification_uri: json.verification_uri,
|
|
329
|
+
interval: json.interval ?? 5,
|
|
330
|
+
expires_in: json.expires_in
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
async function pollForGitHubToken(device, signal) {
|
|
334
|
+
let intervalMs = device.interval * 1e3;
|
|
335
|
+
const expiresAt = Date.now() + device.expires_in * 1e3;
|
|
336
|
+
while (Date.now() < expiresAt) {
|
|
337
|
+
await sleep(intervalMs, signal);
|
|
338
|
+
const res = await fetch(ACCESS_TOKEN_URL, {
|
|
339
|
+
method: "POST",
|
|
340
|
+
headers: {
|
|
341
|
+
accept: "application/json",
|
|
342
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
343
|
+
"user-agent": COPILOT_HEADERS["User-Agent"]
|
|
344
|
+
},
|
|
345
|
+
body: new URLSearchParams({
|
|
346
|
+
client_id: CLIENT_ID2,
|
|
347
|
+
device_code: device.device_code,
|
|
348
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
349
|
+
}).toString(),
|
|
350
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(15e3)])
|
|
351
|
+
});
|
|
352
|
+
const json = await res.json().catch(() => ({}));
|
|
353
|
+
if (json.access_token) return json.access_token;
|
|
354
|
+
if (json.error === "authorization_pending") continue;
|
|
355
|
+
if (json.error === "slow_down") {
|
|
356
|
+
intervalMs += 5e3;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
throw new Error(`Device flow failed: ${json.error ?? "unknown error"}`);
|
|
360
|
+
}
|
|
361
|
+
throw new FetchError2({
|
|
362
|
+
message: "Device code expired \u2014 please restart the login.",
|
|
363
|
+
status: 408,
|
|
364
|
+
// Request Timeout
|
|
365
|
+
context: { provider: "github-copilot", op: "device-code-poll", reason: "expired" }
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
function isUsableCopilotChatModel(item) {
|
|
369
|
+
if (typeof item.id !== "string" || item.id.length === 0) return false;
|
|
370
|
+
const cap = item.capabilities;
|
|
371
|
+
if (cap?.type !== "chat") return false;
|
|
372
|
+
if (cap.supports?.tool_calls !== true) return false;
|
|
373
|
+
const eps = item.supported_endpoints;
|
|
374
|
+
if (Array.isArray(eps) && !eps.includes("/chat/completions")) return false;
|
|
375
|
+
if (item.policy?.state === "disabled") return false;
|
|
376
|
+
if (item.vendor === "Experimental") return false;
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
async function fetchCopilotModels(copilotToken, signal) {
|
|
380
|
+
try {
|
|
381
|
+
const base = copilotBaseUrlFromToken(copilotToken);
|
|
382
|
+
const res = await fetch(`${base}/models`, {
|
|
383
|
+
headers: {
|
|
384
|
+
accept: "application/json",
|
|
385
|
+
authorization: `Bearer ${copilotToken}`,
|
|
386
|
+
"X-GitHub-Api-Version": COPILOT_API_VERSION,
|
|
387
|
+
...COPILOT_HEADERS
|
|
388
|
+
},
|
|
389
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(8e3)])
|
|
390
|
+
});
|
|
391
|
+
if (!res.ok) return [];
|
|
392
|
+
const json = await res.json();
|
|
393
|
+
const data = json?.data;
|
|
394
|
+
if (!Array.isArray(data)) return [];
|
|
395
|
+
const usable = data.filter(isUsableCopilotChatModel);
|
|
396
|
+
usable.sort((a, b) => copilotModelRank(a) - copilotModelRank(b));
|
|
397
|
+
return usable.map((m) => m.id);
|
|
398
|
+
} catch {
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function copilotModelRank(item) {
|
|
403
|
+
if (item.is_chat_default === true) return 0;
|
|
404
|
+
if (item.is_chat_fallback === true) return 1;
|
|
405
|
+
return 2;
|
|
406
|
+
}
|
|
407
|
+
async function runCopilotOAuthLogin(deps, opts = {}) {
|
|
408
|
+
const providerId = opts.providerId ?? COPILOT_PROVIDER_ID;
|
|
409
|
+
const ac = new AbortController();
|
|
410
|
+
const onSig = () => ac.abort();
|
|
411
|
+
const external = opts.signal;
|
|
412
|
+
if (external) {
|
|
413
|
+
if (external.aborted) ac.abort();
|
|
414
|
+
else external.addEventListener("abort", () => ac.abort(), { once: true });
|
|
415
|
+
} else {
|
|
416
|
+
process.on("SIGINT", onSig);
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
deps.renderer.write(
|
|
420
|
+
color2.bold(`
|
|
421
|
+
Sign in with GitHub Copilot \u2014 ${color2.cyan(providerId)}
|
|
422
|
+
`) + color2.dim(" Uses your GitHub Copilot subscription (not an API key).\n") + color2.amber(" \u26A0 Using Copilot outside its official editor integrations may violate\n") + color2.amber(" GitHub\u2019s Terms \u2014 your account could be rate-limited or banned.\n\n")
|
|
423
|
+
);
|
|
424
|
+
const device = await startDeviceFlow(ac.signal);
|
|
425
|
+
deps.renderer.write(
|
|
426
|
+
color2.bold(` ${"\u2500".repeat(56)}
|
|
427
|
+
`) + color2.bold(" Open this URL and enter the code:\n") + color2.cyan(` ${device.verification_uri}
|
|
428
|
+
`) + color2.bold(" Code: ") + color2.green(color2.bold(device.user_code)) + "\n" + color2.bold(` ${"\u2500".repeat(56)}
|
|
429
|
+
|
|
430
|
+
`)
|
|
431
|
+
);
|
|
432
|
+
openBrowser2(device.verification_uri);
|
|
433
|
+
deps.renderer.write(color2.dim(" Waiting for you to authorize in the browser...\n"));
|
|
434
|
+
const githubToken = await pollForGitHubToken(device, ac.signal);
|
|
435
|
+
deps.renderer.write(color2.dim(" Authorized. Fetching your Copilot token...\n"));
|
|
436
|
+
const copilot = await refreshCopilotToken(githubToken, ac.signal);
|
|
437
|
+
const models = await fetchCopilotModels(copilot.token, ac.signal);
|
|
438
|
+
const saved = await saveCopilotTokens(
|
|
439
|
+
deps,
|
|
440
|
+
providerId,
|
|
441
|
+
copilot.token,
|
|
442
|
+
githubToken,
|
|
443
|
+
copilot.expires,
|
|
444
|
+
models
|
|
445
|
+
);
|
|
446
|
+
if (!saved) return 1;
|
|
447
|
+
deps.renderer.write(color2.green("\n \u2713 Signed in with GitHub Copilot!\n"));
|
|
448
|
+
const modelHint = models[0] ?? "gpt-4o";
|
|
449
|
+
deps.renderer.writeInfo(
|
|
450
|
+
` Saved as provider ${color2.bold(providerId)}${models.length ? ` (${models.length} models)` : ""}.
|
|
451
|
+
Use: ${color2.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
|
|
452
|
+
` + color2.dim(" The Copilot token refreshes automatically.\n")
|
|
453
|
+
);
|
|
454
|
+
return 0;
|
|
455
|
+
} catch (err) {
|
|
456
|
+
const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
|
|
457
|
+
deps.renderer.writeError(` Login failed: ${msg}`);
|
|
458
|
+
return 1;
|
|
459
|
+
} finally {
|
|
460
|
+
if (!external) process.off("SIGINT", onSig);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async function saveCopilotTokens(deps, providerId, copilotToken, githubToken, expires, models) {
|
|
464
|
+
const entry = {
|
|
465
|
+
label: "oauth-default",
|
|
466
|
+
apiKey: copilotToken,
|
|
467
|
+
createdAt: nowIso(),
|
|
468
|
+
authMethod: "oauth",
|
|
469
|
+
expiresAt: new Date(expires).toISOString(),
|
|
470
|
+
refreshToken: githubToken,
|
|
471
|
+
tokenType: "bearer"
|
|
472
|
+
};
|
|
473
|
+
try {
|
|
474
|
+
await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {
|
|
475
|
+
const existing = all[providerId];
|
|
476
|
+
const p = existing ? { ...existing } : { type: providerId };
|
|
477
|
+
p.family = "github-copilot";
|
|
478
|
+
if (!p.baseUrl) p.baseUrl = copilotBaseUrlFromToken(copilotToken);
|
|
479
|
+
if (models.length > 0) p.models = models;
|
|
480
|
+
else if (!p.models || p.models.length === 0) p.models = ["gpt-4o"];
|
|
481
|
+
const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
|
|
482
|
+
keys.push(entry);
|
|
483
|
+
writeKeysBack(p, keys);
|
|
484
|
+
p.activeKey = entry.label;
|
|
485
|
+
all[providerId] = p;
|
|
486
|
+
}, deps.profileConfigPath);
|
|
487
|
+
return true;
|
|
488
|
+
} catch (err) {
|
|
489
|
+
deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/auth-menu/oauth-menu.ts
|
|
495
|
+
function renderOAuthLoginOptions(deps, indent = " ") {
|
|
496
|
+
deps.renderer.write(
|
|
497
|
+
`${indent}${color3.bold("OAuth login options")} ${color3.dim("(subscription sign-in)")}
|
|
498
|
+
${indent}${color3.bold("chatgpt")} ChatGPT Plus/Pro ${color3.dim("(\u2192 openai-codex)")}
|
|
499
|
+
${indent}${color3.bold("claude")} Claude Pro/Max ${color3.dim("(\u2192 anthropic-oauth)")}
|
|
500
|
+
${indent}${color3.bold("copilot")} GitHub Copilot ${color3.dim("(\u2192 github-copilot)")}
|
|
501
|
+
`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
var OAUTH_ALIASES = {
|
|
505
|
+
chatgpt: ["chatgpt", "openai", "codex", "codex-cli", "openai-codex", "chatgpt-plus", "plus", "pro"],
|
|
506
|
+
claude: ["claude", "anthropic", "claude-pro", "claude-max", "anthropic-oauth", "max"],
|
|
507
|
+
copilot: ["copilot", "github", "gh", "github-copilot"]
|
|
508
|
+
};
|
|
509
|
+
var OAUTH_NUMERIC = { "1": "chatgpt", "2": "claude", "3": "copilot" };
|
|
510
|
+
function resolveOAuthKind(choice, opts = {}) {
|
|
511
|
+
const pick = choice.trim().toLowerCase();
|
|
512
|
+
if (!pick) return void 0;
|
|
513
|
+
if (opts.allowNumeric !== false && OAUTH_NUMERIC[pick]) return OAUTH_NUMERIC[pick];
|
|
514
|
+
for (const [kind, aliases] of Object.entries(OAUTH_ALIASES)) {
|
|
515
|
+
if (aliases.includes(pick)) return kind;
|
|
516
|
+
}
|
|
517
|
+
return void 0;
|
|
518
|
+
}
|
|
519
|
+
async function runOAuthLoginChoice(deps, choice, opts = {}) {
|
|
520
|
+
const kind = resolveOAuthKind(choice, opts);
|
|
521
|
+
if (!kind) return false;
|
|
522
|
+
await runOAuthLoginKind(deps, kind);
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
async function runOAuthLoginKind(deps, kind) {
|
|
526
|
+
if (kind === "claude") return runClaudeOAuthLogin(deps);
|
|
527
|
+
if (kind === "copilot") return runCopilotOAuthLogin(deps);
|
|
528
|
+
return runCodexOAuthLogin(deps);
|
|
529
|
+
}
|
|
530
|
+
async function runOAuthLoginMenu(deps) {
|
|
531
|
+
deps.renderer.write(
|
|
532
|
+
`
|
|
533
|
+
${color3.bold("Login with OAuth:")}
|
|
534
|
+
` + color3.amber(" \u26A0 Subscription tokens used outside official clients may violate provider\n") + color3.amber(" Terms \u2014 your account could be rate-limited or banned. An API key is the\n") + color3.dim(" sanctioned path for programmatic use.\n") + ` ${color3.bold("1")} ChatGPT Plus/Pro ${color3.dim("(\u2192 openai-codex)")}
|
|
535
|
+
${color3.bold("2")} Claude Pro/Max ${color3.dim("(\u2192 anthropic-oauth)")}
|
|
536
|
+
${color3.bold("3")} GitHub Copilot ${color3.dim("(\u2192 github-copilot)")}
|
|
537
|
+
`
|
|
538
|
+
);
|
|
539
|
+
const pick = await deps.reader.readLine(` ${color3.amber("?")} Pick ${color3.dim("(or b to go back)")}: `);
|
|
540
|
+
await runOAuthLoginChoice(deps, pick);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/auth-menu/helpers.ts
|
|
544
|
+
async function loadProviders(deps) {
|
|
545
|
+
return loadConfigProviders(deps.profileConfigPath, deps.vault, {
|
|
546
|
+
warn: (msg) => deps.renderer.writeWarning(msg),
|
|
547
|
+
profileConfigPath: deps.profileConfigPath
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/auth-menu/shared.ts
|
|
552
|
+
import { color as color4 } from "@wrongstack/core/utils";
|
|
553
|
+
function renderProviderLine(renderer, id, cfg, idx) {
|
|
554
|
+
const keys = normalizeKeys(cfg);
|
|
555
|
+
const active = activeLabel(cfg, keys);
|
|
556
|
+
const firstKey = keys[0];
|
|
557
|
+
let summary;
|
|
558
|
+
if (keys.length === 0) {
|
|
559
|
+
summary = color4.dim("(no keys)");
|
|
560
|
+
} else if (keys.length === 1) {
|
|
561
|
+
summary = maskedKey(firstKey?.apiKey ?? "");
|
|
562
|
+
} else {
|
|
563
|
+
const activeKeyObj = active != null ? keys.find((k) => k.label === active) : void 0;
|
|
564
|
+
summary = `${color4.dim(`${keys.length} keys`)} ${color4.dim("active:")} ${color4.bold(active ?? "?")} ` + maskedKey(activeKeyObj?.apiKey ?? firstKey?.apiKey ?? "");
|
|
565
|
+
}
|
|
566
|
+
const fam = cfg.family ? color4.dim(`[${cfg.family}]`) : "";
|
|
567
|
+
const aliasHint = cfg.type && cfg.type !== id ? color4.dim(`\u2192 ${cfg.type}`) : "";
|
|
568
|
+
renderer.write(
|
|
569
|
+
` ${color4.dim(`${idx}.`.padStart(4))} ${id.padEnd(22)} ${fam} ${aliasHint} ${summary}
|
|
570
|
+
`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
function renderProviderHeader(renderer, providerId, cfg) {
|
|
574
|
+
const keys = normalizeKeys(cfg);
|
|
575
|
+
const active = activeLabel(cfg, keys);
|
|
576
|
+
renderer.write(
|
|
577
|
+
`
|
|
578
|
+
${color4.bold(providerId)} ${cfg.family ? color4.dim(`[${cfg.family}]`) : color4.amber("[no family]")}
|
|
579
|
+
`
|
|
580
|
+
);
|
|
581
|
+
const details = [
|
|
582
|
+
color4.dim(` type: ${cfg.type ?? providerId}`),
|
|
583
|
+
color4.dim(` family: ${cfg.family ?? "(unset \u2192 resolved from models.dev when type matches)"}`),
|
|
584
|
+
color4.dim(` baseUrl: ${cfg.baseUrl ?? "(unset \u2192 catalog default)"}`)
|
|
585
|
+
];
|
|
586
|
+
if (cfg.envVars && cfg.envVars.length > 0) {
|
|
587
|
+
details.push(color4.dim(` envVars: ${cfg.envVars.join(", ")}`));
|
|
588
|
+
}
|
|
589
|
+
if (cfg.models && cfg.models.length > 0) {
|
|
590
|
+
details.push(color4.dim(` models: ${cfg.models.join(", ")}`));
|
|
591
|
+
}
|
|
592
|
+
renderer.write(details.join("\n") + "\n");
|
|
593
|
+
if (keys.length === 0) {
|
|
594
|
+
renderer.write(color4.dim(" (no keys saved)\n"));
|
|
595
|
+
} else {
|
|
596
|
+
for (let i = 0; i < keys.length; i++) {
|
|
597
|
+
const key = keys[i];
|
|
598
|
+
if (key) renderKeyLine(renderer, key, i + 1, active);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function renderKeyLine(renderer, key, idx, active) {
|
|
603
|
+
const marker = key.label === active ? color4.green("\u25CF") : color4.dim("\u25CB");
|
|
604
|
+
renderer.write(
|
|
605
|
+
` ${color4.dim(`${idx}.`.padStart(4))} ${marker} ${key.label.padEnd(20)} ${maskedKey(key.apiKey)} ${color4.dim(key.createdAt)}
|
|
606
|
+
`
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
function renderActions(renderer, keysLength) {
|
|
610
|
+
renderer.write(`
|
|
611
|
+
${color4.dim("Actions:")}
|
|
612
|
+
`);
|
|
613
|
+
renderer.write(` ${color4.bold("a")} Add another key
|
|
614
|
+
`);
|
|
615
|
+
if (keysLength > 0) {
|
|
616
|
+
renderer.write(` ${color4.bold("u")} <n> Update key <n>
|
|
617
|
+
`);
|
|
618
|
+
renderer.write(` ${color4.bold("d")} <n> Delete key <n>
|
|
619
|
+
`);
|
|
620
|
+
renderer.write(` ${color4.bold("s")} <n> Set key <n> as active
|
|
621
|
+
`);
|
|
622
|
+
}
|
|
623
|
+
renderer.write(` ${color4.bold("f")} Edit family
|
|
624
|
+
`);
|
|
625
|
+
renderer.write(` ${color4.bold("B")} Edit baseUrl ${color4.dim("(capital B \u2014 b is Back)")}
|
|
626
|
+
`);
|
|
627
|
+
renderer.write(` ${color4.bold("m")} Edit visible model list
|
|
628
|
+
`);
|
|
629
|
+
renderer.write(` ${color4.bold("x")} Remove this provider entirely
|
|
630
|
+
`);
|
|
631
|
+
renderer.write(` ${color4.bold("b")} Back
|
|
632
|
+
`);
|
|
633
|
+
renderer.write(` ${color4.bold("q")} Quit
|
|
634
|
+
`);
|
|
635
|
+
}
|
|
636
|
+
function renderTopMenu(renderer, providers) {
|
|
637
|
+
renderer.write(`
|
|
638
|
+
${color4.bold("WrongStack")} ${color4.dim("\u2014 API key manager")}
|
|
639
|
+
|
|
640
|
+
`);
|
|
641
|
+
const ids = Object.keys(providers).sort();
|
|
642
|
+
if (ids.length === 0) {
|
|
643
|
+
renderer.write(color4.dim(" No providers configured yet.\n"));
|
|
644
|
+
renderer.write(
|
|
645
|
+
color4.dim(
|
|
646
|
+
" Use (a) to add one from the models.dev catalog, (l) for a local server, or (c) for a custom provider.\n"
|
|
647
|
+
)
|
|
648
|
+
);
|
|
649
|
+
} else {
|
|
650
|
+
renderer.write(` ${color4.dim("Saved providers:")}
|
|
651
|
+
`);
|
|
652
|
+
let idx = 1;
|
|
653
|
+
for (const id of ids) {
|
|
654
|
+
const cfg = providers[id];
|
|
655
|
+
if (!cfg) continue;
|
|
656
|
+
renderProviderLine(renderer, id, cfg, idx);
|
|
657
|
+
idx++;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
renderer.write(`
|
|
661
|
+
${color4.dim("Actions:")}
|
|
662
|
+
`);
|
|
663
|
+
renderer.write(` ${color4.bold("a")} Add a provider (from catalog)
|
|
664
|
+
`);
|
|
665
|
+
renderer.write(
|
|
666
|
+
` ${color4.bold("l")} Add a local server ${color4.dim("(OmniRoute / Ollama / vLLM / LM Studio)")}
|
|
667
|
+
`
|
|
668
|
+
);
|
|
669
|
+
renderer.write(` ${color4.bold("c")} Add a custom provider
|
|
670
|
+
`);
|
|
671
|
+
renderer.write(
|
|
672
|
+
` ${color4.bold("s")} Login with OAuth ${color4.dim("(ChatGPT / Claude / Copilot)")}
|
|
673
|
+
`
|
|
674
|
+
);
|
|
675
|
+
if (ids.length > 0) {
|
|
676
|
+
renderer.write(
|
|
677
|
+
` ${color4.dim("1-")}${color4.dim(String(ids.length))} ${color4.bold("Manage a provider")}
|
|
678
|
+
`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
renderer.write(` ${color4.bold("q")} Quit
|
|
682
|
+
`);
|
|
683
|
+
}
|
|
684
|
+
async function readKeyInput(deps, intent) {
|
|
685
|
+
const key = (await deps.reader.readSecret(
|
|
686
|
+
` ${color4.amber("?")} ${intent} ${color4.dim("(hidden, paste OK)")}: `
|
|
687
|
+
)).trim();
|
|
688
|
+
if (!key) {
|
|
689
|
+
deps.renderer.writeError("No key entered.");
|
|
690
|
+
return void 0;
|
|
691
|
+
}
|
|
692
|
+
return key;
|
|
693
|
+
}
|
|
694
|
+
async function confirm(deps, question) {
|
|
695
|
+
const answer = (await deps.reader.readLine(` ${color4.amber("?")} ${question} ${color4.dim("[y/N/q]")} `)).trim().toLowerCase();
|
|
696
|
+
if (answer === "q" || answer === "quit") return null;
|
|
697
|
+
return answer === "y" || answer === "yes";
|
|
698
|
+
}
|
|
699
|
+
function suggestLabel(usedLabels) {
|
|
700
|
+
const candidate = "default";
|
|
701
|
+
if (!usedLabels.has(candidate)) return candidate;
|
|
702
|
+
let n = 2;
|
|
703
|
+
while (usedLabels.has(`key${n}`)) n++;
|
|
704
|
+
return `key${n}`;
|
|
705
|
+
}
|
|
706
|
+
function validateFamily(raw) {
|
|
707
|
+
const valid = ["anthropic", "openai", "openai-compatible", "google"];
|
|
708
|
+
return valid.includes(raw) ? raw : null;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// src/auth-menu/add-provider.ts
|
|
712
|
+
import { atomicWrite, color as color5 } from "@wrongstack/core/utils";
|
|
713
|
+
import * as fs from "node:fs/promises";
|
|
714
|
+
|
|
715
|
+
// src/provider-id.ts
|
|
716
|
+
function nextCustomProviderId(existing) {
|
|
717
|
+
const taken = new Set(existing);
|
|
718
|
+
let n = 1;
|
|
719
|
+
while (taken.has(`custom-${n}`)) n++;
|
|
720
|
+
return `custom-${n}`;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/auth-menu/add-provider.ts
|
|
724
|
+
async function addFromCatalog(deps) {
|
|
725
|
+
let catalog = [];
|
|
726
|
+
try {
|
|
727
|
+
catalog = (await deps.modelsRegistry.listProviders()).filter((p) => p.family !== "unsupported");
|
|
728
|
+
} catch {
|
|
729
|
+
deps.renderer.writeWarning("Catalog unavailable \u2014 falling back to manual entry.\n");
|
|
730
|
+
}
|
|
731
|
+
if (catalog.length === 0) {
|
|
732
|
+
return addManualEntry(deps);
|
|
733
|
+
}
|
|
734
|
+
const saved = new Set(Object.keys(await loadProviders(deps)));
|
|
735
|
+
deps.renderer.write(
|
|
736
|
+
color5.dim(
|
|
737
|
+
` Catalog: ${catalog.length} providers. Type to filter, \u2191/\u2193 navigate, Enter to select.
|
|
738
|
+
`
|
|
739
|
+
)
|
|
740
|
+
);
|
|
741
|
+
deps.renderer.write(` ${color5.dim("\u25C9 already saved \u25CB no key yet")}
|
|
742
|
+
|
|
743
|
+
`);
|
|
744
|
+
renderOAuthLoginOptions(deps);
|
|
745
|
+
deps.renderer.write("\n");
|
|
746
|
+
const chosen = await runLiveProviderPicker(catalog, saved);
|
|
747
|
+
if (!chosen) {
|
|
748
|
+
deps.renderer.write(
|
|
749
|
+
`
|
|
750
|
+
${color5.dim("OAuth login options:")} ${color5.dim("chatgpt")}, ${color5.dim("claude")}, or ${color5.dim("copilot")}?
|
|
751
|
+
`
|
|
752
|
+
);
|
|
753
|
+
const answer = (await deps.reader.readLine(
|
|
754
|
+
` ${color5.amber("?")} OAuth or q to quit ${color5.dim("[chatgpt/claude/copilot]")}: `
|
|
755
|
+
)).trim();
|
|
756
|
+
if (answer && await runOAuthLoginChoice(deps, answer, { allowNumeric: false })) {
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
return addKeyForCatalogProvider(deps, chosen);
|
|
762
|
+
}
|
|
763
|
+
async function addKeyForCatalogProvider(deps, chosen) {
|
|
764
|
+
deps.renderer.write(
|
|
765
|
+
color5.dim(`
|
|
766
|
+
Defaults from models.dev \u2014 press Enter to keep, or type overrides.
|
|
767
|
+
`)
|
|
768
|
+
);
|
|
769
|
+
const famRaw = (await deps.reader.readLine(
|
|
770
|
+
` ${color5.amber("?")} Family ${color5.dim(`[${chosen.family}]`)} ${color5.dim("(q to quit)")}: `
|
|
771
|
+
)).trim();
|
|
772
|
+
if (famRaw === "q") return false;
|
|
773
|
+
let family = chosen.family;
|
|
774
|
+
if (famRaw) {
|
|
775
|
+
const validated = validateFamily(famRaw);
|
|
776
|
+
if (!validated) {
|
|
777
|
+
deps.renderer.writeError(
|
|
778
|
+
`Invalid family: "${famRaw}" (must be: anthropic, openai, openai-compatible, google).`
|
|
779
|
+
);
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
family = validated;
|
|
783
|
+
}
|
|
784
|
+
const baseRaw = (await deps.reader.readLine(
|
|
785
|
+
` ${color5.amber("?")} Base URL ${color5.dim(`[${chosen.apiBase ?? "unset"}]`)} ${color5.dim("(q to quit)")}: `
|
|
786
|
+
)).trim();
|
|
787
|
+
if (baseRaw === "q") return false;
|
|
788
|
+
const baseUrl = baseRaw || chosen.apiBase;
|
|
789
|
+
const providersNow = await loadProviders(deps);
|
|
790
|
+
let suggestedAlias = chosen.id;
|
|
791
|
+
if (family !== chosen.family) {
|
|
792
|
+
let candidate = `${chosen.id}-${family}`;
|
|
793
|
+
let n = 2;
|
|
794
|
+
while (providersNow[candidate]) {
|
|
795
|
+
candidate = `${chosen.id}-${family}-${n}`;
|
|
796
|
+
n++;
|
|
797
|
+
}
|
|
798
|
+
suggestedAlias = candidate;
|
|
799
|
+
}
|
|
800
|
+
const aliasRaw = (await deps.reader.readLine(
|
|
801
|
+
` ${color5.amber("?")} Save as alias ${color5.dim(`[${suggestedAlias}]`)} ${color5.dim("(used with --provider <alias>)")}: `
|
|
802
|
+
)).trim();
|
|
803
|
+
const alias = aliasRaw || suggestedAlias;
|
|
804
|
+
const existing = providersNow[alias];
|
|
805
|
+
if (existing) {
|
|
806
|
+
const sameFamily = (existing.family ?? chosen.family) === family;
|
|
807
|
+
const sameBase = (existing.baseUrl ?? chosen.apiBase) === baseUrl;
|
|
808
|
+
if (!sameFamily || !sameBase) {
|
|
809
|
+
deps.renderer.writeError(
|
|
810
|
+
`Alias "${alias}" already exists with different family/baseUrl.
|
|
811
|
+
Existing: family=${existing.family ?? "(unset)"}, baseUrl=${existing.baseUrl ?? "(unset)"}
|
|
812
|
+
New: family=${family}, baseUrl=${baseUrl ?? "(unset)"}
|
|
813
|
+
Pick a different alias to keep them separate.`
|
|
814
|
+
);
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return addKeyForProvider(alias, deps, {
|
|
819
|
+
type: chosen.id,
|
|
820
|
+
family,
|
|
821
|
+
baseUrl,
|
|
822
|
+
envVars: chosen.envVars
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
async function addCustomProvider(deps) {
|
|
826
|
+
deps.renderer.write(
|
|
827
|
+
`
|
|
828
|
+
${color5.bold("Custom provider")} ${color5.dim("\u2014 for local models or proxies not in the catalog.")}
|
|
829
|
+
`
|
|
830
|
+
);
|
|
831
|
+
const providersNow = await loadProviders(deps);
|
|
832
|
+
const idRaw = (await deps.reader.readLine(
|
|
833
|
+
` ${color5.amber("?")} Provider id ${color5.dim('(e.g. "local-llama", "my-proxy"; blank = auto custom-N, q to quit)')}: `
|
|
834
|
+
)).trim();
|
|
835
|
+
if (idRaw === "q") return false;
|
|
836
|
+
let type = idRaw;
|
|
837
|
+
if (!type) {
|
|
838
|
+
type = nextCustomProviderId(Object.keys(providersNow));
|
|
839
|
+
deps.renderer.write(` ${color5.dim("Using generated id:")} ${color5.bold(type)}
|
|
840
|
+
`);
|
|
841
|
+
} else if (providersNow[type]) {
|
|
842
|
+
deps.renderer.writeWarning(`"${type}" already exists. Pick it from the main menu to edit.`);
|
|
843
|
+
return false;
|
|
844
|
+
}
|
|
845
|
+
const familyRaw = (await deps.reader.readLine(
|
|
846
|
+
` ${color5.amber("?")} Wire family ${color5.dim("(anthropic | openai | openai-compatible | google)")} ${color5.dim("(q to quit)")}: `
|
|
847
|
+
)).trim();
|
|
848
|
+
if (familyRaw === "q") return false;
|
|
849
|
+
const family = validateFamily(familyRaw);
|
|
850
|
+
if (!family) {
|
|
851
|
+
deps.renderer.writeError(`Invalid family: "${familyRaw}"`);
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
const baseUrl = (await deps.reader.readLine(
|
|
855
|
+
` ${color5.amber("?")} Base URL ${color5.dim("(e.g. http://localhost:11434/v1, optional)")}: `
|
|
856
|
+
)).trim();
|
|
857
|
+
const modelsRaw = (await deps.reader.readLine(
|
|
858
|
+
` ${color5.amber("?")} Model ids ${color5.dim("(comma-separated, optional)")}: `
|
|
859
|
+
)).trim();
|
|
860
|
+
const models = modelsRaw ? modelsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
861
|
+
const envVarsRaw = (await deps.reader.readLine(
|
|
862
|
+
` ${color5.amber("?")} Env var names ${color5.dim("(comma-separated, optional)")}: `
|
|
863
|
+
)).trim();
|
|
864
|
+
const envVars = envVarsRaw ? envVarsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
865
|
+
return addKeyForProvider(type, deps, {
|
|
866
|
+
type,
|
|
867
|
+
family,
|
|
868
|
+
...baseUrl ? { baseUrl } : {},
|
|
869
|
+
...models ? { models } : {},
|
|
870
|
+
...envVars ? { envVars } : {}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
async function addManualEntry(deps) {
|
|
874
|
+
const pid = (await deps.reader.readLine(` ${color5.amber("?")} Provider id ${color5.dim("[q to quit]")}: `)).trim();
|
|
875
|
+
if (!pid || pid === "q") return false;
|
|
876
|
+
const famRaw = (await deps.reader.readLine(
|
|
877
|
+
` ${color5.amber("?")} Family ${color5.dim("(anthropic/openai/openai-compatible/google)")}: `
|
|
878
|
+
)).trim();
|
|
879
|
+
const family = validateFamily(famRaw);
|
|
880
|
+
if (!family) {
|
|
881
|
+
deps.renderer.writeError(`Invalid family: "${famRaw}"`);
|
|
882
|
+
return false;
|
|
883
|
+
}
|
|
884
|
+
const baseUrl = (await deps.reader.readLine(` ${color5.amber("?")} Base URL ${color5.dim("(optional)")}: `)).trim();
|
|
885
|
+
return addKeyForProvider(pid, deps, {
|
|
886
|
+
type: pid,
|
|
887
|
+
family,
|
|
888
|
+
...baseUrl ? { baseUrl } : {}
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
async function addKeyForProvider(providerId, deps, template) {
|
|
892
|
+
const providers = await loadProviders(deps);
|
|
893
|
+
const existing = providers[providerId];
|
|
894
|
+
const existingKeys = existing ? normalizeKeys(existing) : [];
|
|
895
|
+
const usedLabels = new Set(existingKeys.map((k) => k.label));
|
|
896
|
+
const label = await promptForLabel(deps, usedLabels);
|
|
897
|
+
if (!label) return false;
|
|
898
|
+
const apiKey = await readKeyInput(deps, `API key for ${providerId}/${label}`);
|
|
899
|
+
if (!apiKey) return false;
|
|
900
|
+
await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {
|
|
901
|
+
const existingProv = all[providerId] ?? {
|
|
902
|
+
type: providerId,
|
|
903
|
+
...template
|
|
904
|
+
};
|
|
905
|
+
if (!existingProv.type) existingProv.type = providerId;
|
|
906
|
+
if (!existingProv.family && template.family) {
|
|
907
|
+
existingProv.family = template.family;
|
|
908
|
+
}
|
|
909
|
+
if (!existingProv.baseUrl && template.baseUrl) {
|
|
910
|
+
existingProv.baseUrl = template.baseUrl;
|
|
911
|
+
}
|
|
912
|
+
if (!existingProv.envVars && template.envVars) {
|
|
913
|
+
existingProv.envVars = template.envVars;
|
|
914
|
+
}
|
|
915
|
+
const list = normalizeKeys(existingProv);
|
|
916
|
+
list.push({ label, apiKey, createdAt: nowIso() });
|
|
917
|
+
writeKeysBack(existingProv, list);
|
|
918
|
+
if (!existingProv.activeKey) existingProv.activeKey = label;
|
|
919
|
+
all[providerId] = existingProv;
|
|
920
|
+
}, deps.profileConfigPath);
|
|
921
|
+
deps.renderer.write(
|
|
922
|
+
` ${color5.green("\u2713")} Saved ${color5.bold(providerId)}/${color5.bold(label)}.
|
|
923
|
+
`
|
|
924
|
+
);
|
|
925
|
+
deps.renderer.write(color5.dim(` Launch: wstack --provider ${providerId} "<task>"
|
|
926
|
+
`));
|
|
927
|
+
try {
|
|
928
|
+
await adoptAsDefaultIfUnset(deps, providerId, template);
|
|
929
|
+
} catch {
|
|
930
|
+
}
|
|
931
|
+
return true;
|
|
932
|
+
}
|
|
933
|
+
async function resolveDefaultModelId(deps, providerId, template) {
|
|
934
|
+
if (template.models && template.models.length > 0) return template.models[0];
|
|
935
|
+
const catalogId = template.type && template.type !== providerId ? template.type : providerId;
|
|
936
|
+
const provider = await deps.modelsRegistry.getProvider(catalogId).catch(() => void 0);
|
|
937
|
+
return provider?.models?.[0]?.id;
|
|
938
|
+
}
|
|
939
|
+
async function adoptAsDefaultIfUnset(deps, providerId, template) {
|
|
940
|
+
const targetPath = deps.profileConfigPath;
|
|
941
|
+
let cfg = {};
|
|
942
|
+
try {
|
|
943
|
+
cfg = JSON.parse(await fs.readFile(targetPath, "utf8"));
|
|
944
|
+
} catch {
|
|
945
|
+
}
|
|
946
|
+
if (cfg["provider"] && cfg["model"]) return;
|
|
947
|
+
const modelId = await resolveDefaultModelId(deps, providerId, template);
|
|
948
|
+
if (!modelId) return;
|
|
949
|
+
cfg["provider"] = providerId;
|
|
950
|
+
cfg["model"] = modelId;
|
|
951
|
+
await atomicWrite(targetPath, JSON.stringify(cfg, null, 2), { mode: 384 });
|
|
952
|
+
deps.renderer.write(
|
|
953
|
+
` ${color5.green("\u2713")} Set ${color5.bold(providerId)}/${color5.bold(modelId)} as the default.
|
|
954
|
+
`
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
async function promptForLabel(deps, usedLabels) {
|
|
958
|
+
const defaultLabel = suggestLabel(usedLabels);
|
|
959
|
+
const labelRaw = (await deps.reader.readLine(
|
|
960
|
+
` ${color5.amber("?")} Label for this key ${color5.dim(`[${defaultLabel}]`)}: `
|
|
961
|
+
)).trim();
|
|
962
|
+
const label = labelRaw || defaultLabel;
|
|
963
|
+
if (usedLabels.has(label)) {
|
|
964
|
+
deps.renderer.writeError(`Label "${label}" is already used. Use update (u) instead.`);
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
return label;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/auth-menu/local.ts
|
|
971
|
+
import { color as color6 } from "@wrongstack/core/utils";
|
|
972
|
+
import { DefaultSecretScrubber as DefaultSecretScrubber2 } from "@wrongstack/core/security";
|
|
973
|
+
import { probeLocalLlm } from "@wrongstack/runtime/probe";
|
|
974
|
+
|
|
975
|
+
// src/auth-menu/auth-menu-audit.ts
|
|
976
|
+
import { DefaultSecretScrubber } from "@wrongstack/core/security";
|
|
977
|
+
var NOOP_AUDIT_LOGGER = {
|
|
978
|
+
emit: () => {
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
function decideAuthLocalEvents(opts) {
|
|
982
|
+
const events = [];
|
|
983
|
+
if (opts.probeSkip) {
|
|
984
|
+
events.push({
|
|
985
|
+
type: "auth.local.probe_skip",
|
|
986
|
+
providerId: opts.providerId,
|
|
987
|
+
baseUrl: opts.baseUrl,
|
|
988
|
+
requestedShape: opts.probeSkip.shape
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
if (opts.probeFailedSave) {
|
|
992
|
+
events.push({
|
|
993
|
+
type: "auth.local.probe_failed_save",
|
|
994
|
+
providerId: opts.providerId,
|
|
995
|
+
baseUrl: opts.baseUrl,
|
|
996
|
+
probeStatus: opts.probeFailedSave.status,
|
|
997
|
+
...opts.probeFailedSave.detail !== void 0 ? { probeDetail: opts.probeFailedSave.detail } : {}
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
if (opts.newModels === null) return events;
|
|
1001
|
+
const hadPriorAllowlist = opts.previousModels !== void 0 && opts.previousModels.length > 0;
|
|
1002
|
+
if (hadPriorAllowlist && opts.newModels.length === 0) {
|
|
1003
|
+
events.push({
|
|
1004
|
+
type: "auth.local.clear",
|
|
1005
|
+
providerId: opts.providerId,
|
|
1006
|
+
baseUrl: opts.baseUrl,
|
|
1007
|
+
previousModels: opts.previousModels
|
|
1008
|
+
});
|
|
1009
|
+
return events;
|
|
1010
|
+
}
|
|
1011
|
+
if (!hadPriorAllowlist && opts.newModels.length > 0) {
|
|
1012
|
+
if (opts.previousModels !== void 0) {
|
|
1013
|
+
events.push({
|
|
1014
|
+
type: "auth.local.undo",
|
|
1015
|
+
providerId: opts.providerId,
|
|
1016
|
+
baseUrl: opts.baseUrl,
|
|
1017
|
+
restoredModels: [...opts.newModels]
|
|
1018
|
+
});
|
|
1019
|
+
} else {
|
|
1020
|
+
events.push({
|
|
1021
|
+
type: "auth.local.add",
|
|
1022
|
+
providerId: opts.providerId,
|
|
1023
|
+
baseUrl: opts.baseUrl,
|
|
1024
|
+
models: [...opts.newModels],
|
|
1025
|
+
...opts.keyLabel !== void 0 ? { keyLabel: opts.keyLabel } : {}
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
return events;
|
|
1029
|
+
}
|
|
1030
|
+
if (hadPriorAllowlist && opts.newModels.length > 0) {
|
|
1031
|
+
events.push({
|
|
1032
|
+
type: "auth.local.add",
|
|
1033
|
+
providerId: opts.providerId,
|
|
1034
|
+
baseUrl: opts.baseUrl,
|
|
1035
|
+
models: [...opts.newModels],
|
|
1036
|
+
...opts.keyLabel !== void 0 ? { keyLabel: opts.keyLabel } : {}
|
|
1037
|
+
});
|
|
1038
|
+
return events;
|
|
1039
|
+
}
|
|
1040
|
+
return events;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/auth-menu/local.ts
|
|
1044
|
+
var PRESET_BY_ID = new Map(LOCAL_LLM_PRESETS.map((p) => [p.id, p]));
|
|
1045
|
+
async function runAuthLocal(deps, opts = {}) {
|
|
1046
|
+
const scrubber = deps.secretScrubber ?? new DefaultSecretScrubber2();
|
|
1047
|
+
const audit = opts.audit ?? NOOP_AUDIT_LOGGER;
|
|
1048
|
+
const preset = opts.name ? PRESET_BY_ID.get(opts.name.toLowerCase()) : void 0;
|
|
1049
|
+
if (opts.name && !preset) {
|
|
1050
|
+
deps.renderer.writeError(
|
|
1051
|
+
`Unknown local server "${opts.name}". Valid: ${LOCAL_LLM_PRESETS.map((p) => p.id).join(", ")}.`
|
|
1052
|
+
);
|
|
1053
|
+
return 1;
|
|
1054
|
+
}
|
|
1055
|
+
const chosen = preset ?? await pickLocalPreset(deps);
|
|
1056
|
+
if (!chosen) {
|
|
1057
|
+
return 0;
|
|
1058
|
+
}
|
|
1059
|
+
const baseUrl = opts.baseUrl?.trim() || chosen.defaultBaseUrl;
|
|
1060
|
+
let apiKey;
|
|
1061
|
+
if (opts.apiKey !== void 0) {
|
|
1062
|
+
apiKey = opts.apiKey.trim() || void 0;
|
|
1063
|
+
} else if (chosen.noAuth) {
|
|
1064
|
+
apiKey = void 0;
|
|
1065
|
+
} else if (opts.skipKey) {
|
|
1066
|
+
apiKey = void 0;
|
|
1067
|
+
} else {
|
|
1068
|
+
apiKey = await promptOptionalKey(deps, chosen);
|
|
1069
|
+
}
|
|
1070
|
+
let probe;
|
|
1071
|
+
let probeFailedAndSaved = false;
|
|
1072
|
+
if (!opts.noProbe) {
|
|
1073
|
+
probe = await probeLocalLlm({
|
|
1074
|
+
baseUrl,
|
|
1075
|
+
apiKey,
|
|
1076
|
+
noAuth: chosen.noAuth,
|
|
1077
|
+
presetLabel: chosen.label,
|
|
1078
|
+
scrubber,
|
|
1079
|
+
fetchImpl: opts.fetchImpl,
|
|
1080
|
+
timeoutMs: opts.probeTimeoutMs
|
|
1081
|
+
});
|
|
1082
|
+
renderProbeResult(deps, probe);
|
|
1083
|
+
if (opts.probeOnly) {
|
|
1084
|
+
return 0;
|
|
1085
|
+
}
|
|
1086
|
+
if (!probe.ok) {
|
|
1087
|
+
if (chosen.noAuth) {
|
|
1088
|
+
deps.renderer.write(
|
|
1089
|
+
color6.dim(
|
|
1090
|
+
` Saving ${chosen.label} anyway \u2014 keyless local gateway needs no key and discovers models when it comes up.
|
|
1091
|
+
`
|
|
1092
|
+
)
|
|
1093
|
+
);
|
|
1094
|
+
probeFailedAndSaved = true;
|
|
1095
|
+
} else {
|
|
1096
|
+
const saveAnyway = await promptSaveAnyway(deps);
|
|
1097
|
+
if (!saveAnyway) {
|
|
1098
|
+
deps.renderer.write(color6.dim(" Cancelled. Nothing saved.\n"));
|
|
1099
|
+
return 0;
|
|
1100
|
+
}
|
|
1101
|
+
probeFailedAndSaved = true;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
const resolvedModels = resolveModelList(opts.models, probe, scrubber);
|
|
1106
|
+
const label = opts.label?.trim() || "default";
|
|
1107
|
+
const providers = await loadProviders(deps);
|
|
1108
|
+
const existing = providers[chosen.id];
|
|
1109
|
+
const previousModels = existing?.models ? [...existing.models] : existing?.models === void 0 ? void 0 : [...existing.models];
|
|
1110
|
+
const usedLabels = new Set(existing ? normalizeKeys(existing).map((k) => k.label) : []);
|
|
1111
|
+
let finalLabel = label;
|
|
1112
|
+
if (usedLabels.has(finalLabel)) {
|
|
1113
|
+
let n = 2;
|
|
1114
|
+
while (usedLabels.has(`${label}-${n}`)) n++;
|
|
1115
|
+
finalLabel = `${label}-${n}`;
|
|
1116
|
+
deps.renderer.writeInfo(`Label collided; saving as "${finalLabel}".`);
|
|
1117
|
+
}
|
|
1118
|
+
try {
|
|
1119
|
+
await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {
|
|
1120
|
+
const p = all[chosen.id] ?? { type: chosen.id };
|
|
1121
|
+
if (!p.type) p.type = chosen.id;
|
|
1122
|
+
if (!p.family) p.family = "openai-compatible";
|
|
1123
|
+
if (!p.baseUrl) p.baseUrl = baseUrl;
|
|
1124
|
+
if (!p.envVars) p.envVars = [];
|
|
1125
|
+
if (resolvedModels !== null) {
|
|
1126
|
+
p.models = [...resolvedModels];
|
|
1127
|
+
}
|
|
1128
|
+
if (apiKey) {
|
|
1129
|
+
const list = normalizeKeys(p);
|
|
1130
|
+
list.push({ label: finalLabel, apiKey, createdAt: nowIso() });
|
|
1131
|
+
writeKeysBack(p, list);
|
|
1132
|
+
if (!p.activeKey) p.activeKey = finalLabel;
|
|
1133
|
+
}
|
|
1134
|
+
all[chosen.id] = p;
|
|
1135
|
+
}, deps.profileConfigPath);
|
|
1136
|
+
} catch (err) {
|
|
1137
|
+
deps.renderer.writeError(`Failed to save ${chosen.id}: ${err.message}`);
|
|
1138
|
+
return 1;
|
|
1139
|
+
}
|
|
1140
|
+
const events = decideAuthLocalEvents({
|
|
1141
|
+
providerId: chosen.id,
|
|
1142
|
+
baseUrl,
|
|
1143
|
+
previousModels,
|
|
1144
|
+
newModels: resolvedModels,
|
|
1145
|
+
keyLabel: apiKey ? finalLabel : void 0,
|
|
1146
|
+
probeFailedSave: probeFailedAndSaved && probe ? {
|
|
1147
|
+
status: probe.status,
|
|
1148
|
+
...probe.detail !== void 0 ? { detail: probe.detail } : {}
|
|
1149
|
+
} : void 0
|
|
1150
|
+
});
|
|
1151
|
+
for (const event of events) {
|
|
1152
|
+
audit.emit(event);
|
|
1153
|
+
}
|
|
1154
|
+
if (apiKey) {
|
|
1155
|
+
deps.renderer.write(
|
|
1156
|
+
` ${color6.green("\u2713")} Saved ${color6.bold(chosen.id)}/${color6.bold(finalLabel)} \u2192 ${color6.cyan(baseUrl)}
|
|
1157
|
+
`
|
|
1158
|
+
);
|
|
1159
|
+
} else {
|
|
1160
|
+
deps.renderer.write(
|
|
1161
|
+
` ${color6.green("\u2713")} Saved ${color6.bold(chosen.id)} (no key) \u2192 ${color6.cyan(baseUrl)}
|
|
1162
|
+
`
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
const firstModel = resolvedModels && resolvedModels.length > 0 ? resolvedModels[0] : null;
|
|
1166
|
+
const modelHint = firstModel ? color6.cyan(firstModel) : color6.dim("<model-id>");
|
|
1167
|
+
deps.renderer.write(
|
|
1168
|
+
color6.dim(
|
|
1169
|
+
` Launch: wstack --provider ${chosen.id} --model ${modelHint} "<task>"
|
|
1170
|
+
`
|
|
1171
|
+
)
|
|
1172
|
+
);
|
|
1173
|
+
return 0;
|
|
1174
|
+
}
|
|
1175
|
+
function resolveModelList(models, probe, scrubber) {
|
|
1176
|
+
if (models === void 0) return null;
|
|
1177
|
+
const trimmed = models.trim();
|
|
1178
|
+
if (trimmed.length === 0) return [];
|
|
1179
|
+
const hasProbeIds = probe?.status === "ok" && (probe.modelIds?.length ?? 0) > 0;
|
|
1180
|
+
const probeIds = hasProbeIds ? probe?.modelIds ?? [] : [];
|
|
1181
|
+
if (trimmed === "first") {
|
|
1182
|
+
if (probeIds.length === 0) return null;
|
|
1183
|
+
return [scrubber.scrub(probeIds[0])];
|
|
1184
|
+
}
|
|
1185
|
+
if (/^\d+$/.test(trimmed)) {
|
|
1186
|
+
if (probeIds.length === 0) return null;
|
|
1187
|
+
const n = Math.min(Number.parseInt(trimmed, 10), probeIds.length);
|
|
1188
|
+
return probeIds.slice(0, n).map((id) => scrubber.scrub(id));
|
|
1189
|
+
}
|
|
1190
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1191
|
+
const out = [];
|
|
1192
|
+
for (const raw of trimmed.split(",")) {
|
|
1193
|
+
const id = scrubber.scrub(raw.trim());
|
|
1194
|
+
if (id.length === 0) continue;
|
|
1195
|
+
if (seen.has(id)) continue;
|
|
1196
|
+
seen.add(id);
|
|
1197
|
+
out.push(id);
|
|
1198
|
+
}
|
|
1199
|
+
return out;
|
|
1200
|
+
}
|
|
1201
|
+
function renderProbeResult(deps, probe, opts = {}) {
|
|
1202
|
+
const label = opts.presetLabel ? ` ${color6.bold(opts.presetLabel)}` : "";
|
|
1203
|
+
const elapsed = probe.elapsedMs !== void 0 ? ` ${color6.dim(`(${probe.elapsedMs}ms)`)}` : "";
|
|
1204
|
+
switch (probe.status) {
|
|
1205
|
+
case "ok": {
|
|
1206
|
+
const models = probe.modelCount !== void 0 ? color6.dim(`, ${probe.modelCount} model${probe.modelCount === 1 ? "" : "s"}`) : "";
|
|
1207
|
+
deps.renderer.write(
|
|
1208
|
+
` ${color6.green("\u25CF")} ${label} health probe ok${models}${elapsed}
|
|
1209
|
+
`
|
|
1210
|
+
);
|
|
1211
|
+
break;
|
|
1212
|
+
}
|
|
1213
|
+
case "timeout": {
|
|
1214
|
+
deps.renderer.write(
|
|
1215
|
+
` ${color6.red("\u25CF")} ${label} health probe timed out${elapsed}
|
|
1216
|
+
${color6.dim("The server did not respond within the timeout.")}
|
|
1217
|
+
${color6.dim("Is the local server running? Check the port and try again.")}
|
|
1218
|
+
`
|
|
1219
|
+
);
|
|
1220
|
+
break;
|
|
1221
|
+
}
|
|
1222
|
+
case "unreachable": {
|
|
1223
|
+
const detail = probe.detail ? ` ${color6.dim(`(${probe.detail})`)}` : "";
|
|
1224
|
+
deps.renderer.write(
|
|
1225
|
+
` ${color6.red("\u25CF")} ${label} server unreachable${detail}
|
|
1226
|
+
${color6.dim("Could not connect. Common causes:")}
|
|
1227
|
+
${color6.dim(" - The local server is not running")}
|
|
1228
|
+
${color6.dim(" - The port is blocked by a firewall")}
|
|
1229
|
+
${color6.dim(" - The base URL is wrong")}
|
|
1230
|
+
`
|
|
1231
|
+
);
|
|
1232
|
+
break;
|
|
1233
|
+
}
|
|
1234
|
+
case "http_error": {
|
|
1235
|
+
const status = probe.httpStatus !== void 0 ? ` HTTP ${probe.httpStatus}` : "";
|
|
1236
|
+
const detail = probe.detail ? ` ${color6.dim(`\u2014 ${probe.detail}`)}` : "";
|
|
1237
|
+
deps.renderer.write(
|
|
1238
|
+
` ${color6.yellow("\u25CF")} ${label} health probe got${status}${detail}
|
|
1239
|
+
`
|
|
1240
|
+
);
|
|
1241
|
+
break;
|
|
1242
|
+
}
|
|
1243
|
+
case "invalid_response": {
|
|
1244
|
+
deps.renderer.write(
|
|
1245
|
+
` ${color6.yellow("\u25CF")} ${label} health probe returned an unexpected shape
|
|
1246
|
+
` + (probe.detail ? ` ${color6.dim(probe.detail)}
|
|
1247
|
+
` : "")
|
|
1248
|
+
);
|
|
1249
|
+
break;
|
|
1250
|
+
}
|
|
1251
|
+
case "skipped": {
|
|
1252
|
+
break;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
async function promptSaveAnyway(deps) {
|
|
1257
|
+
const answer = (await deps.reader.readLine(
|
|
1258
|
+
`
|
|
1259
|
+
${color6.amber("?")} ${color6.bold("Save anyway?")} ${color6.dim("[y/N]")}: `
|
|
1260
|
+
)).trim().toLowerCase();
|
|
1261
|
+
return answer === "y" || answer === "yes";
|
|
1262
|
+
}
|
|
1263
|
+
async function pickLocalPreset(deps) {
|
|
1264
|
+
deps.renderer.write(
|
|
1265
|
+
`
|
|
1266
|
+
${color6.bold("Local LLM servers")} ${color6.dim("\u2014 pick one to pre-fill the base URL")}
|
|
1267
|
+
|
|
1268
|
+
`
|
|
1269
|
+
);
|
|
1270
|
+
let idx = 1;
|
|
1271
|
+
for (const entry of LOCAL_LLM_PRESETS) {
|
|
1272
|
+
const auth = entry.noAuth ? color6.dim("(no auth)") : color6.dim("(optional auth)");
|
|
1273
|
+
deps.renderer.write(
|
|
1274
|
+
` ${color6.dim(`${idx}.`.padStart(4))} ${color6.bold(entry.label.padEnd(12))} ${color6.cyan(entry.defaultBaseUrl.padEnd(34))} ${auth}
|
|
1275
|
+
`
|
|
1276
|
+
);
|
|
1277
|
+
deps.renderer.write(
|
|
1278
|
+
` ${color6.dim(entry.hint)}
|
|
1279
|
+
`
|
|
1280
|
+
);
|
|
1281
|
+
idx++;
|
|
1282
|
+
}
|
|
1283
|
+
const answer = (await deps.reader.readLine(
|
|
1284
|
+
`
|
|
1285
|
+
${color6.amber("?")} Pick ${color6.dim(`(1-${LOCAL_LLM_PRESETS.length}, q to quit)`)}: `
|
|
1286
|
+
)).trim().toLowerCase();
|
|
1287
|
+
if (!answer || answer === "q" || answer === "quit") return void 0;
|
|
1288
|
+
const num = Number.parseInt(answer, 10);
|
|
1289
|
+
if (!Number.isNaN(num) && num >= 1 && num <= LOCAL_LLM_PRESETS.length) {
|
|
1290
|
+
return LOCAL_LLM_PRESETS[num - 1];
|
|
1291
|
+
}
|
|
1292
|
+
const byId = LOCAL_LLM_PRESETS.find((p) => p.id === answer);
|
|
1293
|
+
if (byId) return byId;
|
|
1294
|
+
deps.renderer.writeError(`Unknown selection: "${answer}"`);
|
|
1295
|
+
return void 0;
|
|
1296
|
+
}
|
|
1297
|
+
async function promptOptionalKey(deps, preset) {
|
|
1298
|
+
const answer = (await deps.reader.readSecret(
|
|
1299
|
+
` ${color6.amber("?")} ${color6.bold(preset.label)} API key ${color6.dim("(hidden \u2014 Enter to skip if auth is disabled on the server)")}: `
|
|
1300
|
+
)).trim();
|
|
1301
|
+
if (!answer) return void 0;
|
|
1302
|
+
return answer;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
export {
|
|
1306
|
+
loadProviders,
|
|
1307
|
+
renderProviderHeader,
|
|
1308
|
+
renderActions,
|
|
1309
|
+
renderTopMenu,
|
|
1310
|
+
readKeyInput,
|
|
1311
|
+
confirm,
|
|
1312
|
+
validateFamily,
|
|
1313
|
+
runClaudeOAuthLogin,
|
|
1314
|
+
runCopilotOAuthLogin,
|
|
1315
|
+
resolveOAuthKind,
|
|
1316
|
+
runOAuthLoginKind,
|
|
1317
|
+
runOAuthLoginMenu,
|
|
1318
|
+
nextCustomProviderId,
|
|
1319
|
+
addFromCatalog,
|
|
1320
|
+
addKeyForCatalogProvider,
|
|
1321
|
+
addCustomProvider,
|
|
1322
|
+
addKeyForProvider,
|
|
1323
|
+
runAuthLocal
|
|
1324
|
+
};
|
|
1325
|
+
//# sourceMappingURL=chunk-JBTFWWKV.js.map
|