@reticlehq/server 3.8.2 → 3.10.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.
- package/LICENSE +201 -104
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/SKILL.md +13 -12
- package/bin/launch.js +13 -0
- package/bin/package.json +3 -0
- package/bin/reticle.js +66 -8
- package/dist/chromium-launch-options.d.ts +25 -0
- package/dist/chromium-launch-options.js +21 -1
- package/dist/command/cli/adhoc-suite.js +2 -0
- package/dist/command/cli/adhoc-verdict.d.ts +2 -43
- package/dist/command/cli/adhoc-verdict.js +50 -160
- package/dist/command/cli/answers/gate-exit.d.ts +6 -2
- package/dist/command/cli/answers/gate-exit.js +12 -2
- package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
- package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
- package/dist/command/cli/ci-hint.d.ts +44 -0
- package/dist/command/cli/ci-hint.js +72 -0
- package/dist/command/cli/cli-doctor.d.ts +6 -1
- package/dist/command/cli/cli-doctor.js +46 -5
- package/dist/command/cli/cli-flow-commands.d.ts +7 -1
- package/dist/command/cli/cli-flow-commands.js +39 -24
- package/dist/command/cli/cli-kill.d.ts +21 -0
- package/dist/command/cli/cli-kill.js +47 -4
- package/dist/command/cli/cli-parse-compare.d.ts +22 -0
- package/dist/command/cli/cli-parse-compare.js +75 -0
- package/dist/command/cli/cli-parse-grade.d.ts +36 -0
- package/dist/command/cli/cli-parse-grade.js +75 -0
- package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
- package/dist/command/cli/cli-parse-grammar.js +18 -20
- package/dist/command/cli/cli-parse-verify.d.ts +10 -0
- package/dist/command/cli/cli-parse-verify.js +63 -14
- package/dist/command/cli/cli-parse.d.ts +22 -17
- package/dist/command/cli/cli-parse.js +54 -32
- package/dist/command/cli/cli-update-commands.d.ts +1 -1
- package/dist/command/cli/cli-update-commands.js +29 -20
- package/dist/command/cli/cli-usage.js +61 -31
- package/dist/command/cli/cli-verify.d.ts +17 -3
- package/dist/command/cli/cli-verify.js +59 -21
- package/dist/command/cli/cloud-cli.js +2 -119
- package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
- package/dist/command/cli/daemon-link/daemon-client.js +205 -0
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
- package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
- package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
- package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
- package/dist/command/cli/doctor/doctor-fix.js +89 -0
- package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/doctor-repairs.d.ts +64 -0
- package/dist/command/cli/doctor-repairs.js +214 -0
- package/dist/command/cli/drive/drive-command.js +3 -7
- package/dist/command/cli/ensure-daemon.d.ts +26 -0
- package/dist/command/cli/ensure-daemon.js +57 -0
- package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
- package/dist/command/cli/finished-run/grade-command.js +180 -0
- package/dist/command/cli/first-use-checks.d.ts +41 -0
- package/dist/command/cli/first-use-checks.js +127 -0
- package/dist/command/cli/first-verdict-node.d.ts +3 -0
- package/dist/command/cli/first-verdict-node.js +189 -0
- package/dist/command/cli/first-verdict.d.ts +126 -0
- package/dist/command/cli/first-verdict.js +267 -0
- package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
- package/dist/command/cli/launch/sdk-version-match.js +3 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
- package/dist/command/cli/mcp-command.js +13 -4
- package/dist/command/cli/ports/port-holder.d.ts +11 -0
- package/dist/command/cli/ports/port-holder.js +36 -4
- package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
- package/dist/command/cli/setup-mcp-cli.js +98 -23
- package/dist/command/cli/status/status-lines.d.ts +2 -0
- package/dist/command/cli/status/status-lines.js +72 -17
- package/dist/command/cli/status-fields.d.ts +5 -1
- package/dist/command/cli/status-fields.js +24 -6
- package/dist/command/cli/try-command.d.ts +7 -0
- package/dist/command/cli/try-command.js +11 -3
- package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
- package/dist/command/cli/two-builds/compare-command.js +314 -0
- package/dist/command/cli/verify-against.d.ts +61 -0
- package/dist/command/cli/verify-against.js +187 -0
- package/dist/command/cli/verify-no-session.d.ts +2 -0
- package/dist/command/cli/verify-no-session.js +13 -0
- package/dist/command/cli/verify-results.d.ts +35 -2
- package/dist/command/cli/verify-results.js +76 -10
- package/dist/command/cli.js +112 -70
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
- package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
- package/dist/command/daemon/daemon-argv.d.ts +9 -0
- package/dist/command/daemon/daemon-argv.js +11 -0
- package/dist/command/daemon/daemon-resolve.d.ts +8 -1
- package/dist/command/daemon/daemon-resolve.js +27 -1
- package/dist/command/daemon/daemon.d.ts +22 -4
- package/dist/command/daemon/daemon.js +52 -10
- package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
- package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
- package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
- package/dist/command/daemon/lifetime/death-cause.js +149 -0
- package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
- package/dist/command/daemon/lifetime/heartbeat.js +11 -15
- package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
- package/dist/command/daemon/lifetime/startup-failure.js +62 -0
- package/dist/command/daemon/port-state.d.ts +14 -0
- package/dist/command/daemon/port-state.js +31 -0
- package/dist/command/setup/agent-configs.d.ts +2 -0
- package/dist/command/setup/agent-configs.js +42 -15
- package/dist/command/setup/agent-next-steps.d.ts +77 -0
- package/dist/command/setup/agent-next-steps.js +284 -0
- package/dist/command/setup/agent-writer.js +11 -6
- package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
- package/dist/command/setup/bringup/dev-server-files.js +17 -0
- package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
- package/dist/command/setup/bringup/static-page-server.js +43 -9
- package/dist/command/setup/init/init-host.js +2 -0
- package/dist/command/setup/init/init-runtime.d.ts +6 -1
- package/dist/command/setup/init/init-runtime.js +63 -21
- package/dist/command/setup/mcp-launcher.d.ts +3 -0
- package/dist/command/setup/mcp-launcher.js +11 -0
- package/dist/command/setup/node-effects.d.ts +3 -2
- package/dist/command/setup/node-effects.js +9 -3
- package/dist/command/setup/remaining-steps.d.ts +8 -0
- package/dist/command/setup/remaining-steps.js +10 -0
- package/dist/command/setup/run-setup.d.ts +2 -0
- package/dist/command/setup/run-setup.js +9 -1
- package/dist/command/setup/setup-command.js +36 -15
- package/dist/command/setup/setup-mcp.d.ts +8 -0
- package/dist/command/setup/setup-mcp.js +47 -20
- package/dist/command/setup/start-dev-server.d.ts +8 -0
- package/dist/command/setup/start-dev-server.js +43 -0
- package/dist/command/update/updater.js +6 -0
- package/dist/command/version/identity/server-version.d.ts +2 -0
- package/dist/command/version/identity/server-version.js +2 -0
- package/dist/command/version/version-skew.d.ts +7 -0
- package/dist/command/version/version-skew.js +14 -3
- package/dist/features/compare/action-plan.d.ts +260 -0
- package/dist/features/compare/action-plan.js +543 -0
- package/dist/features/compare/behaviour-compare.d.ts +24 -0
- package/dist/features/compare/behaviour-compare.js +85 -0
- package/dist/features/compare/behaviour-diff.d.ts +97 -0
- package/dist/features/compare/behaviour-diff.js +202 -0
- package/dist/features/compare/behaviour-report.d.ts +40 -0
- package/dist/features/compare/behaviour-report.js +128 -0
- package/dist/features/compare/build-readings.d.ts +106 -0
- package/dist/features/compare/build-readings.js +250 -0
- package/dist/features/compare/compare-diff.d.ts +62 -0
- package/dist/features/compare/compare-diff.js +177 -0
- package/dist/features/compare/compare-report-html.d.ts +6 -0
- package/dist/features/compare/compare-report-html.js +102 -0
- package/dist/features/compare/compare-report.d.ts +15 -0
- package/dist/features/compare/compare-report.js +73 -0
- package/dist/features/compare/compare-run.d.ts +60 -0
- package/dist/features/compare/compare-run.js +123 -0
- package/dist/features/compare/outside/effect-lines.d.ts +26 -0
- package/dist/features/compare/outside/effect-lines.js +103 -0
- package/dist/features/compare/outside/state-sources.d.ts +76 -0
- package/dist/features/compare/outside/state-sources.js +164 -0
- package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
- package/dist/features/compare/outside/value-dictionary.js +227 -0
- package/dist/features/compare/visible-content.d.ts +20 -0
- package/dist/features/compare/visible-content.js +125 -0
- package/dist/features/crawl/crawl-empty.js +2 -1
- package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
- package/dist/features/crawl/crawl-exhaustive.js +138 -0
- package/dist/features/crawl/crawl-tools.d.ts +9 -2
- package/dist/features/crawl/crawl-tools.js +28 -12
- package/dist/features/crawl/crawl.d.ts +8 -9
- package/dist/features/crawl/crawl.js +61 -65
- package/dist/features/exhaust/app-coverage.d.ts +5 -1
- package/dist/features/exhaust/app-coverage.js +3 -1
- package/dist/features/exhaust/code-coverage.js +8 -4
- package/dist/features/exhaust/edge-cases.d.ts +133 -0
- package/dist/features/exhaust/edge-cases.js +310 -0
- package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
- package/dist/features/exhaust/edges/edge-drive.js +364 -0
- package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
- package/dist/features/exhaust/edges/edge-run.js +169 -0
- package/dist/features/exhaust/explorer.d.ts +87 -14
- package/dist/features/exhaust/explorer.js +159 -25
- package/dist/features/exhaust/failure-branches.d.ts +5 -0
- package/dist/features/exhaust/failure-branches.js +29 -11
- package/dist/features/exhaust/invariants.d.ts +112 -0
- package/dist/features/exhaust/invariants.js +188 -0
- package/dist/features/exhaust/ledger.d.ts +68 -0
- package/dist/features/exhaust/ledger.js +121 -13
- package/dist/features/exhaust/session-fold.d.ts +10 -0
- package/dist/features/exhaust/session-fold.js +46 -3
- package/dist/features/exhaust/session-port.d.ts +3 -1
- package/dist/features/exhaust/session-port.js +97 -28
- package/dist/features/grade/grade-answer.d.ts +99 -0
- package/dist/features/grade/grade-answer.js +112 -0
- package/dist/features/grade/grade-noise.d.ts +27 -0
- package/dist/features/grade/grade-noise.js +55 -0
- package/dist/features/grade/grade-renumber.d.ts +12 -0
- package/dist/features/grade/grade-renumber.js +86 -0
- package/dist/features/grade/grade-state.d.ts +50 -0
- package/dist/features/grade/grade-state.js +93 -0
- package/dist/features/grade/grade-suggest.d.ts +33 -0
- package/dist/features/grade/grade-suggest.js +98 -0
- package/dist/features/license/license.d.ts +11 -71
- package/dist/features/license/license.js +43 -136
- package/dist/features/visual/desktop-capture.d.ts +33 -0
- package/dist/features/visual/desktop-capture.js +94 -0
- package/dist/features/visual/step-shots.js +139 -35
- package/dist/features/visual/visual-tools.d.ts +0 -18
- package/dist/features/visual/visual-tools.js +67 -99
- package/dist/import-graph.js +5 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +19 -7
- package/dist/judgement/capsule/causal-summary.js +9 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +16 -2
- package/dist/judgement/runs/run-ledger.d.ts +7 -5
- package/dist/judgement/runs/run-ledger.js +98 -69
- package/dist/judgement/runs/runner-port.d.ts +2 -0
- package/dist/judgement/runs/runner-port.js +23 -5
- package/dist/judgement/runs/verification-sync.d.ts +6 -3
- package/dist/judgement/runs/verification-sync.js +21 -10
- package/dist/language/flows/affected-tools.d.ts +1 -1
- package/dist/language/flows/affected-tools.js +49 -30
- package/dist/language/flows/change/git-changed.d.ts +2 -1
- package/dist/language/flows/change/git-changed.js +19 -2
- package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
- package/dist/language/flows/fields/flow-secret-field.js +77 -21
- package/dist/language/flows/flow-anchor.d.ts +60 -2
- package/dist/language/flows/flow-anchor.js +125 -20
- package/dist/language/flows/flow-classify.d.ts +20 -0
- package/dist/language/flows/flow-classify.js +90 -12
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +25 -1
- package/dist/language/flows/flow-journey.d.ts +38 -4
- package/dist/language/flows/flow-journey.js +138 -8
- package/dist/language/flows/flow-replay-run.d.ts +0 -16
- package/dist/language/flows/flow-replay-run.js +76 -14
- package/dist/language/flows/flow-replay-types.d.ts +4 -2
- package/dist/language/flows/flow-replay.d.ts +19 -1
- package/dist/language/flows/flow-replay.js +169 -52
- package/dist/language/flows/flow-replayable.js +7 -1
- package/dist/language/flows/flow-start-path.d.ts +34 -5
- package/dist/language/flows/flow-start-path.js +202 -15
- package/dist/language/flows/flow-step-runners.d.ts +14 -13
- package/dist/language/flows/flow-step-runners.js +184 -109
- package/dist/language/flows/flow-store-for-session.d.ts +26 -25
- package/dist/language/flows/flow-store-for-session.js +51 -5
- package/dist/language/flows/flow-tools.js +46 -33
- package/dist/language/flows/flows.js +176 -76
- package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
- package/dist/language/flows/recording/tape/recordings.js +112 -49
- package/dist/language/flows/replay.d.ts +36 -12
- package/dist/language/flows/replay.js +193 -50
- package/dist/language/flows/steps/page-steps.d.ts +68 -0
- package/dist/language/flows/steps/page-steps.js +198 -0
- package/dist/language/flows/suite/sequential-run.d.ts +68 -0
- package/dist/language/flows/suite/sequential-run.js +96 -0
- package/dist/language/flows/verify-change-tools.js +26 -15
- package/dist/launch-chromium.d.ts +1 -0
- package/dist/launch-chromium.js +49 -12
- package/dist/memory/cloud/background-sync.d.ts +13 -0
- package/dist/memory/cloud/background-sync.js +24 -3
- package/dist/memory/cloud/sync-daemon.js +26 -27
- package/dist/memory/cloud/sync-screenshots.js +6 -1
- package/dist/memory/journal/attach-journal.js +2 -1
- package/dist/memory/journal/drive-flow.d.ts +7 -0
- package/dist/memory/journal/drive-flow.js +44 -13
- package/dist/memory/journal/journal-recorder.d.ts +4 -2
- package/dist/memory/journal/session-journal.d.ts +7 -0
- package/dist/memory/journal/session-journal.js +58 -10
- package/dist/memory/project/root-claims.d.ts +9 -0
- package/dist/memory/project/root-claims.js +9 -4
- package/dist/portal/bridge/bridge.d.ts +13 -4
- package/dist/portal/bridge/bridge.js +10 -9
- package/dist/portal/input/download-capture.d.ts +7 -0
- package/dist/portal/input/download-capture.js +92 -0
- package/dist/portal/input/network-detail.d.ts +6 -0
- package/dist/portal/input/network-detail.js +12 -1
- package/dist/portal/input/network-mock-tools.js +49 -13
- package/dist/portal/input/network-mock.d.ts +8 -0
- package/dist/portal/input/network-mock.js +30 -10
- package/dist/portal/input/page-capture.d.ts +24 -0
- package/dist/portal/input/page-capture.js +33 -0
- package/dist/portal/input/real-input.d.ts +35 -18
- package/dist/portal/input/real-input.js +99 -33
- package/dist/portal/input/viewport-tools.d.ts +8 -0
- package/dist/portal/input/viewport-tools.js +81 -21
- package/dist/portal/pool/browser-pool.d.ts +16 -30
- package/dist/portal/pool/browser-pool.js +64 -52
- package/dist/portal/pool/context-permissions.d.ts +34 -17
- package/dist/portal/pool/context-permissions.js +94 -1
- package/dist/portal/pool/csp-widen.d.ts +62 -0
- package/dist/portal/pool/csp-widen.js +132 -0
- package/dist/portal/pool/determinism.d.ts +52 -0
- package/dist/portal/pool/determinism.js +259 -0
- package/dist/portal/pool/lease-emulation.d.ts +34 -1
- package/dist/portal/pool/lease-emulation.js +18 -0
- package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
- package/dist/portal/pool/lease-marker-stamp.js +15 -1
- package/dist/portal/pool/lease-page-error.d.ts +56 -0
- package/dist/portal/pool/lease-page-error.js +160 -0
- package/dist/portal/pool/page-error-journal.d.ts +63 -0
- package/dist/portal/pool/page-error-journal.js +208 -0
- package/dist/portal/pool/playwright-launcher.js +71 -15
- package/dist/portal/pool/pool-contract.d.ts +49 -1
- package/dist/portal/pool/pool-lease.d.ts +70 -2
- package/dist/portal/pool/pool-lease.js +23 -0
- package/dist/portal/pool/zero-install.d.ts +8 -0
- package/dist/portal/pool/zero-install.js +91 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
- package/dist/portal/session/dev-server/dev-command.js +110 -16
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
- package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
- package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
- package/dist/portal/session/dev-server/project-ports.js +62 -0
- package/dist/portal/session/facts/departed-network.d.ts +66 -0
- package/dist/portal/session/facts/departed-network.js +142 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
- package/dist/portal/session/no-session-diagnosis.js +76 -27
- package/dist/portal/session/no-session-next-action.d.ts +14 -0
- package/dist/portal/session/no-session-next-action.js +56 -7
- package/dist/portal/session/no-session-watch.d.ts +6 -0
- package/dist/portal/session/no-session-watch.js +41 -4
- package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
- package/dist/portal/session/page-commands/undeclared-command.js +16 -7
- package/dist/portal/session/recorded-gaps.d.ts +1 -1
- package/dist/portal/session/recorded-gaps.js +5 -2
- package/dist/portal/session/serving-directory.d.ts +10 -0
- package/dist/portal/session/serving-directory.js +39 -8
- package/dist/portal/session/session-manager.d.ts +10 -0
- package/dist/portal/session/session-manager.js +211 -81
- package/dist/portal/session/session.js +4 -3
- package/dist/portal/session/zero-install-first-call.d.ts +20 -1
- package/dist/portal/session/zero-install-first-call.js +34 -2
- package/dist/start-wiring.d.ts +30 -2
- package/dist/start-wiring.js +74 -9
- package/dist/surface/mcp/mcp-proxy.js +10 -2
- package/dist/surface/mcp/mcp.js +8 -5
- package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
- package/dist/surface/mcp/peer/agents-connected.js +58 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
- package/dist/surface/mcp/shared-params.d.ts +2 -0
- package/dist/surface/mcp/shared-params.js +31 -0
- package/dist/surface/tools/act/act-observation.d.ts +40 -2
- package/dist/surface/tools/act/act-observation.js +118 -12
- package/dist/surface/tools/act/act-preflight.js +38 -18
- package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
- package/dist/surface/tools/act/act-sequence-retry.js +61 -5
- package/dist/surface/tools/act/act-target.js +3 -1
- package/dist/surface/tools/act/after-match.d.ts +10 -0
- package/dist/surface/tools/act/after-match.js +40 -0
- package/dist/surface/tools/act/already-true.js +1 -1
- package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
- package/dist/surface/tools/act/navigation/reload-result.js +25 -2
- package/dist/surface/tools/act/resolve-target.d.ts +17 -1
- package/dist/surface/tools/act/resolve-target.js +59 -9
- package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
- package/dist/surface/tools/act/sequence-grade.js +45 -27
- package/dist/surface/tools/act/settle-in-flight.js +27 -15
- package/dist/surface/tools/act-command.d.ts +23 -0
- package/dist/surface/tools/act-command.js +50 -0
- package/dist/surface/tools/act-sequence-tool.js +81 -50
- package/dist/surface/tools/act-tools.d.ts +1 -13
- package/dist/surface/tools/act-tools.js +83 -95
- package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
- package/dist/surface/tools/agent/build-staleness.js +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +18 -12
- package/dist/surface/tools/browser-tools.js +87 -13
- package/dist/surface/tools/compare-tools.d.ts +11 -0
- package/dist/surface/tools/compare-tools.js +94 -0
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.js +11 -3
- package/dist/surface/tools/empty-session-list.d.ts +2 -0
- package/dist/surface/tools/empty-session-list.js +4 -2
- package/dist/surface/tools/error-recovery.d.ts +12 -1
- package/dist/surface/tools/error-recovery.js +131 -40
- package/dist/surface/tools/explore-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +29 -21
- package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
- package/dist/surface/tools/lease-coverage-tools.js +128 -0
- package/dist/surface/tools/lease-hint.d.ts +42 -1
- package/dist/surface/tools/lease-hint.js +52 -3
- package/dist/surface/tools/lease-readiness.d.ts +47 -0
- package/dist/surface/tools/lease-readiness.js +126 -0
- package/dist/surface/tools/lease-seed.d.ts +16 -1
- package/dist/surface/tools/lease-seed.js +37 -4
- package/dist/surface/tools/lease-tools.d.ts +3 -41
- package/dist/surface/tools/lease-tools.js +104 -124
- package/dist/surface/tools/lease-url-identity.d.ts +46 -0
- package/dist/surface/tools/lease-url-identity.js +97 -0
- package/dist/surface/tools/merge-tools.js +99 -2
- package/dist/surface/tools/merged-name-redirect.js +5 -1
- package/dist/surface/tools/observe-tools.js +15 -9
- package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
- package/dist/surface/tools/read/console-pre-attach.js +23 -0
- package/dist/surface/tools/read-tools.js +33 -30
- package/dist/surface/tools/real-input-attempt.d.ts +0 -8
- package/dist/surface/tools/real-input-attempt.js +143 -28
- package/dist/surface/tools/repeat-hint.d.ts +40 -0
- package/dist/surface/tools/repeat-hint.js +171 -0
- package/dist/surface/tools/run-report.d.ts +2 -2
- package/dist/surface/tools/run-report.js +27 -15
- package/dist/surface/tools/session-ports.d.ts +14 -0
- package/dist/surface/tools/session-ports.js +53 -0
- package/dist/surface/tools/tool-kit.d.ts +18 -0
- package/dist/surface/tools/tool-kit.js +9 -3
- package/dist/surface/tools/tool-surface.js +0 -1
- package/dist/surface/tools/tools.js +54 -18
- package/dist/surface/tools/type-spelling.d.ts +13 -0
- package/dist/surface/tools/type-spelling.js +36 -0
- package/dist/telemetry/agent-client.d.ts +6 -0
- package/dist/telemetry/agent-client.js +10 -0
- package/dist/telemetry/argument-shape.js +17 -7
- package/dist/telemetry/cli-telemetry.d.ts +10 -1
- package/dist/telemetry/cli-telemetry.js +15 -0
- package/dist/telemetry/init-telemetry.d.ts +2 -1
- package/dist/telemetry/init-telemetry.js +47 -2
- package/dist/telemetry/install-trace.js +23 -4
- package/dist/telemetry/onboarding-funnel.d.ts +19 -2
- package/dist/telemetry/onboarding-funnel.js +25 -3
- package/dist/telemetry/proxy-telemetry.d.ts +2 -1
- package/dist/telemetry/proxy-telemetry.js +2 -2
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +67 -27
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +60 -28
- package/dist/telemetry/tool-refused.d.ts +6 -3
- package/dist/telemetry/tool-refused.js +15 -3
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/architecture.md +3 -6
- package/docs/capabilities.mdx +61 -1
- package/docs/cli/affected.mdx +9 -7
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/compare.mdx +94 -0
- package/docs/cli/doctor.mdx +41 -2
- package/docs/cli/grade.mdx +97 -0
- package/docs/cli/help.mdx +4 -2
- package/docs/cli/license.mdx +13 -12
- package/docs/cli/restart.mdx +7 -5
- package/docs/cli/rollback.mdx +5 -5
- package/docs/cli/setup.mdx +13 -2
- package/docs/cli/status.mdx +4 -0
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/update.mdx +2 -2
- package/docs/cli/verify.mdx +23 -1
- package/docs/cli.mdx +34 -24
- package/docs/deploy-checks.md +25 -0
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +17 -22
- package/docs/environment.mdx +1 -0
- package/docs/first-drive.md +1 -1
- package/docs/flows.md +11 -1
- package/docs/frameworks.mdx +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/install-manual.mdx +16 -1
- package/docs/license-activation.mdx +3 -3
- package/docs/packages/engine.mdx +1 -1
- package/docs/packages/server.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/test.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/platform-integration.md +1 -1
- package/docs/predicates.mdx +1 -1
- package/docs/quickstart.mdx +68 -49
- package/docs/skill-setup.md +1 -1
- package/docs/skill.md +14 -13
- package/docs/telemetry.md +6 -4
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/verify.mdx +4 -1
- package/docs/troubleshooting.mdx +1 -1
- package/docs/usage.md +17 -14
- package/package.json +18 -14
- package/LICENSE-ENTERPRISE +0 -37
- package/dist/features/ee/audit-log.d.ts +0 -14
- package/dist/features/ee/audit-log.js +0 -11
- package/dist/features/exhaust/exhaust-run.d.ts +0 -33
- package/dist/features/exhaust/exhaust-run.js +0 -82
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The safe repairs `reticle doctor --fix` can apply, in the order it applies them.
|
|
3
|
+
*
|
|
4
|
+
* Each one changes only Reticle's own state, and each reuses the code that already owns that state:
|
|
5
|
+
* `removePid` for the pid file, `stopWedgedDaemon` for a stuck daemon, the launch path's pinned
|
|
6
|
+
* install for Chromium, and `reticle setup mcp` for the agent registrations. The facts each one
|
|
7
|
+
* reads are injected, so the decision is tested without a home directory or a port.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { claudeAvailableProbe, claudeStaleEntryNote, claudeUserEntryIsFloating, codexAvailableProbe, probeCli, } from '@reticlehq/init';
|
|
12
|
+
import { mcpLauncher } from '../setup/mcp-launcher.js';
|
|
13
|
+
import { isAlive, readPid, removePid } from '../daemon/daemon.js';
|
|
14
|
+
import { PortPresence, probePresence } from '../daemon/binding/port-presence.js';
|
|
15
|
+
import { fetchStatus } from '../daemon/binding/daemon-status-probe.js';
|
|
16
|
+
import { identifyPortHolder } from '../daemon/port-state.js';
|
|
17
|
+
import { probeDaemon } from '../../surface/mcp/mcp-proxy.js';
|
|
18
|
+
import { probeChromiumWithFallback, probeLaunchableChromium } from '../../launch-chromium.js';
|
|
19
|
+
import { setupMcp } from '../setup/setup-mcp.js';
|
|
20
|
+
import { handleSetupMcp } from './setup-mcp-cli.js';
|
|
21
|
+
import { stopWedgedDaemon } from './cli-kill.js';
|
|
22
|
+
import { captureLookup } from './ports/port-holder.js';
|
|
23
|
+
import { bundledPlaywrightVersion, chromiumInstallCommand, } from './doctor/browser/chromium-hint.js';
|
|
24
|
+
import { DoctorRow, doctorRow } from './doctor/doctor-rows.js';
|
|
25
|
+
import { launcherLine, registeredEntries } from './doctor/doctor-launcher-line.js';
|
|
26
|
+
const SETUP_MCP_COMMAND = 'reticle setup mcp';
|
|
27
|
+
/** A pid file whose process is gone. `removePid` itself never removes the file of a live process. */
|
|
28
|
+
export function stalePidRepair(deps) {
|
|
29
|
+
const needed = () => {
|
|
30
|
+
const pid = deps.readPid();
|
|
31
|
+
return pid !== null && !deps.isAlive(pid);
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
row: DoctorRow.DAEMON,
|
|
35
|
+
what: 'pid file of a stopped daemon',
|
|
36
|
+
step: 'delete the pid file of the stopped daemon in ~/.reticle',
|
|
37
|
+
needed: () => Promise.resolve(needed()),
|
|
38
|
+
apply: () => {
|
|
39
|
+
deps.removePid();
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A Reticle daemon that holds the port and does not answer. Only a holder whose command line is a
|
|
46
|
+
* Reticle daemon's qualifies; `stopWedgedDaemon` checks that again, after a grace period, before it
|
|
47
|
+
* sends a signal. A stranger and the agent's own `reticle mcp` proxy are never stopped.
|
|
48
|
+
*/
|
|
49
|
+
export function wedgedDaemonRepair(port, deps) {
|
|
50
|
+
return {
|
|
51
|
+
row: DoctorRow.DAEMON,
|
|
52
|
+
what: 'a Reticle daemon that does not answer',
|
|
53
|
+
step: `reticle serve --restart --port ${String(port)}`,
|
|
54
|
+
needed: async () => PortPresence.FOREIGN === (await deps.presence()) && true === deps.holder()?.reticleDaemon,
|
|
55
|
+
apply: async () => {
|
|
56
|
+
await deps.stop();
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* No browser that a launch can use. Only when Playwright's own build is missing or at the wrong
|
|
62
|
+
* revision and no installed Chrome or Edge stands in. A RETICLE_CHROMIUM_PATH the user set is never
|
|
63
|
+
* replaced, and an absent playwright package is not a download this can fix.
|
|
64
|
+
*/
|
|
65
|
+
export function chromiumRepair(deps) {
|
|
66
|
+
return {
|
|
67
|
+
row: DoctorRow.CHROMIUM,
|
|
68
|
+
what: "Playwright's own Chromium",
|
|
69
|
+
step: chromiumInstallCommand(deps.playwrightVersion),
|
|
70
|
+
needed: async () => {
|
|
71
|
+
const probe = await deps.probe();
|
|
72
|
+
return (!probe.exists &&
|
|
73
|
+
probe.fallback === undefined &&
|
|
74
|
+
true !== probe.configured &&
|
|
75
|
+
probe.executablePath !== undefined);
|
|
76
|
+
},
|
|
77
|
+
apply: async () => {
|
|
78
|
+
await deps.install();
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Agents found on this machine that do not have Reticle's MCP server registered. */
|
|
83
|
+
export function mcpRepair(deps) {
|
|
84
|
+
return {
|
|
85
|
+
row: DoctorRow.MCP,
|
|
86
|
+
what: 'agent registrations',
|
|
87
|
+
step: SETUP_MCP_COMMAND,
|
|
88
|
+
needed: () => Promise.resolve(deps.gaps().length > 0),
|
|
89
|
+
apply: () => {
|
|
90
|
+
deps.register();
|
|
91
|
+
return Promise.resolve();
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** How the launcher row starts its command, as `doctor-launcher-line.ts` writes it. */
|
|
96
|
+
const LAUNCHER_RUN = 'Run: ';
|
|
97
|
+
/**
|
|
98
|
+
* An agent config starts Reticle from an installed copy that no longer exists. `setup mcp`
|
|
99
|
+
* replaces Reticle's own entry in the JSON clients. It keeps an existing Claude Code entry, so for
|
|
100
|
+
* Claude Code the row still fails after the repair, and the step is the row's remove-then-add.
|
|
101
|
+
*/
|
|
102
|
+
export function launcherRepair(deps) {
|
|
103
|
+
let step = SETUP_MCP_COMMAND;
|
|
104
|
+
return {
|
|
105
|
+
row: DoctorRow.LAUNCHER,
|
|
106
|
+
what: 'a registered launcher that does not exist or has no version',
|
|
107
|
+
// The step is read after `needed`, which reads the row again.
|
|
108
|
+
get step() {
|
|
109
|
+
return step;
|
|
110
|
+
},
|
|
111
|
+
needed: () => {
|
|
112
|
+
const line = deps.line();
|
|
113
|
+
if (line === undefined) {
|
|
114
|
+
return Promise.resolve(false);
|
|
115
|
+
}
|
|
116
|
+
const at = line.indexOf(LAUNCHER_RUN);
|
|
117
|
+
step = at < 0 ? SETUP_MCP_COMMAND : line.slice(at + LAUNCHER_RUN.length);
|
|
118
|
+
return Promise.resolve(true);
|
|
119
|
+
},
|
|
120
|
+
apply: () => {
|
|
121
|
+
deps.register();
|
|
122
|
+
return Promise.resolve();
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** The launcher row for the agent configs of this machine and folder. */
|
|
127
|
+
export function readLauncherLine(cwd) {
|
|
128
|
+
const io = {
|
|
129
|
+
exists: existsSync,
|
|
130
|
+
homeDir: homedir,
|
|
131
|
+
readFile: (p) => {
|
|
132
|
+
try {
|
|
133
|
+
return readFileSync(p, 'utf8');
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
return launcherLine(registeredEntries(io, cwd), existsSync, claudeUserEntryIsFloating(io) ? claudeStaleEntryNote(mcpLauncher()) : undefined);
|
|
141
|
+
}
|
|
142
|
+
/** The `mcp` doctor row. */
|
|
143
|
+
export function mcpRegistrationRow(registration) {
|
|
144
|
+
const { missing, present } = registration;
|
|
145
|
+
if (missing.length > 0) {
|
|
146
|
+
const also = 0 === present.length ? '' : ` (registered with ${present.join(', ')})`;
|
|
147
|
+
return doctorRow(DoctorRow.MCP, `✗ not registered with ${missing.join(', ')}; run \`${SETUP_MCP_COMMAND}\`${also}`);
|
|
148
|
+
}
|
|
149
|
+
if (0 === present.length) {
|
|
150
|
+
return doctorRow(DoctorRow.MCP, `no agent that this check knows was found; \`${SETUP_MCP_COMMAND}\` names the agents it knows`);
|
|
151
|
+
}
|
|
152
|
+
return doctorRow(DoctorRow.MCP, `✓ registered with ${present.join(', ')}`);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Ask `setupMcp` what it would do, and do none of it. Config writes and registration commands are
|
|
156
|
+
* recorded, not run; only the two `--version` probes run, because they say whether an agent is here.
|
|
157
|
+
* No telemetry: this is a check, not a registration.
|
|
158
|
+
*/
|
|
159
|
+
export function readMcpRegistration() {
|
|
160
|
+
const probes = [claudeAvailableProbe(), codexAvailableProbe()];
|
|
161
|
+
const isProbe = (command, args) => probes.some((p) => p.command === command && p.args.join(' ') === args.join(' '));
|
|
162
|
+
const result = setupMcp({
|
|
163
|
+
exists: (p) => existsSync(p),
|
|
164
|
+
readFile: (p) => {
|
|
165
|
+
try {
|
|
166
|
+
return readFileSync(p, 'utf8');
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
writeFile: () => { },
|
|
173
|
+
homeDir: () => homedir(),
|
|
174
|
+
print: () => { },
|
|
175
|
+
runCli: (command, args) => (isProbe(command, args) ? probeCli(command, args) : true),
|
|
176
|
+
reportStep: () => { },
|
|
177
|
+
});
|
|
178
|
+
return { missing: result.registered, present: result.alreadyThere };
|
|
179
|
+
}
|
|
180
|
+
/** The repairs for this machine and port, in the order `--fix` applies them. */
|
|
181
|
+
export function machineRepairs(port, reportStep) {
|
|
182
|
+
return [
|
|
183
|
+
stalePidRepair({
|
|
184
|
+
readPid: () => readPid(port),
|
|
185
|
+
isAlive,
|
|
186
|
+
removePid: () => {
|
|
187
|
+
removePid(port);
|
|
188
|
+
},
|
|
189
|
+
}),
|
|
190
|
+
wedgedDaemonRepair(port, {
|
|
191
|
+
presence: () => probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus }),
|
|
192
|
+
holder: () => identifyPortHolder(port, captureLookup),
|
|
193
|
+
stop: () => stopWedgedDaemon(port),
|
|
194
|
+
}),
|
|
195
|
+
chromiumRepair({
|
|
196
|
+
probe: () => probeChromiumWithFallback(),
|
|
197
|
+
install: () => probeLaunchableChromium(),
|
|
198
|
+
playwrightVersion: bundledPlaywrightVersion(),
|
|
199
|
+
}),
|
|
200
|
+
// Before `mcp`: its `setup mcp` also replaces a stale launcher, and this one says so.
|
|
201
|
+
launcherRepair({
|
|
202
|
+
line: () => readLauncherLine(process.cwd()),
|
|
203
|
+
register: () => {
|
|
204
|
+
handleSetupMcp(reportStep, false);
|
|
205
|
+
},
|
|
206
|
+
}),
|
|
207
|
+
mcpRepair({
|
|
208
|
+
gaps: () => readMcpRegistration().missing,
|
|
209
|
+
register: () => {
|
|
210
|
+
handleSetupMcp(reportStep, false);
|
|
211
|
+
},
|
|
212
|
+
}),
|
|
213
|
+
];
|
|
214
|
+
}
|
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { start } from '../../../index.js';
|
|
10
10
|
import { log } from '../../../log.js';
|
|
11
|
-
import { probePresence
|
|
11
|
+
import { probePresence } from '../../daemon/binding/port-presence.js';
|
|
12
12
|
import { probeDaemon } from '../../../surface/mcp/mcp-proxy.js';
|
|
13
|
-
import { readPid } from '../../daemon/daemon.js';
|
|
14
13
|
import { fetchStatus } from '../../daemon/binding/daemon-status-probe.js';
|
|
15
|
-
import {
|
|
14
|
+
import { describePortState } from '../../daemon/port-state.js';
|
|
16
15
|
import { DriveMode, NO_ATTACHED_APP, attachedApp, decideDriveMode, describeAttached, driveForeignHolder, driveHeadlessOnAttach, driveRaceLost, requestDriveSession, } from './drive-attach.js';
|
|
17
16
|
/** `projectId` is the one in this directory's `.reticle.json`, read by the caller. */
|
|
18
17
|
export function handleDrive(parsed, projectId) {
|
|
@@ -34,10 +33,7 @@ async function driveWithHonestConflict(parsed, projectId) {
|
|
|
34
33
|
const presence = await probePresence(parsed.port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
35
34
|
const mode = decideDriveMode(presence);
|
|
36
35
|
if (DriveMode.REFUSE === mode) {
|
|
37
|
-
const reason = driveForeignHolder(
|
|
38
|
-
ourPid: readPid(parsed.port),
|
|
39
|
-
holderPid: findPortHolder(parsed.port, captureLookup)?.pid,
|
|
40
|
-
}));
|
|
36
|
+
const reason = driveForeignHolder(describePortState(presence, parsed.port));
|
|
41
37
|
log('reticle_drive_port_conflict', { port: parsed.port, presence, reason });
|
|
42
38
|
process.stderr.write(`${reason}\n`);
|
|
43
39
|
process.exit(1);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle open` and every other flow that needs a daemon on `port`.
|
|
3
|
+
*
|
|
4
|
+
* The retry: a FOREIGN reading seconds after `reticle restart` is usually OUR OWN daemon still
|
|
5
|
+
* answering its first `/status` — the 1 s `/status` window and the 500 ms TCP window do not
|
|
6
|
+
* survive a cold Windows start, and the refusal then names a holder it could not identify, while
|
|
7
|
+
* `reticle status` run moments later shows the daemon fine. The pid file names what `restart`
|
|
8
|
+
* started; while that process is alive the port is probed once more before refusing. A stranger's
|
|
9
|
+
* pid is never in the file, so the refusal keeps its teeth for the stranger case it was written
|
|
10
|
+
* for.
|
|
11
|
+
*
|
|
12
|
+
* Probes, the pid file and the spawn are injected so the rule is testable without a socket — the
|
|
13
|
+
* same shape `probePresence`'s own callers use.
|
|
14
|
+
*/
|
|
15
|
+
import { PortPresence } from '../daemon/binding/port-presence.js';
|
|
16
|
+
export interface EnsureDaemonDeps {
|
|
17
|
+
/** The classified presence probe. Defaults to the shared TCP + `/status` pair. */
|
|
18
|
+
probe?: (port: number) => Promise<PortPresence>;
|
|
19
|
+
readPid?: (port: number) => number | null;
|
|
20
|
+
isAlive?: (pid: number) => boolean;
|
|
21
|
+
/** How long to wait before the second probe. */
|
|
22
|
+
retryDelayMs?: number;
|
|
23
|
+
spawnDaemon?: (port: number) => Promise<void>;
|
|
24
|
+
onUsable?: (port: number) => Promise<void> | void;
|
|
25
|
+
}
|
|
26
|
+
export declare function ensureDaemon(port: number, deps?: EnsureDaemonDeps): Promise<void>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle open` and every other flow that needs a daemon on `port`.
|
|
3
|
+
*
|
|
4
|
+
* The retry: a FOREIGN reading seconds after `reticle restart` is usually OUR OWN daemon still
|
|
5
|
+
* answering its first `/status` — the 1 s `/status` window and the 500 ms TCP window do not
|
|
6
|
+
* survive a cold Windows start, and the refusal then names a holder it could not identify, while
|
|
7
|
+
* `reticle status` run moments later shows the daemon fine. The pid file names what `restart`
|
|
8
|
+
* started; while that process is alive the port is probed once more before refusing. A stranger's
|
|
9
|
+
* pid is never in the file, so the refusal keeps its teeth for the stranger case it was written
|
|
10
|
+
* for.
|
|
11
|
+
*
|
|
12
|
+
* Probes, the pid file and the spawn are injected so the rule is testable without a socket — the
|
|
13
|
+
* same shape `probePresence`'s own callers use.
|
|
14
|
+
*/
|
|
15
|
+
import { PortPresence, probePresence, presenceIsUsable, } from '../daemon/binding/port-presence.js';
|
|
16
|
+
import { describePortState } from '../daemon/port-state.js';
|
|
17
|
+
import { probeDaemon, waitForDaemon } from '../../surface/mcp/mcp-proxy.js';
|
|
18
|
+
import { fetchStatus } from '../daemon/binding/daemon-status-probe.js';
|
|
19
|
+
import { isAlive, readPid, spawnDaemon } from '../daemon/daemon.js';
|
|
20
|
+
import { warnOnDaemonSkew } from './launch/cli-launch.js';
|
|
21
|
+
import { DAEMON_INNER_COMMAND, PORT_FLAG } from './cli-parse.js';
|
|
22
|
+
/** The probe pair every daemon-touching command shares. */
|
|
23
|
+
const REAL_PROBES = {
|
|
24
|
+
tcpOpen: (port) => probeDaemon(port),
|
|
25
|
+
status: (port) => fetchStatus(port),
|
|
26
|
+
};
|
|
27
|
+
export async function ensureDaemon(port, deps = {}) {
|
|
28
|
+
const probe = deps.probe ?? (() => probePresence(port, REAL_PROBES));
|
|
29
|
+
const readRecordedPid = deps.readPid ?? readPid;
|
|
30
|
+
const pidIsAlive = deps.isAlive ?? isAlive;
|
|
31
|
+
const spawn = deps.spawnDaemon ?? spawnRealDaemon;
|
|
32
|
+
const onUsable = deps.onUsable ?? warnOnDaemonSkew;
|
|
33
|
+
const retryDelayMs = deps.retryDelayMs ?? 1_500;
|
|
34
|
+
let presence = await probe(port);
|
|
35
|
+
if (PortPresence.FOREIGN === presence) {
|
|
36
|
+
const recorded = readRecordedPid(port);
|
|
37
|
+
if (recorded !== null && pidIsAlive(recorded)) {
|
|
38
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
39
|
+
presence = await probe(port);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (presenceIsUsable(presence)) {
|
|
43
|
+
return void (await onUsable(port));
|
|
44
|
+
}
|
|
45
|
+
if (PortPresence.FREE !== presence) {
|
|
46
|
+
throw new Error(describePortState(presence, port));
|
|
47
|
+
}
|
|
48
|
+
await spawn(port);
|
|
49
|
+
}
|
|
50
|
+
async function spawnRealDaemon(port) {
|
|
51
|
+
const scriptPath = process.argv[1];
|
|
52
|
+
if (scriptPath === undefined) {
|
|
53
|
+
throw new Error('cannot locate the reticle daemon script');
|
|
54
|
+
}
|
|
55
|
+
spawnDaemon(process.execPath, scriptPath, [DAEMON_INNER_COMMAND, PORT_FLAG, String(port)], port);
|
|
56
|
+
await waitForDaemon(port);
|
|
57
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type StateIo } from '../../../features/compare/outside/state-sources.js';
|
|
2
|
+
import { type GradeArgs } from '../cli-parse-grade.js';
|
|
3
|
+
export declare const GradeExit: {
|
|
4
|
+
readonly PASS: 0;
|
|
5
|
+
readonly FAIL: 1;
|
|
6
|
+
readonly UNVERIFIED: 2;
|
|
7
|
+
};
|
|
8
|
+
/** The most difference lines printed as text. `--json` gives every line. */
|
|
9
|
+
export declare const MAX_TEXT_LINES = 10;
|
|
10
|
+
type Ok = Extract<GradeArgs, {
|
|
11
|
+
kind: 'ok';
|
|
12
|
+
}>;
|
|
13
|
+
export interface GradeOut {
|
|
14
|
+
line: (text: string) => void;
|
|
15
|
+
exit: (code: number) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface GradeIo {
|
|
18
|
+
state: StateIo;
|
|
19
|
+
readText: (path: string) => Promise<string>;
|
|
20
|
+
writeText: (path: string, text: string) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare function handleGrade(args: Ok, out: GradeOut, io?: GradeIo): Promise<void>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle grade`: grade a finished run of an app from snapshots of its stored data.
|
|
3
|
+
*
|
|
4
|
+
* - `snapshot` reads the declared state sources (and the page the run ended on) into a file.
|
|
5
|
+
* - `judge` compares a candidate snapshot with two reference snapshots, and checks the final answer
|
|
6
|
+
* against a source read at grade time.
|
|
7
|
+
* - `tables` writes a state declaration with one source for each table of a database.
|
|
8
|
+
* - `suggest` removes the sources that change between two runs of the same correct steps.
|
|
9
|
+
*
|
|
10
|
+
* It needs no daemon and no browser. The rules are in `@/features/grade/`.
|
|
11
|
+
*/
|
|
12
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { readFile } from 'node:fs/promises';
|
|
14
|
+
import { dirname } from 'node:path';
|
|
15
|
+
import { MSG_READ_FAILED, readStateSources, sourceRows, StateDeclarationSchema, } from '../../../features/compare/outside/state-sources.js';
|
|
16
|
+
import { checkAnswer, ExpectSchema } from '../../../features/grade/grade-answer.js';
|
|
17
|
+
import { GradeVerdict, gradeState, SnapshotSchema, urlLine, } from '../../../features/grade/grade-state.js';
|
|
18
|
+
import { quietSources, tableSources } from '../../../features/grade/grade-suggest.js';
|
|
19
|
+
import { GradeStep } from '../cli-parse-grade.js';
|
|
20
|
+
import { hostStateIo } from '../two-builds/compare-command.js';
|
|
21
|
+
export const GradeExit = { PASS: 0, FAIL: 1, UNVERIFIED: 2 };
|
|
22
|
+
const EXIT = {
|
|
23
|
+
[GradeVerdict.PASS]: GradeExit.PASS,
|
|
24
|
+
[GradeVerdict.FAIL]: GradeExit.FAIL,
|
|
25
|
+
[GradeVerdict.UNVERIFIED]: GradeExit.UNVERIFIED,
|
|
26
|
+
};
|
|
27
|
+
/** The most difference lines printed as text. `--json` gives every line. */
|
|
28
|
+
export const MAX_TEXT_LINES = 10;
|
|
29
|
+
const UTF8 = 'utf8';
|
|
30
|
+
const COLUMN = '\t';
|
|
31
|
+
const LINE_BREAK = /\r?\n/;
|
|
32
|
+
const SKIP_SEPARATOR = ',';
|
|
33
|
+
const reasonOf = (error) => error instanceof Error ? error.message : String(error);
|
|
34
|
+
function writeFile(path, text) {
|
|
35
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
36
|
+
writeFileSync(path, text);
|
|
37
|
+
}
|
|
38
|
+
const defaultIo = {
|
|
39
|
+
state: hostStateIo,
|
|
40
|
+
readText: (path) => readFile(path, UTF8),
|
|
41
|
+
writeText: writeFile,
|
|
42
|
+
};
|
|
43
|
+
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
44
|
+
async function readSnapshot(io, path) {
|
|
45
|
+
const parsed = SnapshotSchema.safeParse(JSON.parse(await io.readText(path)));
|
|
46
|
+
if (!parsed.success) {
|
|
47
|
+
throw new Error(`${path} is not a grade snapshot: ${parsed.error.message}`);
|
|
48
|
+
}
|
|
49
|
+
return parsed.data;
|
|
50
|
+
}
|
|
51
|
+
/** The sources of a state declaration file, or why they could not be read. */
|
|
52
|
+
async function declarationOf(io, path) {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = StateDeclarationSchema.safeParse(JSON.parse(await io.readText(path)));
|
|
55
|
+
return parsed.success ? parsed.data.sources : { error: parsed.error.message };
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return { error: reasonOf(error) };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** At most `MAX_TEXT_LINES` lines, then a line that says how many were left out. */
|
|
62
|
+
function capped(sign, lines) {
|
|
63
|
+
const shown = lines.slice(0, MAX_TEXT_LINES).map((line) => ` ${sign} ${line}`);
|
|
64
|
+
const rest = lines.length - shown.length;
|
|
65
|
+
return rest > 0 ? [...shown, ` ... and ${String(rest)} more (--json lists every line)`] : shown;
|
|
66
|
+
}
|
|
67
|
+
async function snapshot(args, io, out) {
|
|
68
|
+
const sources = await declarationOf(io, args.state ?? '');
|
|
69
|
+
if ('error' in sources) {
|
|
70
|
+
out.line(`Could not read the state sources in ${args.state ?? ''}: ${sources.error}`);
|
|
71
|
+
out.exit(GradeExit.UNVERIFIED);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// No row cap: the grade diff is linear, and a cut row would not name its record.
|
|
75
|
+
const lines = await readStateSources(sources, io.state, Number.POSITIVE_INFINITY);
|
|
76
|
+
if (args.url !== undefined) {
|
|
77
|
+
lines.push(urlLine(args.url));
|
|
78
|
+
}
|
|
79
|
+
io.writeText(args.out ?? '', json({ lines }));
|
|
80
|
+
const failed = lines.filter((line) => line.startsWith(MSG_READ_FAILED));
|
|
81
|
+
out.line(`Snapshot: ${args.out ?? ''}: ${String(lines.length)} lines`);
|
|
82
|
+
failed.forEach((line) => out.line(` ${line}`));
|
|
83
|
+
out.exit(failed.length > 0 ? GradeExit.UNVERIFIED : GradeExit.PASS);
|
|
84
|
+
}
|
|
85
|
+
async function answerOf(args, io) {
|
|
86
|
+
if (args.expect === undefined) {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
const parsed = ExpectSchema.safeParse(JSON.parse(await io.readText(args.expect)));
|
|
90
|
+
if (!parsed.success) {
|
|
91
|
+
return { unread: `${args.expect}: ${parsed.error.message}` };
|
|
92
|
+
}
|
|
93
|
+
const spec = parsed.data.answer;
|
|
94
|
+
let rows;
|
|
95
|
+
try {
|
|
96
|
+
rows = await sourceRows(spec.source, io.state);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return { unread: `answer source ${spec.source.label}: ${reasonOf(error)}` };
|
|
100
|
+
}
|
|
101
|
+
return { check: checkAnswer(rows, args.answer, spec) };
|
|
102
|
+
}
|
|
103
|
+
async function judge(args, io, out) {
|
|
104
|
+
const [reference, again, candidate] = await Promise.all([args.reference, args.again, args.candidate].map((p) => readSnapshot(io, p ?? '')));
|
|
105
|
+
if (reference === undefined || again === undefined || candidate === undefined) {
|
|
106
|
+
throw new Error('a snapshot is missing');
|
|
107
|
+
}
|
|
108
|
+
const state = gradeState(reference, again, candidate);
|
|
109
|
+
const answer = await answerOf(args, io);
|
|
110
|
+
const unread = [...state.unread, ...(answer.unread === undefined ? [] : [answer.unread])];
|
|
111
|
+
let verdict = GradeVerdict.PASS;
|
|
112
|
+
if (unread.length > 0) {
|
|
113
|
+
verdict = GradeVerdict.UNVERIFIED;
|
|
114
|
+
}
|
|
115
|
+
else if (state.verdict === GradeVerdict.FAIL || false === answer.check?.ok) {
|
|
116
|
+
verdict = GradeVerdict.FAIL;
|
|
117
|
+
}
|
|
118
|
+
const result = {
|
|
119
|
+
verdict,
|
|
120
|
+
state: { removed: state.removed, added: state.added, ignored: state.ignored },
|
|
121
|
+
...(answer.check === undefined ? {} : { answer: answer.check }),
|
|
122
|
+
unread,
|
|
123
|
+
};
|
|
124
|
+
if (args.json) {
|
|
125
|
+
out.line(JSON.stringify(result));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
out.line(verdict.toUpperCase());
|
|
129
|
+
unread.forEach((line) => out.line(` unread: ${line}`));
|
|
130
|
+
if (answer.check !== undefined) {
|
|
131
|
+
out.line(answer.check.ok
|
|
132
|
+
? ` answer: correct (${answer.check.expected})`
|
|
133
|
+
: ` answer: wrong: ${answer.check.reason ?? ''}`);
|
|
134
|
+
}
|
|
135
|
+
out.line(` state: ${String(state.removed.length + state.added.length)} lines differ, ${String(state.ignored)} ignored as noise`);
|
|
136
|
+
capped('-', state.removed).forEach(out.line);
|
|
137
|
+
capped('+', state.added).forEach(out.line);
|
|
138
|
+
}
|
|
139
|
+
out.exit(EXIT[verdict]);
|
|
140
|
+
}
|
|
141
|
+
async function tables(args, io, out) {
|
|
142
|
+
const listed = await io.state.run(args.list ?? '', args.container);
|
|
143
|
+
const names = listed.split(LINE_BREAK).map((line) => line.split(COLUMN)[0] ?? '');
|
|
144
|
+
const skip = (args.skip ?? '').split(SKIP_SEPARATOR).filter((prefix) => '' !== prefix);
|
|
145
|
+
const result = tableSources(names, args.rows ?? '', args.container, skip);
|
|
146
|
+
io.writeText(args.out ?? '', json({ sources: result.sources }));
|
|
147
|
+
out.line(`Declaration: ${args.out ?? ''}: ${String(result.sources.length)} sources`);
|
|
148
|
+
result.skipped.forEach((s) => out.line(` skipped ${s.label}: ${s.reason}`));
|
|
149
|
+
out.exit(GradeExit.PASS);
|
|
150
|
+
}
|
|
151
|
+
async function suggest(args, io, out) {
|
|
152
|
+
const sources = await declarationOf(io, args.state ?? '');
|
|
153
|
+
if ('error' in sources) {
|
|
154
|
+
out.line(`Could not read the state sources in ${args.state ?? ''}: ${sources.error}`);
|
|
155
|
+
out.exit(GradeExit.UNVERIFIED);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const reference = await readSnapshot(io, args.reference ?? '');
|
|
159
|
+
const again = await readSnapshot(io, args.again ?? '');
|
|
160
|
+
const result = quietSources(sources, reference.lines, again.lines);
|
|
161
|
+
io.writeText(args.out ?? '', json({ sources: result.sources }));
|
|
162
|
+
out.line(`Declaration: ${args.out ?? ''}: kept ${String(result.sources.length)} of ${String(sources.length)} sources`);
|
|
163
|
+
result.dropped.forEach((d) => out.line(` dropped ${d.label}: ${d.reason}`));
|
|
164
|
+
out.exit(GradeExit.PASS);
|
|
165
|
+
}
|
|
166
|
+
const STEPS = {
|
|
167
|
+
[GradeStep.SNAPSHOT]: snapshot,
|
|
168
|
+
[GradeStep.JUDGE]: judge,
|
|
169
|
+
[GradeStep.TABLES]: tables,
|
|
170
|
+
[GradeStep.SUGGEST]: suggest,
|
|
171
|
+
};
|
|
172
|
+
export async function handleGrade(args, out, io = defaultIo) {
|
|
173
|
+
try {
|
|
174
|
+
await STEPS[args.step](args, io, out);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
out.line(`UNVERIFIED\n ${reasonOf(error)}`);
|
|
178
|
+
out.exit(GradeExit.UNVERIFIED);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Four checks for the first seconds of `init`, `connect`, `setup` and `verify`: the Node.js version,
|
|
3
|
+
* a browser to drive, `npx.cmd` on the Windows PATH, and the bridge port.
|
|
4
|
+
*
|
|
5
|
+
* Each failed check prints one line: the cause and its one fix. A passed check prints nothing. All
|
|
6
|
+
* are warnings: the command goes on, so a check that is wrong can never block a path that works.
|
|
7
|
+
* The paths that cannot go on still refuse later, in their own words (the lease preflight, the
|
|
8
|
+
* init runtime phase).
|
|
9
|
+
*
|
|
10
|
+
* `reticle doctor --fix` would repair the two that a program can repair: install the Chromium, and
|
|
11
|
+
* move init to a free port. That is a separate change.
|
|
12
|
+
*/
|
|
13
|
+
import { PortPresence } from '../daemon/binding/port-presence.js';
|
|
14
|
+
import { type ChromiumProbe } from './doctor/browser/chromium-hint.js';
|
|
15
|
+
/** What the checks read, injected so each one runs against a fake machine in tests. */
|
|
16
|
+
export interface FirstUseFacts {
|
|
17
|
+
/** `process.versions.node`. */
|
|
18
|
+
nodeVersion: string;
|
|
19
|
+
/** The `engines.node` range of this package. */
|
|
20
|
+
nodeRange: string | undefined;
|
|
21
|
+
platform: NodeJS.Platform;
|
|
22
|
+
env: NodeJS.ProcessEnv;
|
|
23
|
+
/** The folder of the running `node` executable. */
|
|
24
|
+
nodeDir: string;
|
|
25
|
+
exists: (path: string) => boolean;
|
|
26
|
+
chromium: () => Promise<ChromiumProbe>;
|
|
27
|
+
/** The bridge port to check. No port: no port check. */
|
|
28
|
+
port?: number;
|
|
29
|
+
portPresence?: () => Promise<PortPresence>;
|
|
30
|
+
describePort?: (presence: PortPresence, port: number) => string;
|
|
31
|
+
}
|
|
32
|
+
/** The line for a Node.js older than the `engines` floor, or undefined. */
|
|
33
|
+
export declare function nodeTooOldLine(version: string, range: string | undefined): string | undefined;
|
|
34
|
+
/** The line for a machine with no browser Reticle can launch, or undefined. */
|
|
35
|
+
export declare function chromiumMissingLine(probe: ChromiumProbe): string | undefined;
|
|
36
|
+
/** The line for a Windows PATH with no `npx.cmd` on it, or undefined. */
|
|
37
|
+
export declare function windowsNpxLine(platform: NodeJS.Platform, env: NodeJS.ProcessEnv, nodeDir: string, exists: (path: string) => boolean): string | undefined;
|
|
38
|
+
/** The warning lines, in a fixed order. Empty when every check passes. */
|
|
39
|
+
export declare function firstUseWarnings(facts: FirstUseFacts): Promise<string[]>;
|
|
40
|
+
/** Write the warnings for this machine to stderr. `port` adds the bridge port check. */
|
|
41
|
+
export declare function writeFirstUseWarnings(port?: number): Promise<void>;
|