@webpresso/agent-kit 3.1.28 → 3.2.0
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.
Potentially problematic release.
This version of @webpresso/agent-kit might be problematic. Click here for more details.
- package/README.md +53 -15
- package/bin/_run.js +179 -19
- package/catalog/AGENTS.md.tpl +6 -6
- package/catalog/agent/agents/implementer.md +45 -0
- package/catalog/agent/rules/ci-cost-local-first.md +139 -0
- package/catalog/agent/rules/ci-test-perf.md +5 -1
- package/catalog/agent/rules/cmd-execution.md +9 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
- package/catalog/agent/rules/pre-implementation.md +30 -2
- package/catalog/agent/rules/supported-agent-clis.md +9 -0
- package/catalog/agent/rules/test-scan-perf.md +80 -0
- package/catalog/agent/rules/typescript-version.md +50 -0
- package/catalog/agent/rules/workflow-skills-routing.md +4 -2
- package/catalog/agent/skills/codex/SKILL.md +25 -1
- package/catalog/agent/skills/fix/SKILL.md +3 -0
- package/catalog/agent/skills/verify/SKILL.md +22 -2
- package/catalog/docs/templates/blueprint.md +1 -1
- package/dist/esm/audit/agents.js +1 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
- package/dist/esm/audit/blueprint-pr-coverage.js +72 -7
- package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
- package/dist/esm/audit/ci-guardrails-wiring.js +90 -0
- package/dist/esm/audit/ci-test-perf.js +30 -0
- package/dist/esm/audit/command-surface.js +4 -2
- package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
- package/dist/esm/audit/host-substitution-risk.js +256 -0
- package/dist/esm/audit/registry.d.ts +4 -4
- package/dist/esm/audit/registry.js +11 -1
- package/dist/esm/audit/roadmap-links.js +6 -0
- package/dist/esm/audit/test-scan-perf.d.ts +18 -0
- package/dist/esm/audit/test-scan-perf.js +275 -0
- package/dist/esm/audit/toolchain-isolation.js +1 -2
- package/dist/esm/audit/typescript-version.d.ts +2 -0
- package/dist/esm/audit/typescript-version.js +123 -0
- package/dist/esm/blueprint/core/parser.js +43 -1
- package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
- package/dist/esm/blueprint/core/validation/criteria.js +41 -2
- package/dist/esm/blueprint/core/validation/state.js +25 -14
- package/dist/esm/blueprint/db/migrations/run.js +4 -2
- package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
- package/dist/esm/blueprint/lifecycle/audit.js +65 -10
- package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
- package/dist/esm/blueprint/lifecycle/engine.d.ts +9 -0
- package/dist/esm/blueprint/lifecycle/engine.js +53 -6
- package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
- package/dist/esm/blueprint/lifecycle/local.js +19 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/review-provenance.js +118 -12
- package/dist/esm/blueprint/markdown/helpers.js +6 -2
- package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
- package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
- package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
- package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
- package/dist/esm/blueprint/trust/command-runner.js +148 -0
- package/dist/esm/blueprint/trust/gates.d.ts +7 -0
- package/dist/esm/blueprint/trust/gates.js +20 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
- package/dist/esm/blueprint/trust/promotion.js +62 -112
- package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
- package/dist/esm/blueprint/trust/scaffold.js +64 -0
- package/dist/esm/blueprint/trust/validator.d.ts +1 -0
- package/dist/esm/blueprint/trust/validator.js +7 -1
- package/dist/esm/blueprint/utils/archive.js +27 -5
- package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
- package/dist/esm/blueprint/utils/document-paths.js +45 -12
- package/dist/esm/build/cli-mcp-parity.js +10 -0
- package/dist/esm/ci/act-replay.js +110 -7
- package/dist/esm/ci/act-runner.d.ts +4 -0
- package/dist/esm/ci/act-runner.js +85 -26
- package/dist/esm/ci/act-worktree-git.d.ts +11 -0
- package/dist/esm/ci/act-worktree-git.js +219 -0
- package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
- package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +34 -0
- package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
- package/dist/esm/cli/commands/agent-launch.js +82 -4
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit-core.js +8 -1
- package/dist/esm/cli/commands/audit.js +20 -5
- package/dist/esm/cli/commands/blueprint/execution.js +18 -6
- package/dist/esm/cli/commands/blueprint/mutations.js +83 -47
- package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
- package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
- package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
- package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
- package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
- package/dist/esm/cli/commands/blueprint/router.js +40 -3
- package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
- package/dist/esm/cli/commands/ci-preflight.js +6 -3
- package/dist/esm/cli/commands/ci.d.ts +8 -1
- package/dist/esm/cli/commands/ci.js +80 -21
- package/dist/esm/cli/commands/compile.js +5 -31
- package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
- package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
- package/dist/esm/cli/commands/dash/keymap.js +1 -0
- package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
- package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
- package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
- package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
- package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
- package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
- package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
- package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
- package/dist/esm/cli/commands/dash/plan-once.d.ts +22 -2
- package/dist/esm/cli/commands/dash/plan-once.js +41 -4
- package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
- package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
- package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
- package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
- package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
- package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
- package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
- package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
- package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
- package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
- package/dist/esm/cli/commands/dash/tui/runtime.js +239 -4
- package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
- package/dist/esm/cli/commands/dash/tui/state.js +257 -4
- package/dist/esm/cli/commands/doctor.js +5 -0
- package/dist/esm/cli/commands/format.js +0 -2
- package/dist/esm/cli/commands/hook.d.ts +5 -0
- package/dist/esm/cli/commands/hook.js +15 -9
- package/dist/esm/cli/commands/init/config.d.ts +0 -1
- package/dist/esm/cli/commands/init/config.js +3 -6
- package/dist/esm/cli/commands/init/convergence-apply.d.ts +24 -0
- package/dist/esm/cli/commands/init/convergence-apply.js +221 -0
- package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
- package/dist/esm/cli/commands/init/convergence-state.js +54 -0
- package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
- package/dist/esm/cli/commands/init/convergence.js +57 -0
- package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
- package/dist/esm/cli/commands/init/git-convergence.js +168 -0
- package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
- package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
- package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
- package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
- package/dist/esm/cli/commands/init/host-visibility.js +94 -5
- package/dist/esm/cli/commands/init/index.d.ts +1 -0
- package/dist/esm/cli/commands/init/index.js +326 -173
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
- package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
- package/dist/esm/cli/commands/init/merge.d.ts +1 -0
- package/dist/esm/cli/commands/init/merge.js +30 -1
- package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
- package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
- package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +11 -0
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.d.ts +5 -6
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +51 -23
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.d.ts +0 -2
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +297 -173
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +7 -0
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
- package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
- package/dist/esm/cli/commands/logs.d.ts +15 -0
- package/dist/esm/cli/commands/logs.js +192 -3
- package/dist/esm/cli/commands/opencode-rotate.d.ts +49 -0
- package/dist/esm/cli/commands/opencode-rotate.js +135 -0
- package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
- package/dist/esm/cli/commands/public-readiness.js +42 -0
- package/dist/esm/cli/commands/quality-log-store.d.ts +1 -0
- package/dist/esm/cli/commands/quality-log-store.js +187 -51
- package/dist/esm/cli/commands/review.d.ts +35 -3
- package/dist/esm/cli/commands/review.js +734 -217
- package/dist/esm/cli/commands/run-wait.d.ts +2 -0
- package/dist/esm/cli/commands/run-wait.js +122 -0
- package/dist/esm/cli/commands/secrets.d.ts +1 -0
- package/dist/esm/cli/commands/secrets.js +4 -1
- package/dist/esm/cli/commands/setup-help.js +1 -1
- package/dist/esm/cli/commands/setup-repair-help.js +1 -1
- package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
- package/dist/esm/cli/direct-provider-launch.js +31 -3
- package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
- package/dist/esm/cli/optional-tool-freshness.js +219 -9
- package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
- package/dist/esm/daemon/domains/git-actions.js +136 -0
- package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
- package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
- package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
- package/dist/esm/daemon/domains/sessions-search.js +29 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
- package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
- package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
- package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
- package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/git-handlers.js +27 -0
- package/dist/esm/daemon/handlers/product.d.ts +7 -0
- package/dist/esm/daemon/handlers/product.js +10 -119
- package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
- package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
- package/dist/esm/daemon/handlers/shared.d.ts +5 -0
- package/dist/esm/daemon/handlers/shared.js +16 -0
- package/dist/esm/daemon/methods.d.ts +8 -1
- package/dist/esm/daemon/methods.js +7 -0
- package/dist/esm/daemon/protocol/contract.d.ts +43 -0
- package/dist/esm/daemon/protocol/contract.js +26 -0
- package/dist/esm/daemon/protocol/index.d.ts +1 -1
- package/dist/esm/daemon/protocol/index.js +1 -1
- package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
- package/dist/esm/hooks/__conformance__/matrix.js +137 -0
- package/dist/esm/hooks/conformance/batch.js +5 -0
- package/dist/esm/hooks/doctor.d.ts +15 -0
- package/dist/esm/hooks/doctor.js +79 -8
- package/dist/esm/hooks/permission-request/index.js +9 -3
- package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
- package/dist/esm/hooks/permission-request/policy.js +13 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
- package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/logger.js +16 -0
- package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/runner.js +68 -16
- package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +58 -4
- package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
- package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.d.ts +0 -1
- package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
- package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.d.ts +9 -7
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
- package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
- package/dist/esm/hooks/shared/guard-outcome.js +215 -0
- package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
- package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
- package/dist/esm/hooks/shared/types.d.ts +8 -5
- package/dist/esm/hooks/shared/types.js +23 -8
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
- package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
- package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
- package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
- package/dist/esm/mcp/blueprint/registration.js +3 -2
- package/dist/esm/mcp/server.d.ts +1 -0
- package/dist/esm/mcp/server.js +27 -0
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +5 -0
- package/dist/esm/mcp/tools/_registry.js +10 -0
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
- package/dist/esm/mcp/tools/_shared/result.js +44 -1
- package/dist/esm/mcp/tools/audit.d.ts +5 -1
- package/dist/esm/mcp/tools/audit.js +40 -0
- package/dist/esm/mcp/tools/audits.d.ts +4 -0
- package/dist/esm/mcp/tools/ci-act.js +9 -11
- package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
- package/dist/esm/mcp/tools/ci-preflight.js +183 -0
- package/dist/esm/mcp/tools/pr-upsert.js +39 -1
- package/dist/esm/mcp/tools/review-run.d.ts +28 -0
- package/dist/esm/mcp/tools/review-run.js +216 -0
- package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
- package/dist/esm/mcp/tools/run-wait.js +593 -0
- package/dist/esm/mcp/tools/session-id.d.ts +3 -0
- package/dist/esm/mcp/tools/session-id.js +45 -0
- package/dist/esm/mcp/tools/session-info.d.ts +3 -0
- package/dist/esm/mcp/tools/session-info.js +49 -0
- package/dist/esm/output-transforms/audit.d.ts +23 -0
- package/dist/esm/output-transforms/audit.js +47 -0
- package/dist/esm/output-transforms/index.js +2 -0
- package/dist/esm/paths/state-root.js +3 -0
- package/dist/esm/review/authority.js +30 -7
- package/dist/esm/review/availability.d.ts +22 -0
- package/dist/esm/review/availability.js +123 -23
- package/dist/esm/review/delivery-verifier.d.ts +27 -0
- package/dist/esm/review/delivery-verifier.js +115 -0
- package/dist/esm/review/events.d.ts +3 -2
- package/dist/esm/review/events.js +23 -3
- package/dist/esm/review/execution/adapters.js +26 -9
- package/dist/esm/review/execution/output-policy.d.ts +2 -0
- package/dist/esm/review/execution/output-policy.js +12 -0
- package/dist/esm/review/execution/review-checkout.js +87 -6
- package/dist/esm/review/execution/supervisor.js +5 -0
- package/dist/esm/review/execution/types.d.ts +8 -0
- package/dist/esm/review/opencode-account.d.ts +24 -0
- package/dist/esm/review/opencode-account.js +68 -0
- package/dist/esm/review/opencode-auth.d.ts +33 -0
- package/dist/esm/review/opencode-auth.js +79 -0
- package/dist/esm/review/subject.d.ts +27 -0
- package/dist/esm/review/subject.js +179 -32
- package/dist/esm/runtime/executor.d.ts +1 -0
- package/dist/esm/runtime/executor.js +2 -1
- package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
- package/dist/esm/runtime/opencode-account-materializer.js +33 -0
- package/dist/esm/secret-gate/runner.d.ts +1 -0
- package/dist/esm/secret-gate/runner.js +4 -1
- package/dist/esm/status/snapshot.js +30 -19
- package/dist/esm/test/duration-sequencer.d.ts +20 -0
- package/dist/esm/test/duration-sequencer.js +54 -0
- package/dist/esm/test/shard-durations.json +922 -0
- package/dist/esm/test/worker-budget.d.ts +6 -0
- package/dist/esm/test/worker-budget.js +59 -1
- package/dist/esm/test-helpers/global-setup.d.ts +1 -1
- package/dist/esm/test-helpers/global-setup.js +68 -24
- package/dist/esm/ultragoal/service.d.ts +7 -0
- package/dist/esm/ultragoal/service.js +42 -2
- package/dist/esm/utils/write-json-file.js +3 -1
- package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
- package/dist/esm/worktrees/codex-project-trust.js +56 -0
- package/dist/esm/worktrees/current.d.ts +1 -0
- package/dist/esm/worktrees/current.js +1 -0
- package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
- package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
- package/dist/esm/worktrees/registry.d.ts +8 -0
- package/dist/esm/worktrees/registry.js +18 -3
- package/package.json +18 -15
|
@@ -18,14 +18,17 @@
|
|
|
18
18
|
* provisioned, exact-version binaries with environment-variable names
|
|
19
19
|
* forwarded through Codex's stdio `env_vars` allow-list.
|
|
20
20
|
*/
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
21
|
+
import { spawnSync } from "node:child_process";
|
|
22
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
23
23
|
import { homedir } from "node:os";
|
|
24
24
|
import { dirname, join } from "node:path";
|
|
25
|
-
import {
|
|
25
|
+
import { agentKitMcpLaunchCommand, findWebpressoMcpEntry, resolveExecutableOnPath, resolveWebpressoMcpEntry, WEBPRESSO_MCP_SERVER_NAME, WEBPRESSO_MCP_SPEC, } from "#cli/commands/init/mcp-spec.js";
|
|
26
|
+
// Re-exported so existing importers (tests) keep a stable path; the canonical
|
|
27
|
+
// implementation now lives in `mcp-spec.ts`.
|
|
28
|
+
export { findWebpressoMcpEntry, WEBPRESSO_MCP_SERVER_NAME };
|
|
29
|
+
import { updateScaffolderTextFile } from "#cli/commands/init/scaffolders/file-transaction.js";
|
|
26
30
|
import { isJsonRecord, isUnsafeRuntimeLauncher, parseJson, parseProjectMcpServers, parseTomlString, } from "#hooks/host-config-contract.js";
|
|
27
31
|
import { appendGlobalCapableVpArgs, resolveGlobalCapableVpCommand, } from "#cli/global-vp.js";
|
|
28
|
-
import { pathCandidates } from "#runtime/command-exists.js";
|
|
29
32
|
import { writeFileAtomic } from "#shared-utils/write-json-file.js";
|
|
30
33
|
export const PLAYWRIGHT_MCP_SERVER_NAME = "playwright";
|
|
31
34
|
export const PLAYWRIGHT_MCP_VERSION = "0.0.78";
|
|
@@ -41,6 +44,9 @@ const PLAYWRIGHT_STDIO_MCP_DEFINITION = {
|
|
|
41
44
|
command: "vp",
|
|
42
45
|
args: ["dlx", PLAYWRIGHT_MCP_PACKAGE, ...PLAYWRIGHT_MCP_ARGS],
|
|
43
46
|
};
|
|
47
|
+
function updateCodexConfig(configPath, transform) {
|
|
48
|
+
return updateScaffolderTextFile(configPath, transform);
|
|
49
|
+
}
|
|
44
50
|
function tomlString(value) {
|
|
45
51
|
return JSON.stringify(value);
|
|
46
52
|
}
|
|
@@ -66,15 +72,254 @@ export function commandMcpCodexBlock(definition) {
|
|
|
66
72
|
if (definition.codexEnvVars && definition.codexEnvVars.length > 0) {
|
|
67
73
|
lines.push(`env_vars = ${tomlStringArray(definition.codexEnvVars)}`);
|
|
68
74
|
}
|
|
75
|
+
if (definition.codexEnv && Object.keys(definition.codexEnv).length > 0) {
|
|
76
|
+
const pairs = Object.entries(definition.codexEnv).map(([key, value]) => `${key} = ${tomlString(value)}`);
|
|
77
|
+
lines.push(`env = { ${pairs.join(", ")} }`);
|
|
78
|
+
}
|
|
69
79
|
lines.push("enabled = true");
|
|
70
80
|
for (const [key, value] of Object.entries(definition.codexFields ?? {})) {
|
|
71
81
|
lines.push(`${key} = ${tomlValue(value)}`);
|
|
72
82
|
}
|
|
73
83
|
return `${lines.join("\n")}\n`;
|
|
74
84
|
}
|
|
85
|
+
function parseTomlTableHeader(line) {
|
|
86
|
+
const match = line.match(/^\s*\[\s*([\s\S]*?)\s*\]\s*(?:#.*)?$/u);
|
|
87
|
+
if (!match || line.trimStart().startsWith("[["))
|
|
88
|
+
return null;
|
|
89
|
+
return parseTomlDottedKey(match[1] ?? "");
|
|
90
|
+
}
|
|
91
|
+
function parseTomlArrayTableHeader(line) {
|
|
92
|
+
const match = line.match(/^\s*\[\[\s*([\s\S]*?)\s*\]\]\s*(?:#.*)?$/u);
|
|
93
|
+
if (!match)
|
|
94
|
+
return null;
|
|
95
|
+
return parseTomlDottedKey(match[1] ?? "");
|
|
96
|
+
}
|
|
97
|
+
function parseTomlDottedKey(raw) {
|
|
98
|
+
const segments = [];
|
|
99
|
+
let i = 0;
|
|
100
|
+
const skipSpace = () => {
|
|
101
|
+
while (/\s/u.test(raw[i] ?? ""))
|
|
102
|
+
i += 1;
|
|
103
|
+
};
|
|
104
|
+
while (i < raw.length) {
|
|
105
|
+
skipSpace();
|
|
106
|
+
const segment = parseTomlKeySegment(raw, i);
|
|
107
|
+
if (!segment)
|
|
108
|
+
return null;
|
|
109
|
+
segments.push(segment.value);
|
|
110
|
+
i = segment.next;
|
|
111
|
+
skipSpace();
|
|
112
|
+
if (i >= raw.length)
|
|
113
|
+
break;
|
|
114
|
+
if (raw[i] !== ".")
|
|
115
|
+
return null;
|
|
116
|
+
i += 1;
|
|
117
|
+
}
|
|
118
|
+
return segments.length > 0 ? segments : null;
|
|
119
|
+
}
|
|
120
|
+
function parseTomlKeySegment(raw, start) {
|
|
121
|
+
const first = raw[start];
|
|
122
|
+
if (first === "'") {
|
|
123
|
+
const end = raw.indexOf("'", start + 1);
|
|
124
|
+
if (end === -1)
|
|
125
|
+
return null;
|
|
126
|
+
return { value: raw.slice(start + 1, end), next: end + 1 };
|
|
127
|
+
}
|
|
128
|
+
if (first === '"') {
|
|
129
|
+
return parseBasicTomlKeySegment(raw, start);
|
|
130
|
+
}
|
|
131
|
+
let end = start;
|
|
132
|
+
while (/[A-Za-z0-9_-]/u.test(raw[end] ?? ""))
|
|
133
|
+
end += 1;
|
|
134
|
+
if (end === start)
|
|
135
|
+
return null;
|
|
136
|
+
return { value: raw.slice(start, end), next: end };
|
|
137
|
+
}
|
|
138
|
+
function parseBasicTomlKeySegment(raw, start) {
|
|
139
|
+
let value = "";
|
|
140
|
+
for (let i = start + 1; i < raw.length; i += 1) {
|
|
141
|
+
const ch = raw[i];
|
|
142
|
+
if (ch === '"')
|
|
143
|
+
return { value, next: i + 1 };
|
|
144
|
+
if (ch !== "\\") {
|
|
145
|
+
value += ch;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const escaped = raw[i + 1];
|
|
149
|
+
if (escaped === undefined)
|
|
150
|
+
return null;
|
|
151
|
+
const simple = {
|
|
152
|
+
b: "\b",
|
|
153
|
+
t: "\t",
|
|
154
|
+
n: "\n",
|
|
155
|
+
f: "\f",
|
|
156
|
+
r: "\r",
|
|
157
|
+
'"': '"',
|
|
158
|
+
"\\": "\\",
|
|
159
|
+
};
|
|
160
|
+
if (escaped in simple) {
|
|
161
|
+
value += simple[escaped];
|
|
162
|
+
i += 1;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (escaped === "u" || escaped === "U") {
|
|
166
|
+
const width = escaped === "u" ? 4 : 8;
|
|
167
|
+
const hex = raw.slice(i + 2, i + 2 + width);
|
|
168
|
+
if (!new RegExp(`^[0-9A-Fa-f]{${width}}$`, "u").test(hex))
|
|
169
|
+
return null;
|
|
170
|
+
value += String.fromCodePoint(Number.parseInt(hex, 16));
|
|
171
|
+
i += 1 + width;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
function isServerTableHeader(line, serverId) {
|
|
179
|
+
const segments = parseTomlTableHeader(line);
|
|
180
|
+
return segments?.length === 2 && segments[0] === "mcp_servers" && segments[1] === serverId;
|
|
181
|
+
}
|
|
75
182
|
function isOwnedSubTableHeader(line, serverId) {
|
|
76
|
-
const
|
|
77
|
-
return
|
|
183
|
+
const segments = parseTomlTableHeader(line);
|
|
184
|
+
return (segments !== null &&
|
|
185
|
+
segments.length > 2 &&
|
|
186
|
+
segments[0] === "mcp_servers" &&
|
|
187
|
+
segments[1] === serverId);
|
|
188
|
+
}
|
|
189
|
+
function isOwnedServerOrDescendantTableHeader(line, serverId) {
|
|
190
|
+
const segments = parseTomlArrayTableHeader(line) ?? parseTomlTableHeader(line);
|
|
191
|
+
return (segments !== null &&
|
|
192
|
+
segments.length >= 2 &&
|
|
193
|
+
segments[0] === "mcp_servers" &&
|
|
194
|
+
segments[1] === serverId);
|
|
195
|
+
}
|
|
196
|
+
function tomlLinesStartingInsideMultilineString(lines) {
|
|
197
|
+
const insideAtStart = [];
|
|
198
|
+
let delimiter = null;
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
insideAtStart.push(delimiter !== null);
|
|
201
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
202
|
+
if (delimiter !== null) {
|
|
203
|
+
const end = findTomlMultilineStringEnd(line, i, delimiter);
|
|
204
|
+
if (end === -1)
|
|
205
|
+
break;
|
|
206
|
+
i = end + delimiter.length - 1;
|
|
207
|
+
delimiter = null;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const ch = line[i];
|
|
211
|
+
if (ch === "#")
|
|
212
|
+
break;
|
|
213
|
+
if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
|
|
214
|
+
delimiter = line.startsWith('"""', i) ? '"""' : "'''";
|
|
215
|
+
i += delimiter.length - 1;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (ch === '"') {
|
|
219
|
+
i += 1;
|
|
220
|
+
while (i < line.length) {
|
|
221
|
+
const inner = line[i];
|
|
222
|
+
if (inner === "\\") {
|
|
223
|
+
i += 2;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (inner === '"')
|
|
227
|
+
break;
|
|
228
|
+
i += 1;
|
|
229
|
+
}
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (ch === "'") {
|
|
233
|
+
const end = line.indexOf("'", i + 1);
|
|
234
|
+
if (end === -1)
|
|
235
|
+
break;
|
|
236
|
+
i = end;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return insideAtStart;
|
|
241
|
+
}
|
|
242
|
+
function findTomlMultilineStringEnd(line, start, delimiter) {
|
|
243
|
+
if (delimiter === "'''")
|
|
244
|
+
return line.indexOf(delimiter, start);
|
|
245
|
+
for (let i = start; i < line.length; i += 1) {
|
|
246
|
+
if (!line.startsWith(delimiter, i))
|
|
247
|
+
continue;
|
|
248
|
+
let backslashes = 0;
|
|
249
|
+
for (let j = i - 1; j >= 0 && line[j] === "\\"; j -= 1)
|
|
250
|
+
backslashes += 1;
|
|
251
|
+
if (backslashes % 2 === 0)
|
|
252
|
+
return i;
|
|
253
|
+
}
|
|
254
|
+
return -1;
|
|
255
|
+
}
|
|
256
|
+
function tomlAssignmentKey(line) {
|
|
257
|
+
let quote = null;
|
|
258
|
+
let escaped = false;
|
|
259
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
260
|
+
const ch = line[i];
|
|
261
|
+
if (quote !== null) {
|
|
262
|
+
if (quote === '"' && escaped) {
|
|
263
|
+
escaped = false;
|
|
264
|
+
}
|
|
265
|
+
else if (quote === '"' && ch === "\\") {
|
|
266
|
+
escaped = true;
|
|
267
|
+
}
|
|
268
|
+
else if (ch === quote) {
|
|
269
|
+
quote = null;
|
|
270
|
+
}
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (ch === '"' || ch === "'") {
|
|
274
|
+
quote = ch;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (ch === "#")
|
|
278
|
+
return null;
|
|
279
|
+
if (ch === "=")
|
|
280
|
+
return parseTomlDottedKey(line.slice(0, i).trim());
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
function hasDottedServerDefinition(lines, startsInsideMultiline, serverId) {
|
|
285
|
+
let currentTable = null;
|
|
286
|
+
return lines.some((line, index) => {
|
|
287
|
+
if (startsInsideMultiline[index])
|
|
288
|
+
return false;
|
|
289
|
+
const arrayTable = parseTomlArrayTableHeader(line);
|
|
290
|
+
if (arrayTable !== null &&
|
|
291
|
+
arrayTable.length >= 2 &&
|
|
292
|
+
arrayTable[0] === "mcp_servers" &&
|
|
293
|
+
arrayTable[1] === serverId) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
const table = parseTomlTableHeader(line);
|
|
297
|
+
if (table !== null) {
|
|
298
|
+
currentTable = table;
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
const segments = tomlAssignmentKey(line);
|
|
302
|
+
return (segments !== null &&
|
|
303
|
+
((segments.length >= 2 && segments[0] === "mcp_servers" && segments[1] === serverId) ||
|
|
304
|
+
(currentTable?.length === 1 &&
|
|
305
|
+
currentTable[0] === "mcp_servers" &&
|
|
306
|
+
segments[0] === serverId)));
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
export function hasAmbiguousMcpServerDottedDefinition(raw, serverId) {
|
|
310
|
+
const lines = raw.trimEnd().split(/\r?\n/);
|
|
311
|
+
const startsInsideMultiline = tomlLinesStartingInsideMultilineString(lines);
|
|
312
|
+
const start = lines.findIndex((line, index) => !startsInsideMultiline[index] && isServerTableHeader(line, serverId));
|
|
313
|
+
return start === -1 && hasDottedServerDefinition(lines, startsInsideMultiline, serverId);
|
|
314
|
+
}
|
|
315
|
+
export function hasManagedMcpServerTableConflict(raw, serverId) {
|
|
316
|
+
const lines = raw.trimEnd().split(/\r?\n/);
|
|
317
|
+
const startsInsideMultiline = tomlLinesStartingInsideMultilineString(lines);
|
|
318
|
+
const start = lines.findIndex((line, index) => !startsInsideMultiline[index] && isServerTableHeader(line, serverId));
|
|
319
|
+
if (start === -1)
|
|
320
|
+
return hasDottedServerDefinition(lines, startsInsideMultiline, serverId);
|
|
321
|
+
const end = managedSectionEnd(lines, startsInsideMultiline, start, serverId);
|
|
322
|
+
return hasOwnedServerTableOutsideManagedSection(lines, startsInsideMultiline, start, end, serverId);
|
|
78
323
|
}
|
|
79
324
|
/**
|
|
80
325
|
* Find the exclusive end index of the managed section region: the parent block
|
|
@@ -84,9 +329,11 @@ function isOwnedSubTableHeader(line, serverId) {
|
|
|
84
329
|
* sub-tables under foreign or absent parents are never touched (they remain
|
|
85
330
|
* doctor-report-only territory).
|
|
86
331
|
*/
|
|
87
|
-
function managedSectionEnd(lines, start, serverId) {
|
|
332
|
+
function managedSectionEnd(lines, startsInsideMultiline, start, serverId) {
|
|
88
333
|
let end = lines.length;
|
|
89
334
|
for (let i = start + 1; i < lines.length; i += 1) {
|
|
335
|
+
if (startsInsideMultiline[i])
|
|
336
|
+
continue;
|
|
90
337
|
if (!lines[i].trim().startsWith("["))
|
|
91
338
|
continue;
|
|
92
339
|
if (isOwnedSubTableHeader(lines[i], serverId))
|
|
@@ -94,20 +341,43 @@ function managedSectionEnd(lines, start, serverId) {
|
|
|
94
341
|
end = i;
|
|
95
342
|
break;
|
|
96
343
|
}
|
|
344
|
+
// Trailing comment/blank lines between the managed block and the next table
|
|
345
|
+
// belong to the surrounding document (often annotating the *next* section) —
|
|
346
|
+
// leave them in place rather than swallowing them into the regenerated block.
|
|
347
|
+
while (end > start + 1) {
|
|
348
|
+
const previous = lines[end - 1].trim();
|
|
349
|
+
if (previous.length === 0 || previous.startsWith("#")) {
|
|
350
|
+
end -= 1;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
97
355
|
return end;
|
|
98
356
|
}
|
|
99
|
-
function
|
|
100
|
-
|
|
357
|
+
function hasOwnedServerTableOutsideManagedSection(lines, startsInsideMultiline, start, end, serverId) {
|
|
358
|
+
return lines.some((line, index) => !startsInsideMultiline[index] &&
|
|
359
|
+
(index < start || index >= end) &&
|
|
360
|
+
isOwnedServerOrDescendantTableHeader(line, serverId));
|
|
361
|
+
}
|
|
362
|
+
export function upsertMcpServerBlock(raw, serverId, block) {
|
|
101
363
|
const lines = raw.trimEnd().split(/\r?\n/);
|
|
102
364
|
const hasContent = raw.trim().length > 0;
|
|
103
|
-
const
|
|
365
|
+
const startsInsideMultiline = tomlLinesStartingInsideMultilineString(lines);
|
|
366
|
+
const start = lines.findIndex((line, index) => !startsInsideMultiline[index] && isServerTableHeader(line, serverId));
|
|
104
367
|
if (start === -1) {
|
|
368
|
+
if (hasDottedServerDefinition(lines, startsInsideMultiline, serverId))
|
|
369
|
+
return raw;
|
|
105
370
|
const prefix = hasContent ? `${raw.trimEnd()}\n\n` : "";
|
|
106
371
|
return `${prefix}${block}`;
|
|
107
372
|
}
|
|
108
|
-
const end = managedSectionEnd(lines, start, serverId);
|
|
373
|
+
const end = managedSectionEnd(lines, startsInsideMultiline, start, serverId);
|
|
374
|
+
if (hasOwnedServerTableOutsideManagedSection(lines, startsInsideMultiline, start, end, serverId)) {
|
|
375
|
+
return raw;
|
|
376
|
+
}
|
|
109
377
|
const replacement = block.trimEnd().split("\n");
|
|
110
|
-
|
|
378
|
+
// Separate the block from a preserved tail — unless the tail already starts
|
|
379
|
+
// with its own blank line (kept by the trailing-comment walk-back).
|
|
380
|
+
if (end < lines.length && lines[end].trim().length !== 0)
|
|
111
381
|
replacement.push("");
|
|
112
382
|
return [...lines.slice(0, start), ...replacement, ...lines.slice(end)].join("\n") + "\n";
|
|
113
383
|
}
|
|
@@ -124,31 +394,11 @@ export function commandMcpClaudeServer(definition) {
|
|
|
124
394
|
}
|
|
125
395
|
return server;
|
|
126
396
|
}
|
|
127
|
-
export const WEBPRESSO_MCP_SERVER_NAME = "webpresso";
|
|
128
397
|
export const WEBPRESSO_MCP_HEADER = `[mcp_servers.${WEBPRESSO_MCP_SERVER_NAME}]`;
|
|
129
398
|
function defaultConfigPath() {
|
|
130
399
|
const codexHome = process.env.CODEX_HOME || join(process.env.HOME || homedir(), ".codex");
|
|
131
400
|
return join(codexHome, "config.toml");
|
|
132
401
|
}
|
|
133
|
-
function resolvePinnedMcpExecutable(binary, env) {
|
|
134
|
-
for (const candidate of pathCandidates(binary, {
|
|
135
|
-
pathEnv: env.PATH,
|
|
136
|
-
pathExtEnv: env.PATHEXT,
|
|
137
|
-
})) {
|
|
138
|
-
try {
|
|
139
|
-
if (!statSync(candidate).isFile())
|
|
140
|
-
continue;
|
|
141
|
-
if (process.platform !== "win32")
|
|
142
|
-
accessSync(candidate, constants.X_OK);
|
|
143
|
-
return candidate;
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
// Continue scanning PATH. Returning the candidate, not its realpath, keeps
|
|
147
|
-
// the generated config anchored to Vite+'s stable global-bin symlink.
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
402
|
function installedGlobalPackageMetadata(vpCommand, packageName, spawn) {
|
|
153
403
|
const listCommand = appendGlobalCapableVpArgs(vpCommand, [
|
|
154
404
|
"list",
|
|
@@ -198,7 +448,7 @@ function selectPackageBinary(definition, metadata) {
|
|
|
198
448
|
function provisionPinnedCodexMcp(input, definition) {
|
|
199
449
|
const env = input.env ?? process.env;
|
|
200
450
|
const spawn = input.spawn ?? spawnSync;
|
|
201
|
-
const resolveExecutable = input.resolveExecutable ?? ((binary) =>
|
|
451
|
+
const resolveExecutable = input.resolveExecutable ?? ((binary) => resolveExecutableOnPath(binary, env));
|
|
202
452
|
const vpCommand = input.resolveVpCommand !== undefined
|
|
203
453
|
? input.resolveVpCommand()
|
|
204
454
|
: resolveGlobalCapableVpCommand(env.PATH ?? "", process.platform, env);
|
|
@@ -271,13 +521,8 @@ export function ensurePinnedCodexMcp(input, definition) {
|
|
|
271
521
|
reason: provisioned.reason,
|
|
272
522
|
};
|
|
273
523
|
}
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
if (next === existing)
|
|
277
|
-
return { kind: "unchanged", path: configPath };
|
|
278
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
279
|
-
writeFileAtomic(configPath, next, "utf8");
|
|
280
|
-
return { kind: "written", path: configPath };
|
|
524
|
+
const result = updateCodexConfig(configPath, (existing) => upsertCommandMcpServer(existing, provisioned.definition));
|
|
525
|
+
return { kind: result === "written" ? "written" : "unchanged", path: configPath };
|
|
281
526
|
}
|
|
282
527
|
function removeUnsafePackageManagerMcpBlocks(raw) {
|
|
283
528
|
const lines = raw.split(/\r?\n/u);
|
|
@@ -329,15 +574,11 @@ function removeUnsafePackageManagerMcpBlocks(raw) {
|
|
|
329
574
|
export function ensureCodexProjectMcpServers(input) {
|
|
330
575
|
const configPath = input.configPath ?? defaultConfigPath();
|
|
331
576
|
const sourcePath = input.sourcePath ?? join(input.repoRoot, ".mcp.json");
|
|
332
|
-
const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
333
|
-
const withoutUnsafeLaunchers = removeUnsafePackageManagerMcpBlocks(existing);
|
|
334
|
-
const cleanedUnsafeLaunchers = withoutUnsafeLaunchers !== existing;
|
|
335
577
|
const persistSafetyCleanup = () => {
|
|
336
|
-
if (
|
|
578
|
+
if (input.options.dryRun)
|
|
337
579
|
return undefined;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
return true;
|
|
580
|
+
const result = updateCodexConfig(configPath, removeUnsafePackageManagerMcpBlocks);
|
|
581
|
+
return result === "written" ? true : undefined;
|
|
341
582
|
};
|
|
342
583
|
const parsed = existsSync(sourcePath)
|
|
343
584
|
? parseProjectMcpServers(readFileSync(sourcePath, "utf8"))
|
|
@@ -368,12 +609,10 @@ export function ensureCodexProjectMcpServers(input) {
|
|
|
368
609
|
}
|
|
369
610
|
provisioned.push(result.definition);
|
|
370
611
|
}
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
612
|
+
const result = updateCodexConfig(configPath, (existing) => provisioned.reduce(upsertCommandMcpServer, removeUnsafePackageManagerMcpBlocks(existing)));
|
|
613
|
+
if (result === "unchanged") {
|
|
373
614
|
return { kind: "codex-project-mcp-unchanged", path: configPath, servers };
|
|
374
615
|
}
|
|
375
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
376
|
-
writeFileAtomic(configPath, next, "utf8");
|
|
377
616
|
return { kind: "codex-project-mcp-written", path: configPath, servers };
|
|
378
617
|
}
|
|
379
618
|
function claudePlaywrightServer() {
|
|
@@ -421,130 +660,13 @@ export function ensureClaudePlaywrightMcp(input) {
|
|
|
421
660
|
// ────────────────────────────────────────────────────────────────────────────
|
|
422
661
|
// Agent-kit MCP server registration
|
|
423
662
|
// ────────────────────────────────────────────────────────────────────────────
|
|
424
|
-
const WP_BIN_RELATIVE = join("bin", process.platform === "win32" ? "wp.cmd" : "wp");
|
|
425
|
-
const WP_LAUNCHER_REQUIRED_RELATIVES = [
|
|
426
|
-
WP_BIN_RELATIVE,
|
|
427
|
-
join("bin", "_run.js"),
|
|
428
|
-
join("bin", "runtime-lanes.js"),
|
|
429
|
-
join("bin", "runtime-manifest.json"),
|
|
430
|
-
];
|
|
431
|
-
/**
|
|
432
|
-
* Resolve the absolute path to webpresso's global `bin/wp` launcher on this machine. Probes
|
|
433
|
-
* the locations consumers use to install webpresso, in order of stability:
|
|
434
|
-
*
|
|
435
|
-
* 1. the stable global `wp` shim available on PATH
|
|
436
|
-
* 2. the currently executing `@webpresso/agent-kit` package
|
|
437
|
-
* 3. Claude plugin install — `~/.claude/plugins/cache/.../agent-kit/`
|
|
438
|
-
* (path-stable; updated by Claude Code's plugin manager)
|
|
439
|
-
* 4. bun global — `~/.bun/install/global/node_modules/@webpresso/agent-kit/`
|
|
440
|
-
* 5. pnpm global — `$(pnpm root -g)/@webpresso/agent-kit/`
|
|
441
|
-
* 6. npm global — `$(npm root -g)/@webpresso/agent-kit/`
|
|
442
|
-
*
|
|
443
|
-
* Returns `null` when none of the candidates contain `bin/wp`. The caller
|
|
444
|
-
* surfaces a clear error rather than writing a broken codex config.
|
|
445
|
-
*/
|
|
446
|
-
export function findWebpressoMcpEntry(probe = {}) {
|
|
447
|
-
return resolveWebpressoMcpEntry(probe).entryPath;
|
|
448
|
-
}
|
|
449
|
-
function isUsableWebpressoMcpRoot(root) {
|
|
450
|
-
return WP_LAUNCHER_REQUIRED_RELATIVES.every((relativePath) => existsSync(join(root, relativePath)));
|
|
451
|
-
}
|
|
452
|
-
function resolveWebpressoMcpEntry(probe = {}) {
|
|
453
|
-
const checked = [];
|
|
454
|
-
if (probe.candidates === undefined) {
|
|
455
|
-
const stableEntry = probe.stableEntry !== undefined
|
|
456
|
-
? probe.stableEntry()
|
|
457
|
-
: resolvePinnedMcpExecutable("wp", process.env);
|
|
458
|
-
if (stableEntry)
|
|
459
|
-
return { entryPath: stableEntry, checked: [stableEntry] };
|
|
460
|
-
}
|
|
461
|
-
const candidateGroups = probe.candidates !== undefined ? [probe.candidates] : [defaultFixedCandidates()];
|
|
462
|
-
for (const candidates of candidateGroups) {
|
|
463
|
-
for (const root of candidates) {
|
|
464
|
-
if (!root)
|
|
465
|
-
continue;
|
|
466
|
-
checked.push(root);
|
|
467
|
-
const wpBin = join(root, WP_BIN_RELATIVE);
|
|
468
|
-
if (isUsableWebpressoMcpRoot(root))
|
|
469
|
-
return { entryPath: wpBin, checked };
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
if (probe.candidates === undefined) {
|
|
473
|
-
for (const root of defaultPackageManagerCandidates(probe)) {
|
|
474
|
-
if (!root)
|
|
475
|
-
continue;
|
|
476
|
-
checked.push(root);
|
|
477
|
-
const wpBin = join(root, WP_BIN_RELATIVE);
|
|
478
|
-
if (isUsableWebpressoMcpRoot(root))
|
|
479
|
-
return { entryPath: wpBin, checked };
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
return { entryPath: null, checked };
|
|
483
|
-
}
|
|
484
|
-
function defaultFixedCandidates() {
|
|
485
|
-
const home = process.env.HOME || homedir();
|
|
486
|
-
const packageRoot = currentAgentKitPackageRoot();
|
|
487
|
-
const claudePlugin = join(home, ".claude", "plugins", "cache", "webpresso", "agent-kit");
|
|
488
|
-
const bunGlobal = join(home, ".bun", "install", "global", "node_modules", "@webpresso", "agent-kit");
|
|
489
|
-
return [packageRoot ?? "", claudePlugin, bunGlobal];
|
|
490
|
-
}
|
|
491
|
-
function defaultPackageManagerCandidates(probe) {
|
|
492
|
-
const pnpmRoot = (probe.pnpmGlobalRoot ?? probePnpmGlobalRoot)();
|
|
493
|
-
const npmRoot = (probe.npmGlobalRoot ?? probeNpmGlobalRoot)();
|
|
494
|
-
return [
|
|
495
|
-
pnpmRoot ? join(pnpmRoot, "@webpresso", "agent-kit") : "",
|
|
496
|
-
npmRoot ? join(npmRoot, "@webpresso", "agent-kit") : "",
|
|
497
|
-
];
|
|
498
|
-
}
|
|
499
|
-
function currentAgentKitPackageRoot() {
|
|
500
|
-
let current = dirname(fileURLToPath(import.meta.url));
|
|
501
|
-
for (let depth = 0; depth < 12; depth += 1) {
|
|
502
|
-
const packagePath = join(current, "package.json");
|
|
503
|
-
if (existsSync(packagePath)) {
|
|
504
|
-
try {
|
|
505
|
-
const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
506
|
-
if (pkg.name === "@webpresso/agent-kit")
|
|
507
|
-
return current;
|
|
508
|
-
}
|
|
509
|
-
catch {
|
|
510
|
-
return null;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
const parent = dirname(current);
|
|
514
|
-
if (parent === current)
|
|
515
|
-
break;
|
|
516
|
-
current = parent;
|
|
517
|
-
}
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
function probePnpmGlobalRoot() {
|
|
521
|
-
return runQuiet("pnpm", ["root", "-g"]);
|
|
522
|
-
}
|
|
523
|
-
function probeNpmGlobalRoot() {
|
|
524
|
-
return runQuiet("npm", ["root", "-g"]);
|
|
525
|
-
}
|
|
526
|
-
function runQuiet(cmd, args) {
|
|
527
|
-
try {
|
|
528
|
-
const output = execFileSync(cmd, [...args], {
|
|
529
|
-
encoding: "utf8",
|
|
530
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
531
|
-
});
|
|
532
|
-
const trimmed = output.trim();
|
|
533
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
534
|
-
}
|
|
535
|
-
catch {
|
|
536
|
-
return null;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
export function agentKitMcpLaunchCommand(entryPath) {
|
|
540
|
-
return { command: entryPath, args: ["mcp"] };
|
|
541
|
-
}
|
|
542
663
|
export function agentKitMcpBlock(entryPath) {
|
|
543
664
|
const launch = agentKitMcpLaunchCommand(entryPath);
|
|
544
665
|
return commandMcpCodexBlock({
|
|
545
666
|
id: WEBPRESSO_MCP_SERVER_NAME,
|
|
546
667
|
command: launch.command,
|
|
547
668
|
args: launch.args,
|
|
669
|
+
codexEnv: WEBPRESSO_MCP_SPEC.env,
|
|
548
670
|
});
|
|
549
671
|
}
|
|
550
672
|
export function upsertWebpressoMcpServer(raw, entryPath) {
|
|
@@ -562,11 +684,13 @@ export function ensureCodexWebpressoMcp(input) {
|
|
|
562
684
|
return { kind: "codex-webpresso-mcp-not-installed", path: configPath, checked };
|
|
563
685
|
}
|
|
564
686
|
const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
565
|
-
|
|
566
|
-
|
|
687
|
+
if (hasAmbiguousMcpServerDottedDefinition(existing, WEBPRESSO_MCP_SERVER_NAME) ||
|
|
688
|
+
hasManagedMcpServerTableConflict(existing, WEBPRESSO_MCP_SERVER_NAME)) {
|
|
689
|
+
return { kind: "codex-webpresso-mcp-ambiguous", path: configPath, entryPath };
|
|
690
|
+
}
|
|
691
|
+
const result = updateCodexConfig(configPath, (raw) => upsertWebpressoMcpServer(raw, entryPath));
|
|
692
|
+
if (result === "unchanged") {
|
|
567
693
|
return { kind: "codex-webpresso-mcp-unchanged", path: configPath, entryPath };
|
|
568
694
|
}
|
|
569
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
570
|
-
writeFileAtomic(configPath, next, "utf8");
|
|
571
695
|
return { kind: "codex-webpresso-mcp-written", path: configPath, entryPath };
|
|
572
696
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Serialize a complete read/modify/write transaction for a host config file. */
|
|
2
|
+
export declare function withScaffolderFileLock<T>(path: string, action: () => T): T;
|
|
3
|
+
/**
|
|
4
|
+
* Serialize a text-file read/modify/write and install the replacement strictly
|
|
5
|
+
* atomically without weakening an existing file's permissions.
|
|
6
|
+
*/
|
|
7
|
+
export declare function updateScaffolderTextFile(path: string, transform: (existing: string) => string): "written" | "unchanged";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import lockfile from "proper-lockfile";
|
|
4
|
+
import { writeFileAtomicStrict } from "#shared-utils/write-json-file.js";
|
|
5
|
+
const LOCK_TIMEOUT_MS = 5_000;
|
|
6
|
+
const LOCK_POLL_MS = 25;
|
|
7
|
+
const LOCK_STALE_MS = 30_000;
|
|
8
|
+
function sleepSync(ms) {
|
|
9
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
10
|
+
}
|
|
11
|
+
/** Serialize a complete read/modify/write transaction for a host config file. */
|
|
12
|
+
export function withScaffolderFileLock(path, action) {
|
|
13
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
14
|
+
const startedAt = Date.now();
|
|
15
|
+
let release;
|
|
16
|
+
while (!release) {
|
|
17
|
+
try {
|
|
18
|
+
release = lockfile.lockSync(path, {
|
|
19
|
+
realpath: false,
|
|
20
|
+
stale: LOCK_STALE_MS,
|
|
21
|
+
update: LOCK_STALE_MS / 2,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error.code !== "ELOCKED")
|
|
26
|
+
throw error;
|
|
27
|
+
if (Date.now() - startedAt >= LOCK_TIMEOUT_MS) {
|
|
28
|
+
throw new Error(`Timed out waiting for host config lock: ${path}`, { cause: error });
|
|
29
|
+
}
|
|
30
|
+
sleepSync(LOCK_POLL_MS);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return action();
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
release();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Serialize a text-file read/modify/write and install the replacement strictly
|
|
42
|
+
* atomically without weakening an existing file's permissions.
|
|
43
|
+
*/
|
|
44
|
+
export function updateScaffolderTextFile(path, transform) {
|
|
45
|
+
return withScaffolderFileLock(path, () => {
|
|
46
|
+
const existed = existsSync(path);
|
|
47
|
+
if (existed && lstatSync(path).isSymbolicLink()) {
|
|
48
|
+
throw new Error(`Refusing to replace symlinked host config: ${path}`);
|
|
49
|
+
}
|
|
50
|
+
const existing = existed ? readFileSync(path, "utf8") : "";
|
|
51
|
+
const next = transform(existing);
|
|
52
|
+
if (next === existing)
|
|
53
|
+
return "unchanged";
|
|
54
|
+
const mode = existed ? lstatSync(path).mode & 0o777 : 0o600;
|
|
55
|
+
writeFileAtomicStrict(path, next, { encoding: "utf8", mode });
|
|
56
|
+
return "written";
|
|
57
|
+
});
|
|
58
|
+
}
|