@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,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/plugin-management.ts"],
|
|
4
|
+
"sourcesContent": ["import * as fs from 'node:fs/promises';\nimport { atomicWrite } from '@wrongstack/core/utils';\nimport type { Config, PluginConfig, PluginManagerConfig } from '@wrongstack/core/types';\nimport { OFFICIAL_PLUGIN_AUDIT_ENTRIES } from '@wrongstack/plugins/audit';\n\nexport interface PluginAuditEntry {\n name: string;\n risk: 'low' | 'medium' | 'high';\n summary: string;\n defaultState: 'active' | 'inactive';\n canDisable: boolean;\n}\n\n/** Host-owned plugins that are not published by @wrongstack/plugins. */\nconst HOST_PLUGIN_AUDIT_ENTRIES: readonly PluginAuditEntry[] = [\n {\n name: 'wstack-prompts',\n risk: 'medium',\n summary: 'Prompt library and prompt authoring commands.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: 'wstack-sync',\n risk: 'medium',\n summary: 'Cloud sync commands for prompts, skills, settings, memory, and history.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: 'wstack-git',\n risk: 'high',\n summary: 'Git commands for commit, status checks, and push.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: 'wstack-observability',\n risk: 'low',\n summary: 'Runtime metrics and health slash commands.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: 'wstack-chimera',\n risk: 'medium',\n summary: 'Spawns a post-session code review subagent when explicitly enabled.',\n defaultState: 'inactive',\n canDisable: true,\n },\n {\n name: 'wstack-skills',\n risk: 'medium',\n summary: 'Skill library, authoring, install, update, and uninstall commands.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: 'wstack-plan',\n risk: 'medium',\n summary: 'Strategic plan board slash command.',\n defaultState: 'active',\n canDisable: true,\n },\n {\n name: '@wrongstack/plug-lsp',\n risk: 'medium',\n summary: 'Language Server Protocol tools and slash commands.',\n defaultState: 'inactive',\n canDisable: true,\n },\n {\n name: 'telegram',\n risk: 'medium',\n summary: 'Telegram bridge for messages, approvals, and notifications.',\n defaultState: 'inactive',\n canDisable: true,\n },\n] as const;\n\nexport const PLUGIN_AUDIT_ENTRIES: readonly PluginAuditEntry[] = Object.freeze([\n ...HOST_PLUGIN_AUDIT_ENTRIES,\n ...OFFICIAL_PLUGIN_AUDIT_ENTRIES,\n]);\n\nexport const OFFICIAL_PLUGINS = [\n {\n alias: 'telegram',\n specifier: '@wrongstack/telegram',\n description: 'Telegram bridge for prompts, notifications, and slash commands.',\n },\n {\n alias: 'lsp',\n specifier: '@wrongstack/plug-lsp',\n description: 'Language Server Protocol tools for code intelligence.',\n },\n] as const;\n\nexport interface PluginManagementDeps {\n config: Config;\n configPath: string;\n}\n\nexport interface PluginManagementResult {\n code: number;\n level: 'output' | 'info' | 'error';\n message: string;\n patch?: {\n plugins?: (string | PluginConfig)[] | undefined;\n features?: Record<string, unknown>;\n pluginManager?: PluginManagerConfig | undefined;\n /**\n * Full replacement `extensions` map (patchConfig is a SHALLOW merge,\n * so per-plugin llm overrides must ship the whole merged object).\n */\n extensions?: Record<string, Record<string, unknown>> | undefined;\n };\n restartRequired?: boolean | undefined;\n}\n\nconst OFFICIAL_ALIASES = new Map<string, string>(\n OFFICIAL_PLUGINS.flatMap((p) => [\n [p.alias, p.specifier],\n [p.specifier, p.specifier],\n ]),\n);\n\nexport async function runPluginManagementCommand(\n args: string[],\n deps: PluginManagementDeps,\n): Promise<PluginManagementResult> {\n const sub = args[0];\n if (!sub || sub === 'list' || sub === 'status') {\n return {\n code: 0,\n level: 'output',\n message: renderConfiguredPlugins(deps.config),\n };\n }\n if (sub === 'official' || sub === 'officials') {\n return {\n code: 0,\n level: 'output',\n message: renderOfficialPlugins(deps.config),\n };\n }\n if (sub === 'report' || sub === 'audit' || sub === 'menu') {\n return {\n code: 0,\n level: 'output',\n message: renderPluginAuditReport(deps.config),\n };\n }\n if (sub === 'add' || sub === 'install') {\n const spec = args[1];\n if (!spec) {\n return errorResult('Usage: wstack plugin add <specifier|official-alias> [--disabled]');\n }\n return upsertPlugin(\n resolvePluginSpecifier(spec),\n { enabled: !args.includes('--disabled') },\n deps,\n 'Added',\n );\n }\n if (sub === 'remove' || sub === 'rm' || sub === 'uninstall') {\n const spec = args[1];\n if (!spec) {\n return errorResult('Usage: wstack plugin remove <specifier|official-alias>');\n }\n return removePlugin(resolvePluginSpecifier(spec), deps);\n }\n if (sub === 'enable' || sub === 'disable') {\n const spec = args[1];\n if (!spec) {\n return errorResult(`Usage: wstack plugin ${sub} <specifier|official-alias>`);\n }\n return upsertPlugin(\n resolvePluginSpecifier(spec),\n { enabled: sub === 'enable' },\n deps,\n sub === 'enable' ? 'Enabled' : 'Disabled',\n );\n }\n if (sub === 'toggle') {\n const spec = args[1];\n if (!spec) {\n return errorResult('Usage: wstack plugin toggle <plugin-name|official-alias>');\n }\n return togglePlugin(resolvePluginToggleSpecifier(spec), deps);\n }\n if (sub === 'manager') {\n return runPluginManagerPolicyCommand(args.slice(1), deps);\n }\n if (sub === 'llm') {\n return runPluginLlmCommand(args.slice(1), deps);\n }\n return errorResult(\n `Unknown plugin subcommand: ${sub}\\nUsage: wstack plugin [list|status|report|menu|official|add|install|remove|enable|disable|toggle|manager|llm]`,\n );\n}\n\nconst PLUGIN_MANAGER_POLICY_USAGE = [\n 'Usage:',\n ' wstack plugin manager List plugins locked against LLM state changes.',\n ' wstack plugin manager lock <plugin|*> Prevent plugin_manager from enabling/disabling it.',\n ' wstack plugin manager unlock <plugin|*> Restore plugin_manager enable/disable access.',\n '',\n 'This policy affects only the LLM-facing plugin_manager tool. Human /plugin and',\n 'wstack plugin enable/disable commands remain available.',\n].join('\\n');\n\nasync function runPluginManagerPolicyCommand(\n args: string[],\n deps: PluginManagementDeps,\n): Promise<PluginManagementResult> {\n const action = args[0] ?? 'list';\n if (action === 'list') {\n const locked = deps.config.pluginManager?.locked ?? [];\n return {\n code: 0,\n level: 'output',\n message:\n locked.length === 0\n ? 'LLM plugin-manager policy: no plugins are locked.'\n : `LLM plugin-manager policy \u2014 locked (${locked.length}):\\n${locked.map((name) => ` ${name}`).join('\\n')}`,\n };\n }\n\n if (action !== 'lock' && action !== 'unlock') {\n return errorResult(PLUGIN_MANAGER_POLICY_USAGE);\n }\n const rawName = args[1];\n if (!rawName) return errorResult(PLUGIN_MANAGER_POLICY_USAGE);\n const name = rawName === '*' ? '*' : resolvePluginToggleSpecifier(rawName);\n\n const existing = await readConfig(deps.configPath);\n const filePolicy = isRecord(existing.pluginManager) ? existing.pluginManager : undefined;\n const current = Array.isArray(filePolicy?.['locked'])\n ? filePolicy['locked'].filter((entry): entry is string => typeof entry === 'string')\n : [...(deps.config.pluginManager?.locked ?? [])];\n const canonical = canonicalPolicyName(name);\n const next =\n action === 'lock'\n ? current.some((entry) => canonicalPolicyName(entry) === canonical)\n ? current\n : [...current, name]\n : current.filter((entry) => canonicalPolicyName(entry) !== canonical);\n\n const pluginManager: PluginManagerConfig = { locked: next };\n existing.pluginManager = { ...(filePolicy ?? {}), ...pluginManager };\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n\n return {\n code: 0,\n level: 'info',\n message:\n action === 'lock'\n ? `Locked \"${name}\" against LLM enable/disable changes. Human plugin commands still work.`\n : `Unlocked \"${name}\" for LLM enable/disable changes.`,\n patch: { pluginManager },\n };\n}\n\nfunction canonicalPolicyName(name: string): string {\n if (name === '*') return name;\n return resolvePluginToggleSpecifier(name).trim().toLowerCase();\n}\n\nconst PLUGIN_LLM_USAGE = [\n 'Usage:',\n ' wstack plugin llm List every per-plugin LLM override.',\n \" wstack plugin llm <plugin> Show one plugin's LLM override.\",\n ' wstack plugin llm <plugin> <provider> [model] Route this plugin through a provider (and model).',\n ' wstack plugin llm <plugin> - <model> Keep the session provider, override only the model.',\n ' wstack plugin llm <plugin> --clear Remove the override (back to the session default).',\n '',\n 'The override is stored as config.extensions.<plugin>.llm and is used by',\n 'every api.llm call the plugin makes. Without an override, plugins follow',\n 'the active session provider/model (the chat leader).',\n].join('\\n');\n\n/**\n * Render every configured per-plugin LLM override. Reads\n * `config.extensions.<plugin>.llm` across all plugins so the user can\n * see, at a glance, which plugins deviate from the session default.\n */\nfunction renderPluginLlmOverviews(config: Config, sessionDefault: string): string {\n const rows: Array<{ name: string; provider: string; model: string }> = [];\n for (const [pluginName, ext] of Object.entries(config.extensions ?? {})) {\n const llm = (ext as Record<string, unknown>)?.['llm'];\n if (llm && typeof llm === 'object') {\n const o = llm as { provider?: string; model?: string };\n rows.push({\n name: pluginName,\n provider: o.provider ?? '(session)',\n model: o.model ?? '(session)',\n });\n }\n }\n const lines = [`Per-plugin LLM routing (session default: ${sessionDefault}):`];\n if (rows.length === 0) {\n lines.push(\n ' No plugin overrides \u2014 every plugin follows the session default.',\n ' Set one with: wstack plugin llm <plugin> <provider> [model]',\n );\n } else {\n rows.sort((a, b) => a.name.localeCompare(b.name));\n for (const r of rows) {\n lines.push(` ${r.name.padEnd(24)} provider=${r.provider.padEnd(16)} model=${r.model}`);\n }\n lines.push('', 'Clear one with: wstack plugin llm <plugin> --clear');\n }\n return lines.join('\\n');\n}\n\n/**\n * `wstack plugin llm \u2026` \u2014 per-plugin LLM routing. The chat leader's\n * provider/model stays the DEFAULT; this stores an override under\n * `config.extensions[<plugin>].llm = { provider?, model? }` which\n * `api.llm` resolves ahead of the session default on every call.\n */\nasync function runPluginLlmCommand(\n args: string[],\n deps: PluginManagementDeps,\n): Promise<PluginManagementResult> {\n const sessionDefault = `${deps.config.provider ?? '?'} / ${deps.config.model ?? '?'}`;\n\n // \u2500\u2500 List: `plugin llm` (no name) or `plugin llm list` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (args.length === 0 || args[0] === 'list') {\n return {\n code: 0,\n level: 'output',\n message: renderPluginLlmOverviews(deps.config, sessionDefault),\n };\n }\n\n const name = args[0];\n if (!name || name.startsWith('-')) return errorResult(PLUGIN_LLM_USAGE);\n\n const currentOverride = deps.config.extensions?.[name]?.['llm'];\n\n // \u2500\u2500 Show \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (args.length === 1) {\n const lines = [`LLM routing for plugin \"${name}\":`];\n if (currentOverride && typeof currentOverride === 'object') {\n const o = currentOverride as { provider?: string; model?: string };\n lines.push(\n ` override: provider=${o.provider ?? '(session)'} model=${o.model ?? '(session)'}`,\n ` session default: ${sessionDefault}`,\n );\n } else {\n lines.push(\n ` no override \u2014 follows the session default (${sessionDefault}).`,\n ` Set one with: wstack plugin llm ${name} <provider> [model]`,\n );\n }\n return { code: 0, level: 'output', message: lines.join('\\n') };\n }\n\n const existing = await readConfig(deps.configPath);\n const fileExtensions = isRecord(existing.extensions)\n ? (existing.extensions as Record<string, unknown>)\n : {};\n const pluginExt = isRecord(fileExtensions[name])\n ? { ...(fileExtensions[name] as Record<string, unknown>) }\n : {};\n\n // \u2500\u2500 Clear \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (args[1] === '--clear' || args[1] === 'clear') {\n if (!('llm' in pluginExt) && !currentOverride) {\n return { code: 0, level: 'info', message: `Plugin \"${name}\" has no LLM override.` };\n }\n delete pluginExt['llm'];\n if (Object.keys(pluginExt).length === 0) delete fileExtensions[name];\n else fileExtensions[name] = pluginExt;\n existing.extensions = fileExtensions;\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n const merged = mergeExtensionsForPatch(deps.config, fileExtensions);\n // The in-memory config may still carry the old override \u2014 the merge\n // above unions both sources, so drop the key explicitly.\n const mergedEntry = merged[name];\n if (mergedEntry) {\n delete mergedEntry['llm'];\n if (Object.keys(mergedEntry).length === 0) delete merged[name];\n }\n return {\n code: 0,\n level: 'info',\n message:\n `Cleared LLM override for \"${name}\" \u2014 it now follows the session default (${sessionDefault}). ` +\n 'Takes effect immediately for api.llm calls.',\n patch: { extensions: merged },\n };\n }\n\n // \u2500\u2500 Set \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const providerArg = args[1];\n const modelArg = args[2];\n if (!providerArg || providerArg.startsWith('--')) return errorResult(PLUGIN_LLM_USAGE);\n const provider = providerArg === '-' ? undefined : providerArg;\n const model = modelArg && modelArg !== '-' ? modelArg : undefined;\n if (!provider && !model) return errorResult(PLUGIN_LLM_USAGE);\n\n const llm: Record<string, unknown> = {};\n if (provider) llm['provider'] = provider;\n if (model) llm['model'] = model;\n pluginExt['llm'] = llm;\n fileExtensions[name] = pluginExt;\n existing.extensions = fileExtensions;\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n\n // Soft sanity note: an unknown provider name still works if the\n // models.dev catalog knows it, but flag likely typos against the\n // user's configured providers.\n const configured = Object.keys(deps.config.providers ?? {});\n const note =\n provider && configured.length > 0 && !configured.includes(provider)\n ? `\\nNote: \"${provider}\" is not in config.providers (configured: ${configured.join(', ')}). ` +\n 'Catalog providers (anthropic, openai, \u2026) still resolve if credentials are available.'\n : '';\n\n const merged = mergeExtensionsForPatch(deps.config, fileExtensions);\n return {\n code: 0,\n level: 'info',\n message:\n `Plugin \"${name}\" now routes api.llm calls via ` +\n `provider=${provider ?? '(session)'} model=${model ?? '(session)'}. ` +\n `Session default stays ${sessionDefault}. Takes effect immediately for api.llm calls.${note}`,\n patch: { extensions: merged },\n };\n}\n\n/**\n * patchConfig is a shallow merge \u2014 build the FULL extensions map:\n * in-memory config first, then the file's (just-written) entries win.\n */\nfunction mergeExtensionsForPatch(\n config: Config,\n fileExtensions: Record<string, unknown>,\n): Record<string, Record<string, unknown>> {\n const merged: Record<string, Record<string, unknown>> = {};\n for (const [k, v] of Object.entries(config.extensions ?? {})) {\n merged[k] = { ...v };\n }\n for (const [k, v] of Object.entries(fileExtensions)) {\n if (isRecord(v)) merged[k] = { ...(merged[k] ?? {}), ...v };\n }\n // A cleared plugin entry must disappear from the merged view too.\n for (const k of Object.keys(merged)) {\n if (!(k in fileExtensions) && !(k in (config.extensions ?? {}))) delete merged[k];\n }\n return merged;\n}\n\nexport function resolvePluginSpecifier(input: string): string {\n return OFFICIAL_ALIASES.get(input.toLowerCase()) ?? input;\n}\n\nfunction resolvePluginToggleSpecifier(input: string): string {\n if (input.toLowerCase() === '@wrongstack/telegram') return 'telegram';\n if (auditEntryFor(input)) return input;\n return resolvePluginSpecifier(input);\n}\n\nexport function renderOfficialPlugins(config?: Config): string {\n return [\n 'Official plugins:',\n ...OFFICIAL_PLUGINS.map((p) => {\n const state = config ? officialPluginState(config, p.specifier) : '';\n const status = state ? `${state.padEnd(14)} ` : '';\n return ` ${p.alias.padEnd(12)} ${status}${p.specifier.padEnd(24)} ${p.description}`;\n }),\n '',\n 'Use `wstack plugin add <alias>` or `/plugin install <alias>`.',\n ].join('\\n');\n}\n\nexport function renderConfiguredPlugins(config: Config): string {\n const plugins = config.plugins ?? [];\n if (plugins.length === 0) {\n return [\n 'No plugins configured.',\n 'Use `wstack plugin add <specifier>` or `/plugin install <official-alias>`.',\n ].join('\\n');\n }\n return plugins\n .map((p) => {\n const name = pluginName(p);\n const enabled = typeof p === 'object' && p.enabled === false ? 'disabled' : 'enabled';\n const official = OFFICIAL_PLUGINS.find((entry) => entry.specifier === name);\n const suffix = official ? ` (${official.alias})` : '';\n return ` ${`${name}${suffix}`.padEnd(44)} ${enabled}`;\n })\n .join('\\n');\n}\n\nexport function renderPluginAuditReport(config: Config): string {\n const configured = config.plugins ?? [];\n const lines = [\n 'Plugin audit report:',\n ' State shows the effective boot state. Default-active plugins run unless disabled in config.',\n ' Built-in audit rows are toggleable unless a future row explicitly marks itself locked.',\n '',\n ' Built-in audit entries:',\n ];\n\n for (const entry of PLUGIN_AUDIT_ENTRIES) {\n const { state, source } = effectiveAuditState(config, entry);\n const policy = entry.canDisable ? 'toggleable' : lockedReason(entry);\n lines.push(\n ` ${entry.name.padEnd(24)} ${state.padEnd(8)} ${source.padEnd(8)} risk=${entry.risk.padEnd(6)} ${policy.padEnd(24)} ${entry.summary}`,\n );\n }\n\n const extra = configured.filter(\n (plugin) => !PLUGIN_AUDIT_ENTRIES.some((entry) => pluginMatchesToggleSpec(plugin, entry.name)),\n );\n if (extra.length > 0) {\n lines.push('', ' Configured plugins outside the audit list:');\n for (const plugin of extra) {\n const name = pluginName(plugin);\n const state = typeof plugin === 'object' && plugin.enabled === false ? 'disabled' : 'enabled';\n lines.push(\n ` ${name.padEnd(24)} ${state.padEnd(8)} config risk=custom user-configured plugin`,\n );\n }\n }\n\n lines.push(\n '',\n 'Use `/plugin menu` in the TUI, or `wstack plugin toggle <name>` for toggleable rows.',\n );\n return lines.join('\\n');\n}\n\nasync function readConfig(file: string): Promise<Record<string, unknown>> {\n try {\n return JSON.parse(await fs.readFile(file, 'utf8')) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nfunction pluginName(p: string | PluginConfig): string {\n return typeof p === 'string' ? p : p.name;\n}\n\nfunction pluginEntry(spec: string, enabled: boolean): string | PluginConfig {\n return enabled ? spec : { name: spec, enabled: false };\n}\n\nfunction pluginMatchesToggleSpec(p: string | PluginConfig, spec: string): boolean {\n const name = pluginName(p);\n if (spec === 'telegram') return name === 'telegram' || name === '@wrongstack/telegram';\n return name === spec;\n}\n\nfunction effectiveAuditState(\n config: Config,\n entry: PluginAuditEntry,\n): { state: 'enabled' | 'disabled'; source: 'config' | 'default' } {\n const configured = (config.plugins ?? []).find((plugin) =>\n pluginMatchesToggleSpec(plugin, entry.name),\n );\n if (configured) {\n return {\n state:\n typeof configured === 'object' && configured.enabled === false ? 'disabled' : 'enabled',\n source: 'config',\n };\n }\n return {\n state: entry.defaultState === 'active' ? 'enabled' : 'disabled',\n source: 'default',\n };\n}\n\nfunction lockedReason(entry: PluginAuditEntry): string {\n if (entry.canDisable) return 'toggleable';\n if (entry.name === 'secret-scanner') {\n return 'locked: safety guard';\n }\n return 'locked: core surface';\n}\n\nfunction auditEntryFor(spec: string): PluginAuditEntry | undefined {\n return PLUGIN_AUDIT_ENTRIES.find((entry) => entry.name === spec);\n}\n\nfunction officialPluginState(\n config: Config,\n spec: string,\n): 'enabled' | 'disabled' | 'not configured' {\n const match = (config.plugins ?? []).find((p) => pluginName(p) === spec);\n if (!match) return 'not configured';\n return typeof match === 'object' && match.enabled === false ? 'disabled' : 'enabled';\n}\n\nasync function togglePlugin(\n spec: string,\n deps: PluginManagementDeps,\n): Promise<PluginManagementResult> {\n const auditEntry = auditEntryFor(spec);\n if (auditEntry?.canDisable === false) {\n return errorResult(`Plugin \"${spec}\" is locked and cannot be toggled from the plugin menu.`);\n }\n\n const existing = await readConfig(deps.configPath);\n const plugins = Array.isArray(existing.plugins)\n ? (existing.plugins as Array<string | PluginConfig>)\n : [];\n const idx = plugins.findIndex((p) => pluginMatchesToggleSpec(p, spec));\n const configured = idx >= 0 ? plugins[idx] : undefined;\n const enabled = configured\n ? !(typeof configured === 'object' && configured.enabled === false)\n : auditEntry?.defaultState === 'active';\n\n if (!configured && !auditEntry) {\n return errorResult(\n `Plugin \"${spec}\" is not configured. Use \"wstack plugin add ${spec}\" first.`,\n );\n }\n\n let nextPlugins: Array<string | PluginConfig>;\n let nextEnabled: boolean;\n if (enabled) {\n nextEnabled = false;\n nextPlugins = plugins.filter((p) => !pluginMatchesToggleSpec(p, spec));\n if (auditEntry?.defaultState !== 'inactive') {\n nextPlugins.push({ name: spec, enabled: false });\n }\n } else {\n nextEnabled = true;\n if (auditEntry?.defaultState === 'active') {\n nextPlugins = plugins.filter((p) => !pluginMatchesToggleSpec(p, spec));\n } else {\n nextPlugins = plugins.filter((p) => !pluginMatchesToggleSpec(p, spec));\n nextPlugins.push(spec);\n }\n }\n\n const features = {\n ...(isRecord(deps.config.features) ? deps.config.features : {}),\n ...(isRecord(existing.features) ? existing.features : {}),\n plugins: true,\n };\n existing.plugins = nextPlugins;\n existing.features = features;\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n const restartRequired = spec === 'branch-guard' && !nextEnabled ? undefined : true;\n return {\n code: 0,\n level: 'info',\n message:\n `${nextEnabled ? 'Enabled' : 'Disabled'} \"${spec}\". Config written to ${deps.configPath}.` +\n (spec === 'branch-guard' && !nextEnabled\n ? ' The running branch-guard hook will disable itself on config update.'\n : ''),\n patch: { plugins: nextPlugins, features },\n restartRequired,\n };\n}\n\nasync function upsertPlugin(\n spec: string,\n opts: { enabled: boolean },\n deps: PluginManagementDeps,\n verb: string,\n): Promise<PluginManagementResult> {\n const existing = await readConfig(deps.configPath);\n const plugins = Array.isArray(existing.plugins)\n ? (existing.plugins as Array<string | PluginConfig>)\n : [];\n const idx = plugins.findIndex((p) => pluginName(p) === spec);\n const nextEntry = pluginEntry(spec, opts.enabled);\n if (idx >= 0) plugins[idx] = nextEntry;\n else plugins.push(nextEntry);\n const features = {\n ...(isRecord(deps.config.features) ? deps.config.features : {}),\n ...(isRecord(existing.features) ? existing.features : {}),\n plugins: true,\n };\n existing.plugins = plugins;\n existing.features = features;\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n const restartRequired = spec === 'branch-guard' && !opts.enabled ? undefined : true;\n return {\n code: 0,\n level: 'info',\n message:\n `${verb} \"${spec}\" (${opts.enabled ? 'enabled' : 'disabled'}). Config written to ${deps.configPath}.` +\n (spec === 'branch-guard' && !opts.enabled\n ? ' The running branch-guard hook will disable itself on config update.'\n : ''),\n patch: { plugins, features },\n restartRequired,\n };\n}\n\nasync function removePlugin(\n spec: string,\n deps: PluginManagementDeps,\n): Promise<PluginManagementResult> {\n const existing = await readConfig(deps.configPath);\n const plugins = Array.isArray(existing.plugins)\n ? (existing.plugins as Array<string | PluginConfig>)\n : [];\n const next = plugins.filter((p) => pluginName(p) !== spec);\n if (next.length === plugins.length) {\n return errorResult(`Plugin \"${spec}\" not in config.`);\n }\n existing.plugins = next;\n await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 0o600 });\n return {\n code: 0,\n level: 'info',\n message: `Removed \"${spec}\" from config.`,\n patch: { plugins: next },\n restartRequired: true,\n };\n}\n\nfunction errorResult(message: string): PluginManagementResult {\n return { code: 1, level: 'error', message };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["name", "pluginName", "merged"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
|
|
2
|
+
// src/config-history.ts
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import * as fs from "node:fs/promises";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { atomicWrite, resolveWstackPaths, writeErr } from "@wrongstack/core/utils";
|
|
8
|
+
import { ERROR_CODES, FsError } from "@wrongstack/core/types";
|
|
9
|
+
import { isSecretField } from "@wrongstack/core/security";
|
|
10
|
+
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
11
|
+
var defaultUidFn = () => os.userInfo().uid;
|
|
12
|
+
async function getFileUid(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
const stat2 = await fs.stat(filePath);
|
|
15
|
+
return stat2.uid;
|
|
16
|
+
} catch {
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function checkConfigOwnership(homeFn, uidFn = defaultUidFn, targetConfigPath) {
|
|
21
|
+
if (os.platform() === "win32") return true;
|
|
22
|
+
const cfg = configPath(homeFn, targetConfigPath);
|
|
23
|
+
const fileUid = await getFileUid(cfg);
|
|
24
|
+
if (fileUid === void 0) return true;
|
|
25
|
+
const callerUid = uidFn();
|
|
26
|
+
if (callerUid === void 0) return true;
|
|
27
|
+
return fileUid === callerUid;
|
|
28
|
+
}
|
|
29
|
+
var PROTECTED_BASENAMES = /* @__PURE__ */ new Set(["config.json", ".key", "index.json"]);
|
|
30
|
+
var MAX_CONFIG_BACKUPS = 10;
|
|
31
|
+
var MAX_CONFIG_HISTORY_ENTRIES = 50;
|
|
32
|
+
function assertSafeToDelete(filename, parentDir) {
|
|
33
|
+
if (PROTECTED_BASENAMES.has(filename)) {
|
|
34
|
+
throw new FsError({
|
|
35
|
+
message: `Refusing to delete protected file: ${filename}`,
|
|
36
|
+
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
37
|
+
path: path.join(parentDir, filename),
|
|
38
|
+
context: { reason: "protected_basename" }
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (filename !== path.basename(filename)) {
|
|
42
|
+
throw new FsError({
|
|
43
|
+
message: `Refusing to delete path with traversal: ${filename}`,
|
|
44
|
+
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
45
|
+
path: filename,
|
|
46
|
+
context: { reason: "path_traversal" }
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
|
|
50
|
+
throw new FsError({
|
|
51
|
+
message: `Refusing to delete unknown file: ${filename}`,
|
|
52
|
+
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
53
|
+
path: path.join(parentDir, filename),
|
|
54
|
+
context: { reason: "unknown_file_pattern" }
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const resolvedParent = path.resolve(parentDir);
|
|
58
|
+
const parentOfProfile = path.dirname(resolvedParent);
|
|
59
|
+
const isGlobalRoot = path.basename(resolvedParent) === ".wrongstack";
|
|
60
|
+
const isProfileDir = path.basename(parentOfProfile) === "profiles" && path.basename(path.dirname(parentOfProfile)) === ".wrongstack";
|
|
61
|
+
if (!isGlobalRoot && !isProfileDir) {
|
|
62
|
+
throw new FsError({
|
|
63
|
+
message: `Unexpected parent directory for bak prune: ${resolvedParent}`,
|
|
64
|
+
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
65
|
+
path: resolvedParent,
|
|
66
|
+
context: { reason: "invalid_parent" }
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function safeDelete(filePath) {
|
|
71
|
+
const dir = path.dirname(filePath);
|
|
72
|
+
const filename = path.basename(filePath);
|
|
73
|
+
try {
|
|
74
|
+
assertSafeToDelete(filename, dir);
|
|
75
|
+
await fs.unlink(filePath);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (err instanceof Error && err.message.startsWith("Refusing")) {
|
|
78
|
+
writeErr(`[config-history] SAFETY: ${err.message}
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function maskConfigSecrets(cfg) {
|
|
84
|
+
if (typeof cfg !== "object" || cfg === null) return {};
|
|
85
|
+
const out = {};
|
|
86
|
+
for (const [k, v] of Object.entries(cfg)) {
|
|
87
|
+
if (isSecretField(k)) {
|
|
88
|
+
out[k] = "[REDACTED]";
|
|
89
|
+
} else if (typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
90
|
+
out[k] = maskConfigSecrets(v);
|
|
91
|
+
} else {
|
|
92
|
+
out[k] = v;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
function diffSummary(oldCfg, newCfg) {
|
|
98
|
+
const changes = [];
|
|
99
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldCfg), ...Object.keys(newCfg)]);
|
|
100
|
+
for (const k of allKeys) {
|
|
101
|
+
const o = JSON.stringify(oldCfg[k]);
|
|
102
|
+
const n = JSON.stringify(newCfg[k]);
|
|
103
|
+
if (o !== n) {
|
|
104
|
+
if (isSecretField(k)) {
|
|
105
|
+
changes.push(`${k}: [CHANGED]`);
|
|
106
|
+
} else if (typeof newCfg[k] !== "object") {
|
|
107
|
+
changes.push(`${k}: ${oldCfg[k] ?? "(unset)"} \u2192 ${newCfg[k]}`);
|
|
108
|
+
} else {
|
|
109
|
+
changes.push(`${k}: [CHANGED]`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return changes.length > 0 ? changes.slice(0, 5).join(", ") : "no changes";
|
|
114
|
+
}
|
|
115
|
+
var defaultHomeDir = () => os.homedir();
|
|
116
|
+
function activeProfileConfigPath(homeFn) {
|
|
117
|
+
const paths = homeFn === defaultHomeDir ? resolveWstackPaths({ projectRoot: process.cwd() }) : resolveWstackPaths({ projectRoot: process.cwd(), userHome: homeFn() });
|
|
118
|
+
return paths.profileConfig(paths.profileName);
|
|
119
|
+
}
|
|
120
|
+
function historyDir(homeFn = defaultHomeDir, targetConfigPath) {
|
|
121
|
+
return path.join(path.dirname(configPath(homeFn, targetConfigPath)), "config.history", "entries");
|
|
122
|
+
}
|
|
123
|
+
function historyIndexPath(homeFn = defaultHomeDir, targetConfigPath) {
|
|
124
|
+
return path.join(path.dirname(configPath(homeFn, targetConfigPath)), "config.history", "index.json");
|
|
125
|
+
}
|
|
126
|
+
function configPath(homeFn = defaultHomeDir, targetConfigPath) {
|
|
127
|
+
return targetConfigPath ?? activeProfileConfigPath(homeFn);
|
|
128
|
+
}
|
|
129
|
+
function backupLastPath(homeFn = defaultHomeDir, targetConfigPath) {
|
|
130
|
+
return `${configPath(homeFn, targetConfigPath)}.last`;
|
|
131
|
+
}
|
|
132
|
+
function entryId(ts) {
|
|
133
|
+
return `${ts.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
|
|
134
|
+
}
|
|
135
|
+
async function ensureHistoryDir(homeFn = defaultHomeDir, targetConfigPath) {
|
|
136
|
+
try {
|
|
137
|
+
await fs.mkdir(historyDir(homeFn, targetConfigPath), { recursive: true });
|
|
138
|
+
} catch (err) {
|
|
139
|
+
throw new FsError({
|
|
140
|
+
message: toErrorMessage(err),
|
|
141
|
+
code: ERROR_CODES.FS_MKDIR_FAILED,
|
|
142
|
+
path: historyDir(homeFn, targetConfigPath),
|
|
143
|
+
cause: err
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function readIndex(homeFn = defaultHomeDir, targetConfigPath) {
|
|
148
|
+
try {
|
|
149
|
+
const raw = await fs.readFile(historyIndexPath(homeFn, targetConfigPath), "utf8");
|
|
150
|
+
return JSON.parse(raw);
|
|
151
|
+
} catch {
|
|
152
|
+
return { version: 1, entries: [] };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function writeIndex(idx, homeFn = defaultHomeDir, targetConfigPath) {
|
|
156
|
+
await ensureHistoryDir(homeFn, targetConfigPath);
|
|
157
|
+
try {
|
|
158
|
+
await atomicWrite(historyIndexPath(homeFn, targetConfigPath), JSON.stringify(idx, null, 2));
|
|
159
|
+
} catch (err) {
|
|
160
|
+
throw new FsError({
|
|
161
|
+
message: toErrorMessage(err),
|
|
162
|
+
code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
|
|
163
|
+
path: historyIndexPath(homeFn, targetConfigPath),
|
|
164
|
+
cause: err
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function pruneHistoryEntries(idx, homeFn = defaultHomeDir, targetConfigPath) {
|
|
169
|
+
const removed = idx.entries.splice(MAX_CONFIG_HISTORY_ENTRIES);
|
|
170
|
+
const keep = new Set(idx.entries.map((entry) => `${entry.id}.json`));
|
|
171
|
+
const dir = historyDir(homeFn, targetConfigPath);
|
|
172
|
+
for (const entry of removed) {
|
|
173
|
+
try {
|
|
174
|
+
await fs.unlink(path.join(dir, `${entry.id}.json`));
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const files = await fs.readdir(dir);
|
|
180
|
+
await Promise.all(
|
|
181
|
+
files.filter((file) => file.endsWith(".json") && !keep.has(file)).map(async (file) => {
|
|
182
|
+
try {
|
|
183
|
+
await fs.unlink(path.join(dir, file));
|
|
184
|
+
} catch {
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
);
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function backupCurrent(homeFn = defaultHomeDir, targetConfigPath) {
|
|
192
|
+
const cfg = configPath(homeFn, targetConfigPath);
|
|
193
|
+
const last = backupLastPath(homeFn, targetConfigPath);
|
|
194
|
+
const ts = Date.now();
|
|
195
|
+
let content;
|
|
196
|
+
try {
|
|
197
|
+
content = await fs.readFile(cfg, "utf8");
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
if (content !== void 0) {
|
|
201
|
+
try {
|
|
202
|
+
await atomicWrite(last, content);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
writeErr(
|
|
205
|
+
`[config-history] .last backup failed: ${toErrorMessage(err)}`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (content !== void 0) {
|
|
210
|
+
try {
|
|
211
|
+
const bakPath = `${cfg}.${ts}.bak`;
|
|
212
|
+
await atomicWrite(bakPath, content);
|
|
213
|
+
} catch (err) {
|
|
214
|
+
writeErr(
|
|
215
|
+
`[config-history] timestamped backup failed: ${toErrorMessage(err)}`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
const dir = path.dirname(cfg);
|
|
221
|
+
const files = await fs.readdir(dir);
|
|
222
|
+
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
223
|
+
for (const f of baks.slice(MAX_CONFIG_BACKUPS)) {
|
|
224
|
+
await safeDelete(path.join(dir, f));
|
|
225
|
+
}
|
|
226
|
+
} catch (err) {
|
|
227
|
+
writeErr(
|
|
228
|
+
`[config-history] backup prune failed: ${toErrorMessage(err)}`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDir, targetConfigPath) {
|
|
233
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
234
|
+
const id = entryId(timestamp);
|
|
235
|
+
await ensureHistoryDir(homeFn, targetConfigPath);
|
|
236
|
+
const entry = {
|
|
237
|
+
id,
|
|
238
|
+
timestamp,
|
|
239
|
+
description,
|
|
240
|
+
snapshotMasked: maskConfigSecrets(newCfg),
|
|
241
|
+
diffSummary: diffSummary(oldCfg, newCfg)
|
|
242
|
+
};
|
|
243
|
+
try {
|
|
244
|
+
await fs.writeFile(
|
|
245
|
+
path.join(historyDir(homeFn, targetConfigPath), `${id}.json`),
|
|
246
|
+
JSON.stringify(entry, null, 2),
|
|
247
|
+
"utf8"
|
|
248
|
+
);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
throw new FsError({
|
|
251
|
+
message: toErrorMessage(err),
|
|
252
|
+
code: ERROR_CODES.FS_WRITE_FAILED,
|
|
253
|
+
path: path.join(historyDir(homeFn, targetConfigPath), `${id}.json`),
|
|
254
|
+
cause: err
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
const idx = await readIndex(homeFn, targetConfigPath);
|
|
258
|
+
idx.entries.unshift({ id, timestamp, description });
|
|
259
|
+
await pruneHistoryEntries(idx, homeFn, targetConfigPath);
|
|
260
|
+
await writeIndex(idx, homeFn, targetConfigPath);
|
|
261
|
+
return id;
|
|
262
|
+
}
|
|
263
|
+
async function listHistory(homeFn = defaultHomeDir, targetConfigPath) {
|
|
264
|
+
const idx = await readIndex(homeFn, targetConfigPath);
|
|
265
|
+
return idx.entries;
|
|
266
|
+
}
|
|
267
|
+
async function getHistoryEntry(id, homeFn = defaultHomeDir, targetConfigPath) {
|
|
268
|
+
try {
|
|
269
|
+
const raw = await fs.readFile(
|
|
270
|
+
path.join(historyDir(homeFn, targetConfigPath), `${id}.json`),
|
|
271
|
+
"utf8"
|
|
272
|
+
);
|
|
273
|
+
return JSON.parse(raw);
|
|
274
|
+
} catch {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async function restoreFromHistory(id, homeFn = defaultHomeDir, targetConfigPath) {
|
|
279
|
+
const entry = await getHistoryEntry(id, homeFn, targetConfigPath);
|
|
280
|
+
if (!entry) return { ok: false, backupId: null, error: "History entry not found" };
|
|
281
|
+
if (!await checkConfigOwnership(homeFn, defaultUidFn, targetConfigPath)) {
|
|
282
|
+
return {
|
|
283
|
+
ok: false,
|
|
284
|
+
backupId: null,
|
|
285
|
+
error: "Operation denied: config file is not owned by current user"
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
await backupCurrent(homeFn, targetConfigPath);
|
|
289
|
+
let oldCfg = {};
|
|
290
|
+
try {
|
|
291
|
+
const raw = await fs.readFile(configPath(homeFn, targetConfigPath), "utf8");
|
|
292
|
+
oldCfg = JSON.parse(raw);
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
await atomicWrite(configPath(homeFn, targetConfigPath), JSON.stringify(entry.snapshotMasked, null, 2));
|
|
297
|
+
} catch (err) {
|
|
298
|
+
return { ok: false, backupId: null, error: String(err) };
|
|
299
|
+
}
|
|
300
|
+
const backupId = await appendHistory(
|
|
301
|
+
oldCfg,
|
|
302
|
+
entry.snapshotMasked,
|
|
303
|
+
`Restored from history ${id}`,
|
|
304
|
+
homeFn,
|
|
305
|
+
targetConfigPath
|
|
306
|
+
);
|
|
307
|
+
return { ok: true, backupId };
|
|
308
|
+
}
|
|
309
|
+
async function restoreLast(homeFn = defaultHomeDir, targetConfigPath) {
|
|
310
|
+
const last = backupLastPath(homeFn, targetConfigPath);
|
|
311
|
+
const cfg = configPath(homeFn, targetConfigPath);
|
|
312
|
+
let oldCfg = {};
|
|
313
|
+
try {
|
|
314
|
+
const raw = await fs.readFile(cfg, "utf8");
|
|
315
|
+
oldCfg = JSON.parse(raw);
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
let lastCfg = {};
|
|
319
|
+
try {
|
|
320
|
+
const raw = await fs.readFile(last, "utf8");
|
|
321
|
+
lastCfg = JSON.parse(raw);
|
|
322
|
+
} catch {
|
|
323
|
+
return { ok: false, error: "No prior backup found" };
|
|
324
|
+
}
|
|
325
|
+
if (!await checkConfigOwnership(homeFn, defaultUidFn, targetConfigPath)) {
|
|
326
|
+
return { ok: false, error: "Operation denied: config file is not owned by current user" };
|
|
327
|
+
}
|
|
328
|
+
await backupCurrent(homeFn, targetConfigPath);
|
|
329
|
+
try {
|
|
330
|
+
await atomicWrite(cfg, JSON.stringify(lastCfg, null, 2));
|
|
331
|
+
} catch (err) {
|
|
332
|
+
return { ok: false, error: String(err) };
|
|
333
|
+
}
|
|
334
|
+
await appendHistory(oldCfg, lastCfg, "Restored from config.json.last", homeFn, targetConfigPath);
|
|
335
|
+
return { ok: true };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export {
|
|
339
|
+
backupCurrent,
|
|
340
|
+
appendHistory,
|
|
341
|
+
listHistory,
|
|
342
|
+
getHistoryEntry,
|
|
343
|
+
restoreFromHistory,
|
|
344
|
+
restoreLast
|
|
345
|
+
};
|
|
346
|
+
//# sourceMappingURL=chunk-YWUPHRGB.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/config-history.ts"],
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs/promises';\nimport os from 'node:os';\nimport * as path from 'node:path';\nimport { atomicWrite, resolveWstackPaths, writeErr } from '@wrongstack/core/utils';\nimport { ERROR_CODES, FsError } from '@wrongstack/core/types';\nimport { isSecretField } from '@wrongstack/core/security';\nimport { toErrorMessage } from '@wrongstack/core/utils';\n\n// \u2500\u2500 UID ownership \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype UidFn = () => string | number | undefined;\nconst defaultUidFn: UidFn = () => os.userInfo().uid;\n\nasync function getFileUid(filePath: string): Promise<string | number | undefined> {\n try {\n const stat = await fs.stat(filePath);\n return stat.uid;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Verify the calling process owns config.json before allowing a write operation.\n * Guards against one user on a shared multi-user system overwriting another's config.\n *\n * On Windows, .uid is always undefined on the UserInfo object, and the NT ACL\n * model already restricts writes \u2014 so we skip the check on win32.\n * On Unix, we compare the process euid against the file's uid.\n * If config.json doesn't exist yet, we allow the write (first-time setup).\n */\nasync function checkConfigOwnership(\n homeFn: HomeDirFn,\n uidFn: UidFn = defaultUidFn,\n targetConfigPath?: string,\n): Promise<boolean> {\n if (os.platform() === 'win32') return true; // ACLs handle this on Windows\n\n const cfg = configPath(homeFn, targetConfigPath);\n const fileUid = await getFileUid(cfg);\n if (fileUid === undefined) return true; // file doesn't exist yet \u2014 allow\n\n const callerUid = uidFn();\n if (callerUid === undefined) return true; // can't determine \u2014 allow, don't block\n\n return fileUid === callerUid;\n}\n\n// \u2500\u2500 Protected files/directories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// These are NEVER touched by any operation in this module.\n// Guards against bugs (glob patterns, typos, race conditions)\n// accidentally deleting critical user data.\nconst PROTECTED_BASENAMES = new Set(['config.json', '.key', 'index.json']);\nconst MAX_CONFIG_BACKUPS = 10;\nexport const MAX_CONFIG_HISTORY_ENTRIES = 50;\n\n// Top-level directories that should never be deleted even if a prune\n// pattern accidentally widens. These are absolute directory names\n// relative to the .wrongstack root.\n\n/**\n * Guard: throw if `filename` is a protected file or lives inside a protected\n * directory. Used before any unlink / rm call to make accidentally deleting\n * critical files impossible.\n */\nfunction assertSafeToDelete(filename: string, parentDir: string): void {\n // 1. Exact-match protected files\n if (PROTECTED_BASENAMES.has(filename)) {\n throw new FsError({\n message: `Refusing to delete protected file: ${filename}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: path.join(parentDir, filename),\n context: { reason: 'protected_basename' },\n });\n }\n // 2. No path traversal\n if (filename !== path.basename(filename)) {\n throw new FsError({\n message: `Refusing to delete path with traversal: ${filename}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: filename,\n context: { reason: 'path_traversal' },\n });\n }\n // 3. Validate it's a timestamped config backup (config.json.{ts}.bak)\n // before we ever consider deleting it.\n if (!filename.startsWith('config.json.') || !filename.endsWith('.bak')) {\n // Unknown files \u2014 be conservative, refuse\n throw new FsError({\n message: `Refusing to delete unknown file: ${filename}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: path.join(parentDir, filename),\n context: { reason: 'unknown_file_pattern' },\n });\n }\n // 4. Check parent is the .wrongstack root and the target is not a dir\n const resolvedParent = path.resolve(parentDir);\n const parentOfProfile = path.dirname(resolvedParent);\n const isGlobalRoot = path.basename(resolvedParent) === '.wrongstack';\n const isProfileDir =\n path.basename(parentOfProfile) === 'profiles' &&\n path.basename(path.dirname(parentOfProfile)) === '.wrongstack';\n if (!isGlobalRoot && !isProfileDir) {\n throw new FsError({\n message: `Unexpected parent directory for bak prune: ${resolvedParent}`,\n code: ERROR_CODES.FS_DELETE_FAILED,\n path: resolvedParent,\n context: { reason: 'invalid_parent' },\n });\n }\n}\n\n/**\n * Safely delete a file only if it passes safety checks.\n * Never throws \u2014 errors are swallowed (best-effort).\n */\nasync function safeDelete(filePath: string): Promise<void> {\n const dir = path.dirname(filePath);\n const filename = path.basename(filePath);\n try {\n assertSafeToDelete(filename, dir);\n await fs.unlink(filePath);\n } catch (err) {\n // Log but don't crash \u2014 safety check violations are logged for debugging\n if (err instanceof Error && err.message.startsWith('Refusing')) {\n writeErr(`[config-history] SAFETY: ${err.message}\\n`);\n }\n // Best-effort \u2014 ignore other errors (file doesn't exist, etc.)\n }\n}\n\nexport interface HistoryEntry {\n id: string;\n timestamp: string;\n description: string;\n snapshotMasked: Record<string, unknown>;\n diffSummary: string;\n}\n\ninterface HistoryIndex {\n version: 1;\n entries: Array<{ id: string; timestamp: string; description: string }>;\n}\n\nfunction maskConfigSecrets(cfg: Record<string, unknown>): Record<string, unknown> {\n if (typeof cfg !== 'object' || cfg === null) return {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(cfg)) {\n if (isSecretField(k)) {\n out[k] = '[REDACTED]';\n } else if (typeof v === 'object' && v !== null && !Array.isArray(v)) {\n out[k] = maskConfigSecrets(v as Record<string, unknown>);\n } else {\n out[k] = v;\n }\n }\n return out;\n}\n\nfunction diffSummary(oldCfg: Record<string, unknown>, newCfg: Record<string, unknown>): string {\n const changes: string[] = [];\n const allKeys = new Set([...Object.keys(oldCfg), ...Object.keys(newCfg)]);\n for (const k of allKeys) {\n const o = JSON.stringify(oldCfg[k]);\n const n = JSON.stringify(newCfg[k]);\n if (o !== n) {\n if (isSecretField(k)) {\n changes.push(`${k}: [CHANGED]`);\n } else if (typeof newCfg[k] !== 'object') {\n changes.push(`${k}: ${oldCfg[k] ?? '(unset)'} \u2192 ${newCfg[k]}`);\n } else {\n changes.push(`${k}: [CHANGED]`);\n }\n }\n }\n return changes.length > 0 ? changes.slice(0, 5).join(', ') : 'no changes';\n}\n\ntype HomeDirFn = () => string;\nconst defaultHomeDir: HomeDirFn = () => os.homedir();\n\nfunction activeProfileConfigPath(homeFn: HomeDirFn): string {\n const paths =\n homeFn === defaultHomeDir\n ? resolveWstackPaths({ projectRoot: process.cwd() })\n : resolveWstackPaths({ projectRoot: process.cwd(), userHome: homeFn() });\n return paths.profileConfig(paths.profileName);\n}\n\nfunction historyDir(homeFn: HomeDirFn = defaultHomeDir, targetConfigPath?: string): string {\n return path.join(path.dirname(configPath(homeFn, targetConfigPath)), 'config.history', 'entries');\n}\n\nfunction historyIndexPath(homeFn: HomeDirFn = defaultHomeDir, targetConfigPath?: string): string {\n return path.join(path.dirname(configPath(homeFn, targetConfigPath)), 'config.history', 'index.json');\n}\n\nfunction configPath(homeFn: HomeDirFn = defaultHomeDir, targetConfigPath?: string): string {\n return targetConfigPath ?? activeProfileConfigPath(homeFn);\n}\n\nfunction backupLastPath(homeFn: HomeDirFn = defaultHomeDir, targetConfigPath?: string): string {\n return `${configPath(homeFn, targetConfigPath)}.last`;\n}\n\nfunction entryId(ts: string): string {\n return `${ts.replace(/[:.]/g, '-')}-${randomUUID().slice(0, 8)}`;\n}\n\nasync function ensureHistoryDir(\n homeFn: HomeDirFn = defaultHomeDir,\n targetConfigPath?: string,\n): Promise<void> {\n try {\n await fs.mkdir(historyDir(homeFn, targetConfigPath), { recursive: true });\n } catch (err) {\n throw new FsError({\n message: toErrorMessage(err),\n code: ERROR_CODES.FS_MKDIR_FAILED,\n path: historyDir(homeFn, targetConfigPath),\n cause: err,\n });\n }\n}\n\nasync function readIndex(\n homeFn: HomeDirFn = defaultHomeDir,\n targetConfigPath?: string,\n): Promise<HistoryIndex> {\n try {\n const raw = await fs.readFile(historyIndexPath(homeFn, targetConfigPath), 'utf8');\n return JSON.parse(raw) as HistoryIndex;\n } catch {\n return { version: 1, entries: [] };\n }\n}\n\nasync function writeIndex(\n idx: HistoryIndex,\n homeFn: HomeDirFn = defaultHomeDir,\n targetConfigPath?: string,\n): Promise<void> {\n await ensureHistoryDir(homeFn, targetConfigPath);\n // atomicWrite: torn write here would wipe the entire config-history\n // index, hiding the user's prior backups behind a \"no history\" UI.\n try {\n await atomicWrite(historyIndexPath(homeFn, targetConfigPath), JSON.stringify(idx, null, 2));\n } catch (err) {\n throw new FsError({\n message: toErrorMessage(err),\n code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,\n path: historyIndexPath(homeFn, targetConfigPath),\n cause: err,\n });\n }\n}\n\nasync function pruneHistoryEntries(\n idx: HistoryIndex,\n homeFn: HomeDirFn = defaultHomeDir,\n targetConfigPath?: string,\n): Promise<void> {\n const removed = idx.entries.splice(MAX_CONFIG_HISTORY_ENTRIES);\n const keep = new Set(idx.entries.map((entry) => `${entry.id}.json`));\n const dir = historyDir(homeFn, targetConfigPath);\n\n for (const entry of removed) {\n try {\n await fs.unlink(path.join(dir, `${entry.id}.json`));\n } catch {\n // best-effort: stale index entries should not block config writes\n }\n }\n\n try {\n const files = await fs.readdir(dir);\n await Promise.all(\n files\n .filter((file) => file.endsWith('.json') && !keep.has(file))\n .map(async (file) => {\n try {\n await fs.unlink(path.join(dir, file));\n } catch {\n // best-effort: orphan cleanup should not block config writes\n }\n }),\n );\n } catch {\n // best-effort: missing history dir or readdir failure should not block writes\n }\n}\n\n/**\n * Backup current config.json \u2192 config.json.last and timestamped .bak files.\n * Safe to call even if config.json doesn't exist. Never throws.\n *\n * IMPORTANT: config.json and .key are never deleted by this function.\n * Only config.json.*.bak timestamped snapshots are pruned.\n */\nexport async function backupCurrent(\n homeFn: HomeDirFn = defaultHomeDir,\n targetConfigPath?: string,\n): Promise<void> {\n const cfg = configPath(homeFn, targetConfigPath);\n const last = backupLastPath(homeFn, targetConfigPath);\n const ts = Date.now();\n\n // Read existing config content for .last backup\n let content: string | undefined;\n try {\n content = await fs.readFile(cfg, 'utf8');\n } catch {\n // May not exist yet \u2014 that's fine, we just skip the backup\n }\n\n if (content !== undefined) {\n try {\n await atomicWrite(last, content);\n } catch (err) {\n writeErr(\n `[config-history] .last backup failed: ${toErrorMessage(err)}`,\n );\n }\n }\n\n // Create timestamped snapshot\n if (content !== undefined) {\n try {\n const bakPath = `${cfg}.${ts}.bak`;\n await atomicWrite(bakPath, content);\n } catch (err) {\n writeErr(\n `[config-history] timestamped backup failed: ${toErrorMessage(err)}`,\n );\n }\n }\n\n // Prune old .bak files \u2014 keep last 10\n try {\n const dir = path.dirname(cfg);\n const files = await fs.readdir(dir);\n const baks = files\n .filter((f) => f.startsWith('config.json.') && f.endsWith('.bak'))\n .sort()\n .reverse();\n for (const f of baks.slice(MAX_CONFIG_BACKUPS)) {\n await safeDelete(path.join(dir, f));\n }\n } catch (err) {\n writeErr(\n `[config-history] backup prune failed: ${toErrorMessage(err)}`,\n );\n }\n}\n\n/**\n * Append a history entry for a config change.\n */\nexport async function appendHistory(\n oldCfg: Record<string, unknown>,\n newCfg: Record<string, unknown>,\n description: string,\n homeFn: HomeDirFn = defaultHomeDir,\n targetConfigPath?: string,\n): Promise<string> {\n const timestamp = new Date().toISOString();\n const id = entryId(timestamp);\n\n await ensureHistoryDir(homeFn, targetConfigPath);\n\n const entry: HistoryEntry = {\n id,\n timestamp,\n description,\n snapshotMasked: maskConfigSecrets(newCfg) as Record<string, unknown>,\n diffSummary: diffSummary(oldCfg, newCfg),\n };\n\n try {\n await fs.writeFile(\n path.join(historyDir(homeFn, targetConfigPath), `${id}.json`),\n JSON.stringify(entry, null, 2),\n 'utf8',\n );\n } catch (err) {\n throw new FsError({\n message: toErrorMessage(err),\n code: ERROR_CODES.FS_WRITE_FAILED,\n path: path.join(historyDir(homeFn, targetConfigPath), `${id}.json`),\n cause: err,\n });\n }\n\n const idx = await readIndex(homeFn, targetConfigPath);\n idx.entries.unshift({ id, timestamp, description });\n await pruneHistoryEntries(idx, homeFn, targetConfigPath);\n await writeIndex(idx, homeFn, targetConfigPath);\n\n return id;\n}\n\n/**\n * List all history entries (newest first).\n */\nexport async function listHistory(\n homeFn: HomeDirFn = defaultHomeDir,\n targetConfigPath?: string,\n): Promise<HistoryIndex['entries']> {\n const idx = await readIndex(homeFn, targetConfigPath);\n return idx.entries;\n}\n\n/**\n * Get a specific history entry by ID.\n */\nexport async function getHistoryEntry(\n id: string,\n homeFn: HomeDirFn = defaultHomeDir,\n targetConfigPath?: string,\n): Promise<HistoryEntry | null> {\n try {\n const raw = await fs.readFile(\n path.join(historyDir(homeFn, targetConfigPath), `${id}.json`),\n 'utf8',\n );\n return JSON.parse(raw) as HistoryEntry;\n } catch {\n return null;\n }\n}\n\n/**\n * Restore config.json to a given history entry's snapshot.\n */\nexport async function restoreFromHistory(\n id: string,\n homeFn: HomeDirFn = defaultHomeDir,\n targetConfigPath?: string,\n): Promise<{ ok: boolean; backupId: string | null; error?: string | undefined }> {\n const entry = await getHistoryEntry(id, homeFn, targetConfigPath);\n if (!entry) return { ok: false, backupId: null, error: 'History entry not found' };\n\n // Ownership guard \u2014 refuse to write config.json if the calling process\n // does not own the file. Prevents one user on a multi-user system from\n // overwriting another user's config via a shared wrongstack install.\n if (!(await checkConfigOwnership(homeFn, defaultUidFn, targetConfigPath))) {\n return {\n ok: false,\n backupId: null,\n error: 'Operation denied: config file is not owned by current user',\n };\n }\n\n await backupCurrent(homeFn, targetConfigPath);\n\n let oldCfg: Record<string, unknown> = {};\n try {\n const raw = await fs.readFile(configPath(homeFn, targetConfigPath), 'utf8');\n oldCfg = JSON.parse(raw);\n } catch {\n // No config to restore from\n }\n\n try {\n await atomicWrite(configPath(homeFn, targetConfigPath), JSON.stringify(entry.snapshotMasked, null, 2));\n } catch (err) {\n return { ok: false, backupId: null, error: String(err) };\n }\n\n const backupId = await appendHistory(\n oldCfg,\n entry.snapshotMasked as Record<string, unknown>,\n `Restored from history ${id}`,\n homeFn,\n targetConfigPath,\n );\n\n return { ok: true, backupId };\n}\n\n/**\n * Restore config.json to the .last backup.\n */\nexport async function restoreLast(\n homeFn: HomeDirFn = defaultHomeDir,\n targetConfigPath?: string,\n): Promise<{ ok: boolean; error?: string | undefined }> {\n const last = backupLastPath(homeFn, targetConfigPath);\n const cfg = configPath(homeFn, targetConfigPath);\n\n let oldCfg: Record<string, unknown> = {};\n try {\n const raw = await fs.readFile(cfg, 'utf8');\n oldCfg = JSON.parse(raw);\n } catch {\n // Ignore\n }\n\n let lastCfg: Record<string, unknown> = {};\n try {\n const raw = await fs.readFile(last, 'utf8');\n lastCfg = JSON.parse(raw);\n } catch {\n return { ok: false, error: 'No prior backup found' };\n }\n\n // Ownership guard \u2014 refuse to write config.json if the calling process\n // does not own the file. Prevents one user on a multi-user system from\n // overwriting another user's config via a shared wrongstack install.\n if (!(await checkConfigOwnership(homeFn, defaultUidFn, targetConfigPath))) {\n return { ok: false, error: 'Operation denied: config file is not owned by current user' };\n }\n\n await backupCurrent(homeFn, targetConfigPath);\n\n try {\n await atomicWrite(cfg, JSON.stringify(lastCfg, null, 2));\n } catch (err) {\n return { ok: false, error: String(err) };\n }\n\n await appendHistory(oldCfg, lastCfg, 'Restored from config.json.last', homeFn, targetConfigPath);\n\n return { ok: true };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["stat"]
|
|
7
|
+
}
|