@reticlehq/server 3.1.0 → 3.3.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/SKILL.md +30 -28
- package/dist/command/cli/adhoc-suite.d.ts +19 -0
- package/dist/command/cli/adhoc-suite.js +88 -0
- package/dist/command/cli/adhoc-verdict.d.ts +23 -2
- package/dist/command/cli/adhoc-verdict.js +18 -10
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +37 -5
- package/dist/command/cli/cli-flow-commands.d.ts +2 -1
- package/dist/command/cli/cli-kill.js +1 -1
- package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
- package/dist/command/cli/cli-parse-grammar.js +7 -0
- package/dist/command/cli/cli-parse-verify.d.ts +2 -5
- package/dist/command/cli/cli-parse-verify.js +86 -13
- package/dist/command/cli/cli-parse.d.ts +8 -1
- package/dist/command/cli/cli-parse.js +38 -7
- package/dist/command/cli/cli-verify.d.ts +59 -0
- package/dist/command/cli/cli-verify.js +131 -19
- package/dist/command/cli/cloud-cli.js +7 -6
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +3 -7
- package/dist/command/cli/config/config-discovery.d.ts +12 -0
- package/dist/command/cli/config/config-discovery.js +19 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
- package/dist/command/cli/doctor/doctor-rows.js +12 -0
- package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/drive/drive-command.js +1 -1
- package/dist/command/cli/launch/cli-launch.d.ts +3 -23
- package/dist/command/cli/launch/cli-launch.js +29 -70
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
- package/dist/command/cli/mcp-command.js +5 -3
- package/dist/command/cli/ports/resolve/cli-port.d.ts +15 -1
- package/dist/command/cli/ports/resolve/cli-port.js +50 -2
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli/status/status-lines.d.ts +28 -0
- package/dist/command/cli/status/status-lines.js +117 -0
- package/dist/command/cli.d.ts +1 -1
- package/dist/command/cli.js +29 -177
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +32 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +93 -0
- package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
- package/dist/command/daemon/binding/loopback-reach.js +63 -0
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/daemon-resilience.d.ts +13 -1
- package/dist/command/daemon/daemon-resilience.js +12 -1
- package/dist/command/daemon/daemon.d.ts +2 -11
- package/dist/command/daemon/daemon.js +8 -11
- package/dist/command/daemon/idle-shutdown.d.ts +2 -1
- package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
- package/dist/command/daemon/lifetime/heartbeat.js +10 -0
- package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
- package/dist/command/daemon/lifetime/log-cap.js +115 -0
- package/dist/command/setup/agent-notice-memory.d.ts +16 -0
- package/dist/command/setup/agent-notice-memory.js +65 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
- package/dist/command/setup/bringup/dev-server-wait.js +16 -0
- package/dist/command/setup/bringup/ensure-daemon.js +1 -1
- package/dist/command/setup/init/init-host.js +8 -0
- package/dist/command/setup/init/init-runtime.js +13 -1
- package/dist/command/setup/node-effects.js +1 -1
- package/dist/command/setup/probe/page-probe.js +9 -2
- package/dist/command/setup/run-setup.d.ts +25 -7
- package/dist/command/setup/run-setup.js +51 -9
- package/dist/command/setup/setup-command.js +31 -3
- package/dist/command/setup/terminal/confirm.js +2 -1
- package/dist/command/update/updater.d.ts +27 -0
- package/dist/command/update/updater.js +35 -2
- package/dist/features/crawl/crawl-tools.js +3 -1
- package/dist/features/crawl/crawl.d.ts +2 -0
- package/dist/features/crawl/crawl.js +3 -1
- package/dist/features/harness/drive-report.d.ts +73 -0
- package/dist/features/harness/drive-report.js +196 -0
- package/dist/features/harness/driver.js +29 -8
- package/dist/features/harness/drivers.d.ts +19 -0
- package/dist/features/harness/drivers.js +19 -0
- package/dist/features/harness/fill-values.d.ts +67 -0
- package/dist/features/harness/fill-values.js +153 -0
- package/dist/features/harness/jev-driver.d.ts +188 -0
- package/dist/features/harness/jev-driver.js +661 -0
- package/dist/features/harness/openai-driver.d.ts +46 -0
- package/dist/features/harness/openai-driver.js +197 -0
- package/dist/features/harness/platform-config.d.ts +62 -0
- package/dist/features/harness/platform-config.js +97 -0
- package/dist/features/visual/visual-tools.js +26 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +80 -130
- package/dist/judgement/capsule/capsule-store.d.ts +31 -2
- package/dist/judgement/capsule/capsule-store.js +58 -6
- package/dist/judgement/capsule/capsule.js +5 -0
- package/dist/judgement/capsule/causal-summary.d.ts +13 -1
- package/dist/judgement/capsule/causal-summary.js +7 -0
- package/dist/judgement/domain/domain-model.js +38 -9
- package/dist/judgement/domain/domain-tools.js +4 -2
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- package/dist/judgement/phenomena/phenomena.d.ts +2 -1
- package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
- package/dist/judgement/runs/artifact/gap-report.js +46 -0
- package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
- package/dist/judgement/runs/artifact/gap-summary.js +85 -0
- package/dist/judgement/runs/artifact/render-report.js +4 -1
- package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
- package/dist/judgement/runs/artifact/to-artifact.js +1 -1
- package/dist/judgement/runs/context-tools.js +21 -3
- package/dist/judgement/runs/drive-run.js +7 -2
- package/dist/judgement/runs/verification-sync.d.ts +2 -2
- package/dist/judgement/runs/verification-sync.js +9 -2
- package/dist/language/flows/affected-tools.js +6 -3
- package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
- package/dist/language/flows/annotate-notes/annotate.js +66 -18
- package/dist/language/flows/flow-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -0
- package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
- package/dist/language/flows/flow-expect-grammar.js +92 -11
- package/dist/language/flows/flow-intent.js +1 -1
- package/dist/language/flows/flow-learning.d.ts +3 -0
- package/dist/language/flows/flow-learning.js +39 -8
- package/dist/language/flows/flow-replay-run.d.ts +55 -10
- package/dist/language/flows/flow-replay-run.js +217 -58
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +29 -11
- package/dist/language/flows/flow-replay.js +221 -203
- package/dist/language/flows/flow-result.d.ts +18 -1
- package/dist/language/flows/flow-result.js +18 -2
- package/dist/language/flows/flow-step-runners.d.ts +26 -10
- package/dist/language/flows/flow-step-runners.js +20 -4
- package/dist/language/flows/flow-store-for-session.d.ts +2 -1
- package/dist/language/flows/flow-success.d.ts +21 -6
- package/dist/language/flows/flow-success.js +50 -138
- package/dist/language/flows/flow-tools.js +8 -4
- package/dist/language/flows/flows.d.ts +15 -10
- package/dist/language/flows/flows.js +17 -4
- package/dist/language/flows/heal.d.ts +9 -1
- package/dist/language/flows/heal.js +24 -2
- package/dist/language/flows/learned-sources.d.ts +21 -0
- package/dist/language/flows/learned-sources.js +84 -0
- package/dist/language/flows/narrow-write.d.ts +4 -4
- package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
- package/dist/language/flows/recording/recording-backtrack.js +1 -1
- package/dist/language/flows/recording/replay-halt.d.ts +2 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +2 -2
- package/dist/language/flows/replay.js +13 -9
- package/dist/language/flows/stores/annotation-store.d.ts +5 -5
- package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
- package/dist/language/flows/stores/flake-store.js +2 -3
- package/dist/language/flows/suite/coverage.d.ts +1 -1
- package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
- package/dist/language/flows/suite-selection.d.ts +15 -1
- package/dist/language/flows/suite-selection.js +1 -1
- package/dist/language/flows/verify-change-tools.js +21 -4
- package/dist/memory/cloud/account-state.d.ts +1 -1
- package/dist/memory/cloud/account-state.js +5 -4
- package/dist/memory/cloud/cloud-config.d.ts +9 -0
- package/dist/memory/cloud/cloud-config.js +9 -0
- package/dist/memory/cloud/cloud-sync.d.ts +7 -4
- package/dist/memory/cloud/cloud-sync.js +11 -6
- package/dist/memory/cloud/harness-config.d.ts +12 -0
- package/dist/memory/cloud/harness-config.js +58 -0
- package/dist/memory/cloud/harness-offer.d.ts +44 -0
- package/dist/memory/cloud/harness-offer.js +146 -0
- package/dist/memory/cloud/harness-switch.d.ts +11 -0
- package/dist/memory/cloud/harness-switch.js +53 -0
- package/dist/memory/cloud/sync-cycle.d.ts +12 -0
- package/dist/memory/cloud/sync-cycle.js +29 -4
- package/dist/memory/cloud/sync-daemon.d.ts +35 -2
- package/dist/memory/cloud/sync-daemon.js +45 -0
- package/dist/memory/cloud/sync-disk.js +11 -6
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +2 -1
- package/dist/memory/impact/impact-store.d.ts +5 -0
- package/dist/memory/impact/impact-store.js +25 -0
- package/dist/memory/intent/inline-intent.js +8 -3
- package/dist/memory/intent/intent-shard-store.d.ts +33 -47
- package/dist/memory/intent/intent-shard-store.js +253 -141
- package/dist/memory/intent/intent-shard.d.ts +22 -23
- package/dist/memory/intent/intent-shard.js +1 -1
- package/dist/memory/intent/intent-store.d.ts +13 -8
- package/dist/memory/intent/intent-store.js +46 -90
- package/dist/memory/intent/intent-tools.js +34 -23
- package/dist/memory/journal/ambient-store.js +2 -3
- package/dist/memory/journal/attach-journal.d.ts +11 -0
- package/dist/memory/journal/attach-journal.js +1 -1
- package/dist/memory/journal/deviation-report.js +4 -1
- package/dist/memory/journal/deviation-service.js +10 -2
- package/dist/memory/journal/drive-flow.d.ts +2 -2
- package/dist/memory/journal/drive-flow.js +21 -12
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-query.d.ts +35 -1
- package/dist/memory/journal/journal-query.js +46 -0
- package/dist/memory/journal/journal-recorder.d.ts +71 -2
- package/dist/memory/journal/journal-recorder.js +45 -7
- package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
- package/dist/memory/journal/on-disk/retain-policy.js +44 -0
- package/dist/memory/journal/on-disk/retention.d.ts +91 -2
- package/dist/memory/journal/on-disk/retention.js +181 -11
- package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -2
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -38
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/self-observation.d.ts +3 -0
- package/dist/memory/journal/self-observation.js +67 -0
- package/dist/memory/journal/session-end.d.ts +17 -4
- package/dist/memory/journal/session-end.js +85 -7
- package/dist/memory/journal/session-journal.d.ts +122 -5
- package/dist/memory/journal/session-journal.js +489 -32
- package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
- package/dist/memory/project/artifact-root-resolver.js +89 -0
- package/dist/memory/project/artifact-root.d.ts +39 -2
- package/dist/memory/project/artifact-root.js +58 -1
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
- package/dist/memory/project/dir/fill-value-store.js +88 -0
- package/dist/memory/project/dir/reticle-dir.d.ts +15 -5
- package/dist/memory/project/dir/reticle-dir.js +11 -0
- package/dist/memory/project/fs/fs-port.d.ts +22 -1
- package/dist/memory/project/fs/fs-port.js +72 -5
- package/dist/memory/project/fs/write-atomic.d.ts +26 -0
- package/dist/memory/project/fs/write-atomic.js +48 -0
- package/dist/memory/project/learned-routes.d.ts +16 -2
- package/dist/memory/project/learned-routes.js +26 -10
- package/dist/memory/project/project-for-root.d.ts +30 -0
- package/dist/memory/project/project-for-root.js +43 -0
- package/dist/memory/project/project-tools.js +10 -5
- package/dist/memory/project/session-root.d.ts +35 -6
- package/dist/memory/project/session-root.js +70 -8
- package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
- package/dist/memory/recall/prior/connection-memory.js +8 -0
- package/dist/memory/recall/registered-projects.d.ts +2 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +9 -0
- package/dist/portal/bridge/auth-failure-reason.js +20 -3
- package/dist/portal/bridge/bridge.d.ts +6 -1
- package/dist/portal/bridge/bridge.js +36 -2
- package/dist/portal/input/real-input.js +2 -1
- package/dist/portal/pool/browser-pool.d.ts +10 -0
- package/dist/portal/pool/browser-pool.js +10 -0
- package/dist/portal/pool/playwright-launcher.js +1 -0
- package/dist/portal/pool/pool-contract.d.ts +2 -0
- package/dist/portal/pool/storage-seed.d.ts +2 -1
- package/dist/portal/pool/zero-install.d.ts +27 -0
- package/dist/portal/pool/zero-install.js +54 -0
- package/dist/portal/realm/web-realm.js +6 -0
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
- package/dist/portal/session/dev-server/served-document.d.ts +21 -0
- package/dist/portal/session/dev-server/served-document.js +57 -0
- package/dist/portal/session/facts/background-traffic.d.ts +9 -0
- package/dist/portal/session/facts/background-traffic.js +25 -0
- package/dist/portal/session/facts/session-replaced.d.ts +19 -0
- package/dist/portal/session/facts/session-replaced.js +22 -0
- package/dist/portal/session/live-control-tools.js +48 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
- package/dist/portal/session/no-session-diagnosis.js +67 -60
- package/dist/portal/session/no-session-next-action.js +3 -2
- package/dist/portal/session/no-session-watch.js +14 -10
- package/dist/portal/session/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/rebind.d.ts +18 -0
- package/dist/portal/session/rebind.js +38 -0
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +40 -1
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +13 -0
- package/dist/portal/session/session-manager.js +42 -3
- package/dist/portal/session/session-successor.d.ts +11 -0
- package/dist/portal/session/session-successor.js +14 -1
- package/dist/portal/session/session-verdict-facts.d.ts +28 -0
- package/dist/portal/session/session-verdict-facts.js +15 -0
- package/dist/portal/session/session.d.ts +26 -10
- package/dist/portal/session/session.js +48 -49
- package/dist/status-payload.d.ts +12 -1
- package/dist/status-payload.js +4 -1
- package/dist/surface/http-server.js +3 -5
- package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
- package/dist/surface/mcp/faults/mcp-outage.js +20 -4
- package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
- package/dist/surface/mcp/mcp-proxy.js +23 -12
- package/dist/surface/mcp/mcp.d.ts +10 -1
- package/dist/surface/mcp/mcp.js +59 -29
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
- package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
- package/dist/surface/mcp/proxy/proxy-log.js +7 -22
- package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +12 -1
- package/dist/surface/tools/act/act-preflight.js +6 -1
- package/dist/surface/tools/act/already-true.d.ts +40 -0
- package/dist/surface/tools/act/already-true.js +36 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +6 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
- package/dist/surface/tools/act/sequence-grade.js +13 -0
- package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
- package/dist/surface/tools/act/settle-in-flight.js +29 -6
- package/dist/surface/tools/act-capsule.js +49 -8
- package/dist/surface/tools/act-tools.js +25 -24
- package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
- package/dist/surface/tools/args/numeric-bounds.js +30 -3
- package/dist/surface/tools/assert/assert-grade.js +14 -2
- package/dist/surface/tools/assert/assert-verdict.js +74 -21
- package/dist/surface/tools/browser-tools.js +4 -3
- package/dist/surface/tools/coverage-tools.js +5 -2
- package/dist/surface/tools/dynamic-tools.js +43 -46
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +15 -0
- package/dist/surface/tools/explore-tools.js +80 -7
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +50 -3
- package/dist/surface/tools/first-sentence.d.ts +24 -0
- package/dist/surface/tools/first-sentence.js +0 -0
- package/dist/surface/tools/harness-explore.d.ts +125 -1
- package/dist/surface/tools/harness-explore.js +401 -15
- package/dist/surface/tools/harness-plan.d.ts +76 -0
- package/dist/surface/tools/harness-plan.js +156 -0
- package/dist/surface/tools/harness-toolset.js +12 -1
- package/dist/surface/tools/invoke-tool.js +82 -9
- package/dist/surface/tools/lease-hint.d.ts +8 -0
- package/dist/surface/tools/lease-hint.js +10 -0
- package/dist/surface/tools/lease-tools.d.ts +16 -28
- package/dist/surface/tools/lease-tools.js +53 -5
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/live-call-text.d.ts +6 -0
- package/dist/surface/tools/live-call-text.js +36 -2
- package/dist/surface/tools/observe-tools.js +69 -15
- package/dist/surface/tools/read/query-windowed.d.ts +1 -1
- package/dist/surface/tools/read/query-windowed.js +1 -1
- package/dist/surface/tools/read-tools.js +1 -2
- package/dist/surface/tools/real-input-attempt.d.ts +0 -5
- package/dist/surface/tools/real-input-attempt.js +1 -1
- package/dist/surface/tools/tool-kit.d.ts +23 -3
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools-helpers.d.ts +8 -7
- package/dist/surface/tools/tools-helpers.js +11 -13
- package/dist/surface/tools/tools.js +70 -30
- package/dist/telemetry/bug-found.js +32 -2
- package/dist/telemetry/feedback-context.js +3 -3
- package/dist/telemetry/feedback-local.d.ts +1 -0
- package/dist/telemetry/feedback-local.js +1 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/harness-drive.d.ts +6 -0
- package/dist/telemetry/harness-drive.js +39 -0
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/mcp-connection.d.ts +14 -1
- package/dist/telemetry/mcp-connection.js +17 -2
- package/dist/telemetry/onboarding-firsts.d.ts +30 -1
- package/dist/telemetry/onboarding-firsts.js +54 -12
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/telemetry/verification-of.js +31 -3
- package/dist/wire-journal.d.ts +31 -0
- package/dist/wire-journal.js +100 -0
- package/docs/README.md +1 -0
- package/docs/agent-cheatsheet.md +7 -3
- package/docs/autodrive.md +42 -2
- package/docs/benchmarks.md +1 -1
- package/docs/capabilities.mdx +1 -1
- package/docs/cli/cloud.mdx +11 -9
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/cli/status.mdx +35 -20
- package/docs/cli.mdx +8 -18
- package/docs/desktop.mdx +1 -1
- package/docs/faq.mdx +1 -1
- package/docs/flows.md +11 -0
- package/docs/frameworks.mdx +113 -1
- package/docs/install-agentic.mdx +36 -0
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +144 -4
- package/docs/system-map.md +6 -4
- package/docs/telemetry.md +2 -1
- package/docs/token-efficiency.md +2 -2
- package/docs/tools/overview.mdx +1 -1
- package/docs/tools/query.mdx +10 -10
- package/docs/troubleshooting.mdx +8 -2
- package/docs/usage.md +2 -2
- package/docs/what-is-recorded.md +62 -0
- package/package.json +5 -5
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
|
@@ -6,11 +6,28 @@
|
|
|
6
6
|
* pin, a predicate to assert AND a persona to drive as — so it carries more grammar than the rest of
|
|
7
7
|
* the CLI put together.
|
|
8
8
|
*/
|
|
9
|
-
import { HEADED_FLAG, PORT_FLAG, VERIFY_COMMAND, missingOperand, missingValue, notANumber, unknownArgument, } from './cli-parse-grammar.js';
|
|
9
|
+
import { EXPECT_FLAG, HEADED_FLAG, PORT_FLAG, VERIFY_COMMAND, missingOperand, missingValue, notANumber, unknownArgument, } from './cli-parse-grammar.js';
|
|
10
10
|
const TIMEOUT_FLAG = '--timeout';
|
|
11
11
|
const STORAGE_STATE_FLAG = '--storage-state';
|
|
12
|
-
|
|
12
|
+
/** The same predicate, read from a file, which no shell can mangle. See `parseExpect`. */
|
|
13
|
+
const EXPECT_FILE_FLAG = '--expect-file';
|
|
13
14
|
const SESSION_ID_FLAG = '--session-id';
|
|
15
|
+
/**
|
|
16
|
+
* `--expect` and `--storage-state` cannot both be honoured, so the pair is refused rather than
|
|
17
|
+
* half-applied.
|
|
18
|
+
*
|
|
19
|
+
* `--expect` takes its verdict from the tab a running daemon already owns — it binds nothing and
|
|
20
|
+
* launches nothing, which is exactly what makes it work while the port is busy. There is nowhere in
|
|
21
|
+
* that path to load a storage state, and the flag was not refused, it was dropped: a restricted-user
|
|
22
|
+
* absence assertion was graded against a logged-in admin tab and PASSED. A false green is worse than
|
|
23
|
+
* a missing answer, and the only honest reply to a question this command cannot ask is to say so.
|
|
24
|
+
*/
|
|
25
|
+
const MSG_EXPECT_WITH_STORAGE_STATE = `${EXPECT_FLAG} cannot be combined with ${STORAGE_STATE_FLAG}: the predicate is graded against ` +
|
|
26
|
+
'the tab the running daemon already owns, so the storage state would never be loaded and the ' +
|
|
27
|
+
'verdict would be about whoever is signed in there.\n' +
|
|
28
|
+
` Use one of them: ${EXPECT_FLAG} on its own asserts against the session you have, and ` +
|
|
29
|
+
`${STORAGE_STATE_FLAG} without ${EXPECT_FLAG} makes Reticle drive the url with that state and ` +
|
|
30
|
+
'replay the saved flows.';
|
|
14
31
|
/** Let Reticle drive the app itself and record what it drove, when nothing is saved yet. */
|
|
15
32
|
const EXPLORE_FLAG = '--explore';
|
|
16
33
|
/** Who to be while exploring — a persona, or the business outcome to reach. Implies --explore. */
|
|
@@ -22,6 +39,46 @@ const SELECT_FLAG = '--select';
|
|
|
22
39
|
* [--session-id <id>]`. The first non-flag token is the preview URL. `defaultPort` is already
|
|
23
40
|
* env + `.reticle.json` + 4400.
|
|
24
41
|
*/
|
|
42
|
+
/**
|
|
43
|
+
* Read a predicate off the command line, allowing for what a Windows shell did to it on the way.
|
|
44
|
+
*
|
|
45
|
+
* `cmd.exe` does not treat `'` as a quote character, so the command every doc and README shows
|
|
46
|
+
* arrives with LITERAL single quotes around the JSON. That is unambiguous, so it is undone: a
|
|
47
|
+
* matching pair, and only when what is inside then parses.
|
|
48
|
+
*
|
|
49
|
+
* PowerShell calling a native executable strips the INNER double quotes instead, and `{kind:signal}`
|
|
50
|
+
* is not JSON and cannot be repaired without guessing which bare words were keys and which were
|
|
51
|
+
* string values. Guessing there would assert something the caller did not write, so it is named
|
|
52
|
+
* instead — with the two forms that do work, because a predicate that does not parse produces NO
|
|
53
|
+
* verdict at all, which is the worst of the three possible outcomes.
|
|
54
|
+
*/
|
|
55
|
+
function parseExpect(raw) {
|
|
56
|
+
const unwrapped = raw.length > 1 && raw.startsWith("'") && raw.endsWith("'") ? raw.slice(1, -1) : raw;
|
|
57
|
+
try {
|
|
58
|
+
return { kind: 'ok', value: JSON.parse(unwrapped) };
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
if (looksQuoteStripped(unwrapped)) {
|
|
62
|
+
return {
|
|
63
|
+
kind: 'error',
|
|
64
|
+
message: `${EXPECT_FLAG} received ${unwrapped}, which has had its double quotes removed — that is ` +
|
|
65
|
+
'PowerShell passing an argument to a native program, not a problem with the predicate. ' +
|
|
66
|
+
`Use ${EXPECT_FILE_FLAG} <path> (no quoting at all), or escape them: ` +
|
|
67
|
+
`${EXPECT_FLAG} "{\\"kind\\":\\"signal\\",\\"name\\":\\"order:placed\\"}"`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Named as a JSON problem rather than an unknown argument: the value IS the predicate, and
|
|
71
|
+
// "unknown argument" would send the reader looking at the flag instead of at their quoting.
|
|
72
|
+
return {
|
|
73
|
+
kind: 'error',
|
|
74
|
+
message: `${EXPECT_FLAG} needs a JSON predicate; could not parse: ${unwrapped}`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** A JSON object with every double quote gone: braces, a colon, and not one `"` anywhere. */
|
|
79
|
+
function looksQuoteStripped(value) {
|
|
80
|
+
return (value.startsWith('{') && value.endsWith('}') && value.includes(':') && !value.includes('"'));
|
|
81
|
+
}
|
|
25
82
|
export function parseVerifySuffix(args, defaultPort) {
|
|
26
83
|
let headless = true;
|
|
27
84
|
let url;
|
|
@@ -29,6 +86,7 @@ export function parseVerifySuffix(args, defaultPort) {
|
|
|
29
86
|
let storageState;
|
|
30
87
|
let sessionId;
|
|
31
88
|
let expect;
|
|
89
|
+
let expectFile;
|
|
32
90
|
let explore = false;
|
|
33
91
|
let persona;
|
|
34
92
|
const select = [];
|
|
@@ -102,17 +160,17 @@ export function parseVerifySuffix(args, defaultPort) {
|
|
|
102
160
|
const v = args[i];
|
|
103
161
|
if (v === undefined)
|
|
104
162
|
return missingValue(EXPECT_FLAG);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
163
|
+
const read = parseExpect(v);
|
|
164
|
+
if ('error' === read.kind)
|
|
165
|
+
return read;
|
|
166
|
+
expect = read.value;
|
|
167
|
+
}
|
|
168
|
+
else if (arg === EXPECT_FILE_FLAG) {
|
|
169
|
+
i++;
|
|
170
|
+
const v = args[i];
|
|
171
|
+
if (v === undefined)
|
|
172
|
+
return missingValue(EXPECT_FILE_FLAG);
|
|
173
|
+
expectFile = v;
|
|
116
174
|
}
|
|
117
175
|
else if (arg.startsWith('--')) {
|
|
118
176
|
return unknownArgument(arg);
|
|
@@ -127,12 +185,27 @@ export function parseVerifySuffix(args, defaultPort) {
|
|
|
127
185
|
}
|
|
128
186
|
if (url === undefined)
|
|
129
187
|
return missingOperand(VERIFY_COMMAND, 'a url');
|
|
188
|
+
// Two ways to say the same thing, and no way to say two different things. Preferring one
|
|
189
|
+
// silently is how a run asserts something the person did not write.
|
|
190
|
+
if (expect !== undefined && expectFile !== undefined) {
|
|
191
|
+
return {
|
|
192
|
+
kind: 'error',
|
|
193
|
+
message: `${EXPECT_FLAG} and ${EXPECT_FILE_FLAG} both name the predicate; pass one`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
// Refused here, where nothing has been bound and no browser exists yet, because the damage this
|
|
197
|
+
// pair does is a verdict taken against the wrong user. A predicate from a file is the same
|
|
198
|
+
// predicate, so it is refused the same way.
|
|
199
|
+
if ((expect !== undefined || expectFile !== undefined) && storageState !== undefined) {
|
|
200
|
+
return { kind: 'error', message: MSG_EXPECT_WITH_STORAGE_STATE };
|
|
201
|
+
}
|
|
130
202
|
return {
|
|
131
203
|
kind: 'ok',
|
|
132
204
|
url,
|
|
133
205
|
headless,
|
|
134
206
|
port,
|
|
135
207
|
...(expect !== undefined ? { expect } : {}),
|
|
208
|
+
...(expectFile !== undefined ? { expectFile } : {}),
|
|
136
209
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
137
210
|
...(storageState !== undefined ? { storageState } : {}),
|
|
138
211
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
@@ -15,7 +15,7 @@ export { AGENT_FLAG, BUG_FLAG, FEEDBACK_KINDS, KIND_FLAG, RATING_FLAG, } from '.
|
|
|
15
15
|
* telling readers to do exactly that on 110 lines before it was caught, so the invocation now leads
|
|
16
16
|
* the usage block rather than being a footnote somewhere else.
|
|
17
17
|
*/
|
|
18
|
-
export declare const CLI_USAGE = "usage: npx @reticlehq/server <command> (or `reticle <command>` once the bin is on your PATH)\n\n reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]\n [--env KEY=VALUE]... [--files-only] (wire Reticle into the project in this directory)\n init is ONBOARDING: it wires the project, boots the app and proves a session\n connected. It does not drive. The FIRST RUN is the stage that proves a flow:\n reticle verify <url> --explore --persona \"<who does what>\", or the same thing\n from an agent as reticle_verify { action: \"explore\", persona: \"\u2026\" }\n --app picks WHICH app in a monorepo, when several are found\n --env is what the app needs to reach a usable state: the key from\n .env.example, the mock backend, the variable that skips an auth wall.\n Repeatable, and a value may contain spaces and equals signs\n --files-only writes the files and stops, which is what init did before it\n learned to boot the app and prove the install works\n --relaunch prints the exact command that restarts THIS conversation with\n the tools loaded, so the restart is not a chore handed to a human. It\n refuses when the session id has no transcript behind it, because\n --resume on an empty id opens a blank conversation that looks like\n success. Works with --files-only\n --license writes the key to .env and keeps .env out of git\n --json puts the result on stdout, so an agent reads one object\n --no-open / --no-agents / --url / --timeout are the runtime dials: CI, a\n headless box, or an app you already run\n --no-mcp skips MORE than the server registration: also the agent rule files\n (CLAUDE.md / AGENTS.md / .cursor) and the /reticle command, because all three\n only make sense once the tools are reachable.\n reticle serve [--port N] [--drive <url>] [--headless] [--http] [--http-port N] [--http-token T]\n reticle stop [--port N] [--quiet] (stop the daemon we started, by its recorded pid)\n reticle kill [--port N] [--force] (free the port by its LISTENER, never the agent's mcp proxy)\n reticle restart [--port N] [--force] (kill, then start a daemon and wait for a real bind)\n reticle status [--port N]\n reticle doctor [--port N] (one command to diagnose setup: Chromium, daemon, port)\n reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)\n reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] [--session-id <id>] (one-shot: drive the URL, verify saved flows, exit 0=pass)\n [--explore] [--persona <who>] (no saved flows? let Reticle drive the app itself and record them)\n [--select <label>] (repeatable: verify only flows carrying these labels \u2014 no model, exit 0=pass)\n [--expect '<json predicate>']
|
|
18
|
+
export declare const CLI_USAGE = "usage: npx @reticlehq/server <command> (or `reticle <command>` once the bin is on your PATH)\n\n reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]\n [--env KEY=VALUE]... [--files-only] [--hooks] (wire Reticle into the project in this directory)\n init is ONBOARDING: it wires the project, boots the app and proves a session\n connected. It does not drive. The FIRST RUN is the stage that proves a flow:\n reticle verify <url> --explore --persona \"<who does what>\", or the same thing\n from an agent as reticle_verify { action: \"explore\", persona: \"\u2026\" }\n --app picks WHICH app in a monorepo, when several are found\n --env is what the app needs to reach a usable state: the key from\n .env.example, the mock backend, the variable that skips an auth wall.\n Repeatable, and a value may contain spaces and equals signs\n --files-only writes the files and stops, which is what init did before it\n learned to boot the app and prove the install works\n --relaunch prints the exact command that restarts THIS conversation with\n the tools loaded, so the restart is not a chore handed to a human. It\n refuses when the session id has no transcript behind it, because\n --resume on an empty id opens a blank conversation that looks like\n success. Works with --files-only\n --license writes the key to .env and keeps .env out of git\n --json puts the result on stdout, so an agent reads one object\n --no-open / --no-agents / --url / --timeout are the runtime dials: CI, a\n headless box, or an app you already run\n --no-mcp skips MORE than the server registration: also the agent rule files\n (CLAUDE.md / AGENTS.md / .cursor) and the /reticle command, because all three\n only make sense once the tools are reachable.\n reticle serve [--port N] [--drive <url>] [--headless] [--http] [--http-port N] [--http-token T]\n reticle stop [--port N] [--quiet] (stop the daemon we started, by its recorded pid)\n reticle kill [--port N] [--force] (free the port by its LISTENER, never the agent's mcp proxy)\n reticle restart [--port N] [--force] (kill, then start a daemon and wait for a real bind)\n reticle status [--port N] [--json] (a readable block; --json for the event)\n reticle doctor [--port N] (one command to diagnose setup: Chromium, daemon, port)\n reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)\n reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] [--session-id <id>] (one-shot: drive the URL, verify saved flows, exit 0=pass)\n [--explore] [--persona <who>] (no saved flows? let Reticle drive the app itself and record them)\n [--select <label>] (repeatable: verify only flows carrying these labels \u2014 no model, exit 0=pass)\n [--expect '<json predicate>' | --expect-file <path>] (one verdict, no saved\n flows needed. --expect-file is the form no shell can mangle: cmd.exe keeps the\n single quotes the docs show, and PowerShell strips the inner double quotes, and\n a predicate that does not parse produces no verdict at all \u2014 asks the daemon that\n is already running, so nothing is bound and nothing is stopped. It NEEDS that\n daemon: with none on the port it refuses and names the flag, rather than reporting\n on your saved flows instead. Cannot be combined with --storage-state, which this\n path has nowhere to load. This is the path when your client never loaded the\n reticle_* tools. exit 0 ONLY on verified:\"yes\" \u2014 \"unknown\" is not a pass)\n reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)\n reticle gate [--since <ref>] [file...] (exit non-zero unless passing artifacts cover the affected flows)\n reticle report [--session <id>] [--hook] (what the latest session claimed, and what held)\n reticle watch [url] (on save, report which saved flows must re-verify)\n reticle drive <url> [--headless] (foreground mode, for debugging)\n reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy; auto-starts daemon if needed)\n reticle update (install the latest server version and restart)\n reticle rollback (restore the previous server version and restart)\n reticle license (show enterprise license status: active | eval | missing)\n reticle telemetry [status|enable|disable] (anonymous usage metrics; status shows what's sent + the policy)\n reticle feedback [--rating 1-5] [--bug] \"message\" (tell us what worked and what didn't; prints exactly what it sends)\n reticle feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement|experience> \"message\"\n (agents: file from anywhere, including a setup that never finished)\n reticle identify --context company|side_project|open_source|learning [--company N] [--email E] [--forget]\n (OPT-IN: tell us who you are, e.g. for support or an enterprise trial)\n\nCloud (link this project to Reticle; runs/flows recorded on the dashboard):\n reticle login [--url <u>] [--email <e>] [--code <c>] [--org <n>]\n (browser device flow by default; --email mails a code)\n reticle logout [--url <u>] (sign out of ONE host; others stay signed in)\n reticle link [--project <name|id>] (bind this repo: mints a scoped key, writes .reticle/cloud.json)\n reticle whoami (who am I signed in as, and is this repo attached?)\n reticle project <ls|create <name>> (list or create cloud projects)\n reticle config [--runs on|off] [--memory on|off] [--flows on|off] [--verify local|server]\n reticle push | sync [--watch] (one sync cycle: send the difference, collect decisions)\n reticle runs | regression | share <runId> (read cloud state; regression exits 3 if any flow broke)\n\n'drive' shows the browser; everything else is hidden (serve/mcp own the pool behind leases, replay\nand the spec runner: batch work). --headed opts any of them in, --headless hides drive, CI hides it.";
|
|
19
19
|
/** The `reticle telemetry` sub-actions. Bare `reticle telemetry` means `status`. */
|
|
20
20
|
export declare const TelemetryAction: {
|
|
21
21
|
readonly STATUS: "status";
|
|
@@ -58,6 +58,7 @@ export type CliResult = {
|
|
|
58
58
|
env: string[];
|
|
59
59
|
filesOnly: boolean;
|
|
60
60
|
captureBodies: boolean;
|
|
61
|
+
hooks: boolean;
|
|
61
62
|
json: boolean;
|
|
62
63
|
relaunch: boolean;
|
|
63
64
|
open: boolean;
|
|
@@ -88,6 +89,7 @@ export type CliResult = {
|
|
|
88
89
|
} | {
|
|
89
90
|
kind: 'status';
|
|
90
91
|
port: number;
|
|
92
|
+
json: boolean;
|
|
91
93
|
} | {
|
|
92
94
|
kind: 'license';
|
|
93
95
|
} | {
|
|
@@ -147,6 +149,7 @@ export type CliResult = {
|
|
|
147
149
|
storageState?: string;
|
|
148
150
|
sessionId?: string;
|
|
149
151
|
expect?: unknown;
|
|
152
|
+
expectFile?: string;
|
|
150
153
|
explore?: boolean;
|
|
151
154
|
persona?: string;
|
|
152
155
|
select?: string[];
|
|
@@ -164,6 +167,10 @@ export type CliResult = {
|
|
|
164
167
|
files: string[];
|
|
165
168
|
since?: string;
|
|
166
169
|
hook?: boolean;
|
|
170
|
+
} | {
|
|
171
|
+
kind: 'report';
|
|
172
|
+
session?: string;
|
|
173
|
+
hook: boolean;
|
|
167
174
|
} | {
|
|
168
175
|
kind: 'watch';
|
|
169
176
|
url?: string;
|
|
@@ -23,7 +23,7 @@ export { AGENT_FLAG, BUG_FLAG, FEEDBACK_KINDS, KIND_FLAG, RATING_FLAG, } from '.
|
|
|
23
23
|
export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticle <command>\` once the bin is on your PATH)
|
|
24
24
|
|
|
25
25
|
reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
|
|
26
|
-
[--env KEY=VALUE]... [--files-only] (wire Reticle into the project in this directory)
|
|
26
|
+
[--env KEY=VALUE]... [--files-only] [--hooks] (wire Reticle into the project in this directory)
|
|
27
27
|
init is ONBOARDING: it wires the project, boots the app and proves a session
|
|
28
28
|
connected. It does not drive. The FIRST RUN is the stage that proves a flow:
|
|
29
29
|
reticle verify <url> --explore --persona "<who does what>", or the same thing
|
|
@@ -50,18 +50,24 @@ export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticl
|
|
|
50
50
|
reticle stop [--port N] [--quiet] (stop the daemon we started, by its recorded pid)
|
|
51
51
|
reticle kill [--port N] [--force] (free the port by its LISTENER, never the agent's mcp proxy)
|
|
52
52
|
reticle restart [--port N] [--force] (kill, then start a daemon and wait for a real bind)
|
|
53
|
-
reticle status [--port N]
|
|
53
|
+
reticle status [--port N] [--json] (a readable block; --json for the event)
|
|
54
54
|
reticle doctor [--port N] (one command to diagnose setup: Chromium, daemon, port)
|
|
55
55
|
reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)
|
|
56
56
|
reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] [--session-id <id>] (one-shot: drive the URL, verify saved flows, exit 0=pass)
|
|
57
57
|
[--explore] [--persona <who>] (no saved flows? let Reticle drive the app itself and record them)
|
|
58
58
|
[--select <label>] (repeatable: verify only flows carrying these labels — no model, exit 0=pass)
|
|
59
|
-
[--expect '<json predicate>']
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
[--expect '<json predicate>' | --expect-file <path>] (one verdict, no saved
|
|
60
|
+
flows needed. --expect-file is the form no shell can mangle: cmd.exe keeps the
|
|
61
|
+
single quotes the docs show, and PowerShell strips the inner double quotes, and
|
|
62
|
+
a predicate that does not parse produces no verdict at all — asks the daemon that
|
|
63
|
+
is already running, so nothing is bound and nothing is stopped. It NEEDS that
|
|
64
|
+
daemon: with none on the port it refuses and names the flag, rather than reporting
|
|
65
|
+
on your saved flows instead. Cannot be combined with --storage-state, which this
|
|
66
|
+
path has nowhere to load. This is the path when your client never loaded the
|
|
67
|
+
reticle_* tools. exit 0 ONLY on verified:"yes" — "unknown" is not a pass)
|
|
63
68
|
reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)
|
|
64
69
|
reticle gate [--since <ref>] [file...] (exit non-zero unless passing artifacts cover the affected flows)
|
|
70
|
+
reticle report [--session <id>] [--hook] (what the latest session claimed, and what held)
|
|
65
71
|
reticle watch [url] (on save, report which saved flows must re-verify)
|
|
66
72
|
reticle drive <url> [--headless] (foreground mode, for debugging)
|
|
67
73
|
reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy; auto-starts daemon if needed)
|
|
@@ -102,6 +108,9 @@ const CAPSULES_COMMAND = 'capsules';
|
|
|
102
108
|
const GATE_COMMAND = 'gate';
|
|
103
109
|
/** Hook mode: prose a human can read, and silence when there was simply nothing to check. */
|
|
104
110
|
const HOOK_FLAG = '--hook';
|
|
111
|
+
/** `reticle report [--session <id>] [--hook]` — what the latest session claimed, and what held. */
|
|
112
|
+
const REPORT_COMMAND = 'report';
|
|
113
|
+
const SESSION_FLAG = '--session';
|
|
105
114
|
const WATCH_COMMAND = 'watch';
|
|
106
115
|
const UPDATE_COMMAND = 'update';
|
|
107
116
|
const ROLLBACK_COMMAND = 'rollback';
|
|
@@ -146,6 +155,7 @@ const KNOWN_COMMANDS = new Set([
|
|
|
146
155
|
HUNT_COMMAND,
|
|
147
156
|
CAPSULES_COMMAND,
|
|
148
157
|
GATE_COMMAND,
|
|
158
|
+
REPORT_COMMAND,
|
|
149
159
|
WATCH_COMMAND,
|
|
150
160
|
UPDATE_COMMAND,
|
|
151
161
|
ROLLBACK_COMMAND,
|
|
@@ -228,6 +238,8 @@ const FILES_ONLY_FLAG = '--files-only';
|
|
|
228
238
|
* and `init` runs unattended. Whoever passes this has decided; nothing decides it for them.
|
|
229
239
|
*/
|
|
230
240
|
const CAPTURE_BODIES_FLAG = '--capture-bodies';
|
|
241
|
+
/** `--hooks`: also install the print-only Claude Code Stop hook (`reticle report --hook`). */
|
|
242
|
+
const HOOKS_FLAG = '--hooks';
|
|
231
243
|
/**
|
|
232
244
|
* The rest of the runtime surface.
|
|
233
245
|
*
|
|
@@ -410,6 +422,7 @@ function parseInitFlags(args) {
|
|
|
410
422
|
const env = [];
|
|
411
423
|
let filesOnly = false;
|
|
412
424
|
let captureBodies = false;
|
|
425
|
+
let hooks = false;
|
|
413
426
|
let json = false;
|
|
414
427
|
let open = true;
|
|
415
428
|
let relaunch = false;
|
|
@@ -457,6 +470,9 @@ function parseInitFlags(args) {
|
|
|
457
470
|
else if (arg === CAPTURE_BODIES_FLAG) {
|
|
458
471
|
captureBodies = true;
|
|
459
472
|
}
|
|
473
|
+
else if (arg === HOOKS_FLAG) {
|
|
474
|
+
hooks = true;
|
|
475
|
+
}
|
|
460
476
|
else if (arg === JSON_FLAG) {
|
|
461
477
|
json = true;
|
|
462
478
|
}
|
|
@@ -520,6 +536,7 @@ function parseInitFlags(args) {
|
|
|
520
536
|
env,
|
|
521
537
|
filesOnly,
|
|
522
538
|
captureBodies,
|
|
539
|
+
hooks,
|
|
523
540
|
json,
|
|
524
541
|
open,
|
|
525
542
|
relaunch,
|
|
@@ -606,6 +623,7 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
|
|
|
606
623
|
env: r.env,
|
|
607
624
|
filesOnly: r.filesOnly,
|
|
608
625
|
captureBodies: r.captureBodies,
|
|
626
|
+
hooks: r.hooks,
|
|
609
627
|
json: r.json,
|
|
610
628
|
open: r.open,
|
|
611
629
|
relaunch: r.relaunch,
|
|
@@ -644,7 +662,10 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
|
|
|
644
662
|
}
|
|
645
663
|
case STATUS_COMMAND: {
|
|
646
664
|
const port = parsePortFlag(rest, defaultPort);
|
|
647
|
-
|
|
665
|
+
// The block is the default and the JSON is the opt-in, which is the way round `tutorial`
|
|
666
|
+
// already settled on: a person typing this should not have to ask for prose, and a caller
|
|
667
|
+
// parsing it knows to ask for the object.
|
|
668
|
+
return { kind: 'status', port, json: rest.includes(JSON_FLAG) };
|
|
648
669
|
}
|
|
649
670
|
case 'tutorial': {
|
|
650
671
|
// `--agent` is the opt-in, because a person typing this is the common case and should not have
|
|
@@ -769,6 +790,7 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
|
|
|
769
790
|
...(r.storageState !== undefined ? { storageState: r.storageState } : {}),
|
|
770
791
|
...(r.sessionId !== undefined ? { sessionId: r.sessionId } : {}),
|
|
771
792
|
...(r.expect !== undefined ? { expect: r.expect } : {}),
|
|
793
|
+
...(r.expectFile !== undefined ? { expectFile: r.expectFile } : {}),
|
|
772
794
|
...(true === r.explore ? { explore: true } : {}),
|
|
773
795
|
...(r.persona !== undefined ? { persona: r.persona } : {}),
|
|
774
796
|
...(r.select !== undefined ? { select: r.select } : {}),
|
|
@@ -799,6 +821,15 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
|
|
|
799
821
|
...(hook ? { hook: true } : {}),
|
|
800
822
|
};
|
|
801
823
|
}
|
|
824
|
+
case REPORT_COMMAND: {
|
|
825
|
+
const at = rest.indexOf(SESSION_FLAG);
|
|
826
|
+
const session = -1 === at ? undefined : rest[at + 1];
|
|
827
|
+
return {
|
|
828
|
+
kind: 'report',
|
|
829
|
+
hook: rest.includes(HOOK_FLAG),
|
|
830
|
+
...(session === undefined ? {} : { session }),
|
|
831
|
+
};
|
|
832
|
+
}
|
|
802
833
|
case WATCH_COMMAND: {
|
|
803
834
|
// `reticle watch [url]` — on file save, report which saved flows must re-verify.
|
|
804
835
|
const url = rest.find((arg) => !arg.startsWith('-'));
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { type ReticleVerificationRun } from '@reticlehq/core';
|
|
13
13
|
import { type RunningServer } from '../../index.js';
|
|
14
|
+
import { PortPresence } from '../daemon/binding/port-presence.js';
|
|
14
15
|
import { type VerifyProgressListener } from '../../judgement/runs/reticle-runner.js';
|
|
15
16
|
import type { ToolDeps } from '../../surface/tools/tool-kit.js';
|
|
16
17
|
/** The live capabilities runVerify needs — faked in tests so the logic runs without a browser. */
|
|
@@ -104,10 +105,68 @@ export declare function urlParts(url: string): {
|
|
|
104
105
|
* first bullet — not this change.
|
|
105
106
|
*/
|
|
106
107
|
export declare function portBusyMessage(port: number): string;
|
|
108
|
+
/**
|
|
109
|
+
* What `--expect` is answered with when the port is not serving a daemon.
|
|
110
|
+
*
|
|
111
|
+
* It used to be answered with nothing at all. The predicate was read only inside the daemon branch,
|
|
112
|
+
* so on a free or foreign port the flag was dropped without a word and the command carried on into
|
|
113
|
+
* the saved-flows path — where it reported, accurately and about something else entirely, that there
|
|
114
|
+
* were no saved flows to verify. Byte-identical output to the same command with no `--expect` on it.
|
|
115
|
+
*
|
|
116
|
+
* That is the documented recovery for a client that never loaded the `reticle_*` tools, so it failed
|
|
117
|
+
* for the people with no other route to a verdict, and it failed in the one way they could not
|
|
118
|
+
* diagnose: the refusal they got was true.
|
|
119
|
+
*/
|
|
120
|
+
export declare function expectNeedsDaemonMessage(port: number, presence: PortPresence): string;
|
|
121
|
+
/** What `verify` does next, once the options and the state of the port are both known. */
|
|
122
|
+
export declare const VerifyRoute: {
|
|
123
|
+
/** Ask the daemon that already owns the port for a one-shot verdict on the predicate. */
|
|
124
|
+
readonly ADHOC: "adhoc";
|
|
125
|
+
/** Ask the daemon that already owns the port to replay the saved flows. See runAdhocSuite. */
|
|
126
|
+
readonly ADHOC_SUITE: "adhoc-suite";
|
|
127
|
+
/** Boot our own daemon, drive the url, replay the flows saved on disk. */
|
|
128
|
+
readonly FLOWS: "flows";
|
|
129
|
+
/** Answer the caller, and run nothing. */
|
|
130
|
+
readonly REFUSE: "refuse";
|
|
131
|
+
};
|
|
132
|
+
export type VerifyRoute = (typeof VerifyRoute)[keyof typeof VerifyRoute];
|
|
133
|
+
export type VerifyPlan = {
|
|
134
|
+
route: typeof VerifyRoute.ADHOC;
|
|
135
|
+
} | {
|
|
136
|
+
route: typeof VerifyRoute.ADHOC_SUITE;
|
|
137
|
+
} | {
|
|
138
|
+
route: typeof VerifyRoute.FLOWS;
|
|
139
|
+
} | {
|
|
140
|
+
route: typeof VerifyRoute.REFUSE;
|
|
141
|
+
message: string;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* All four routes this command has, in one pure function.
|
|
145
|
+
*
|
|
146
|
+
* They used to be a pair of nested `if`s that consulted `--expect` only after the port had already
|
|
147
|
+
* answered as a daemon, which is how one of the four went missing: a predicate on any other port
|
|
148
|
+
* state fell through to a path that cannot see it. Stating the cases together is what makes an
|
|
149
|
+
* unhandled one visible, and it lets every one of them be driven with no port, no daemon and no
|
|
150
|
+
* browser — which is where an argument-level decision belongs.
|
|
151
|
+
*/
|
|
152
|
+
export declare function routeVerify(args: {
|
|
153
|
+
/** True when the caller supplied a predicate, by `--expect` or `--expect-file`. */
|
|
154
|
+
hasPredicate: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* True when the caller asked for something only a browser of OUR OWN can do: `--explore` (a model
|
|
157
|
+
* driving a fresh page), `--headed`, or `--storage-state`. The running daemon's tab can honour
|
|
158
|
+
* none of them, so replaying there would silently ignore the request.
|
|
159
|
+
*/
|
|
160
|
+
wantsOwnBrowser?: boolean;
|
|
161
|
+
presence: PortPresence;
|
|
162
|
+
port: number;
|
|
163
|
+
}): VerifyPlan;
|
|
107
164
|
export declare function handleVerify(parsed: {
|
|
108
165
|
url: string;
|
|
109
166
|
/** A parsed predicate for a flow-free, one-shot verdict against the running daemon. */
|
|
110
167
|
expect?: unknown;
|
|
168
|
+
/** The same predicate in a file, which is the form no shell can mangle. Resolved below. */
|
|
169
|
+
expectFile?: string;
|
|
111
170
|
headless: boolean;
|
|
112
171
|
timeoutMs?: number;
|
|
113
172
|
storageState?: string;
|
|
@@ -10,16 +10,19 @@
|
|
|
10
10
|
* — is unit-tested without launching a real browser.
|
|
11
11
|
*/
|
|
12
12
|
import { join, basename } from 'node:path';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
13
14
|
import { runAdhocVerdict } from './adhoc-verdict.js';
|
|
15
|
+
import { runAdhocSuite } from './adhoc-suite.js';
|
|
14
16
|
import { exploreApp, harnessAvailable, MSG_NO_HARNESS_KEY, } from '../../surface/tools/harness-explore.js';
|
|
15
17
|
import { resolveSuiteSelection } from '../../language/flows/suite-selection.js';
|
|
16
18
|
import { readOrCreatePairingTokenSync, defaultPairingTokenDir, } from '../../portal/bridge/pairing-token.js';
|
|
17
19
|
import { randomUUID } from 'node:crypto';
|
|
18
20
|
import { RETICLE_DEFAULT_PORT, ReticleEnv, bridgeWsUrl, isLoopbackHostname, ReticleDir, RunAgentKind, RunFramework, RunProfile, RunTrigger, VerdictStatus, VerifyPhase, } from '@reticlehq/core';
|
|
19
21
|
import { start } from '../../index.js';
|
|
20
|
-
import { probePresence, PortPresence } from '../daemon/binding/port-presence.js';
|
|
22
|
+
import { probePresence, describePresence, PortPresence, } from '../daemon/binding/port-presence.js';
|
|
23
|
+
import { EXPECT_FLAG } from './cli-parse-grammar.js';
|
|
21
24
|
import { probeDaemon } from '../../surface/mcp/mcp-proxy.js';
|
|
22
|
-
import { fetchStatus } from '
|
|
25
|
+
import { fetchStatus } from '../daemon/binding/daemon-status-probe.js';
|
|
23
26
|
import { cloudFetch, createProgressReporter, resolveCloudConfig, syncProgressToCloud, syncRunToCloud, SyncOutcome, } from '../../memory/cloud/cloud-sync.js';
|
|
24
27
|
import { ReticleRunner } from '../../judgement/runs/reticle-runner.js';
|
|
25
28
|
import { createRunnerPort } from '../../judgement/runs/runner-port.js';
|
|
@@ -182,7 +185,7 @@ async function explore(conn, args, ports) {
|
|
|
182
185
|
}
|
|
183
186
|
/**
|
|
184
187
|
* Best-effort push of a finished run to the cloud dashboard. Opt-in: only fires when the user has set
|
|
185
|
-
* RETICLE_CLOUD_URL +
|
|
188
|
+
* RETICLE_CLOUD_URL + RETICLE_API_KEY (the "shifted to server" step). Absent → no-op, nothing leaves the
|
|
186
189
|
* machine (the no-phone-home default). A push failure NEVER changes the verdict or exit code — the run is
|
|
187
190
|
* already reported locally; the cloud copy is an enhancement.
|
|
188
191
|
*/
|
|
@@ -365,8 +368,92 @@ export function portBusyMessage(port) {
|
|
|
365
368
|
"your agent's MCP link and the proxy usually respawns a daemon into the gap before verify can " +
|
|
366
369
|
'bind — prefer one of the above.');
|
|
367
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* What `--expect` is answered with when the port is not serving a daemon.
|
|
373
|
+
*
|
|
374
|
+
* It used to be answered with nothing at all. The predicate was read only inside the daemon branch,
|
|
375
|
+
* so on a free or foreign port the flag was dropped without a word and the command carried on into
|
|
376
|
+
* the saved-flows path — where it reported, accurately and about something else entirely, that there
|
|
377
|
+
* were no saved flows to verify. Byte-identical output to the same command with no `--expect` on it.
|
|
378
|
+
*
|
|
379
|
+
* That is the documented recovery for a client that never loaded the `reticle_*` tools, so it failed
|
|
380
|
+
* for the people with no other route to a verdict, and it failed in the one way they could not
|
|
381
|
+
* diagnose: the refusal they got was true.
|
|
382
|
+
*/
|
|
383
|
+
export function expectNeedsDaemonMessage(port, presence) {
|
|
384
|
+
return (`${EXPECT_FLAG} asks the Reticle daemon that already owns port ${String(port)} for the ` +
|
|
385
|
+
'verdict — it binds nothing and launches nothing, which is what lets it answer while the port ' +
|
|
386
|
+
`is busy. Right now ${describePresence(presence, port)}.\n\n` +
|
|
387
|
+
' The predicate did not run, and nothing was proved.\n\n' +
|
|
388
|
+
' • Start a daemon on that port, pointed at the app, then re-run this command: ' +
|
|
389
|
+
`npx @reticlehq/server serve --port ${String(port)} --drive <url>\n` +
|
|
390
|
+
` • Or drop ${EXPECT_FLAG}, and verify drives the url itself and replays your saved flows — ` +
|
|
391
|
+
'a different question, and it proves nothing about your predicate.');
|
|
392
|
+
}
|
|
393
|
+
/** What `verify` does next, once the options and the state of the port are both known. */
|
|
394
|
+
export const VerifyRoute = {
|
|
395
|
+
/** Ask the daemon that already owns the port for a one-shot verdict on the predicate. */
|
|
396
|
+
ADHOC: 'adhoc',
|
|
397
|
+
/** Ask the daemon that already owns the port to replay the saved flows. See runAdhocSuite. */
|
|
398
|
+
ADHOC_SUITE: 'adhoc-suite',
|
|
399
|
+
/** Boot our own daemon, drive the url, replay the flows saved on disk. */
|
|
400
|
+
FLOWS: 'flows',
|
|
401
|
+
/** Answer the caller, and run nothing. */
|
|
402
|
+
REFUSE: 'refuse',
|
|
403
|
+
};
|
|
404
|
+
/**
|
|
405
|
+
* All four routes this command has, in one pure function.
|
|
406
|
+
*
|
|
407
|
+
* They used to be a pair of nested `if`s that consulted `--expect` only after the port had already
|
|
408
|
+
* answered as a daemon, which is how one of the four went missing: a predicate on any other port
|
|
409
|
+
* state fell through to a path that cannot see it. Stating the cases together is what makes an
|
|
410
|
+
* unhandled one visible, and it lets every one of them be driven with no port, no daemon and no
|
|
411
|
+
* browser — which is where an argument-level decision belongs.
|
|
412
|
+
*/
|
|
413
|
+
export function routeVerify(args) {
|
|
414
|
+
if (args.hasPredicate) {
|
|
415
|
+
return args.presence === PortPresence.DAEMON
|
|
416
|
+
? { route: VerifyRoute.ADHOC }
|
|
417
|
+
: { route: VerifyRoute.REFUSE, message: expectNeedsDaemonMessage(args.port, args.presence) };
|
|
418
|
+
}
|
|
419
|
+
if (args.presence !== PortPresence.DAEMON)
|
|
420
|
+
return { route: VerifyRoute.FLOWS };
|
|
421
|
+
// No predicate, but there may be SAVED FLOWS, and a daemon already owns the port: replay them
|
|
422
|
+
// there rather than refusing, unless the request needs a browser that path never opens.
|
|
423
|
+
return true === args.wantsOwnBrowser
|
|
424
|
+
? { route: VerifyRoute.REFUSE, message: portBusyMessage(args.port) }
|
|
425
|
+
: { route: VerifyRoute.ADHOC_SUITE };
|
|
426
|
+
}
|
|
368
427
|
export function handleVerify(parsed) {
|
|
369
428
|
const now = () => Date.now();
|
|
429
|
+
/*
|
|
430
|
+
* `--expect-file` is `--expect` with the shell taken out of the loop.
|
|
431
|
+
*
|
|
432
|
+
* Resolved here rather than in the parser because the parser is pure and reads no disk, and
|
|
433
|
+
* because the two failures a reader can hit — the file is not there, and what is in it is not
|
|
434
|
+
* JSON — are worth saying apart. Either one ends the run before anything binds: a predicate that
|
|
435
|
+
* could not be read produces NO verdict, and exiting 1 with the reason is the only honest answer.
|
|
436
|
+
*/
|
|
437
|
+
let expectation = parsed.expect;
|
|
438
|
+
if (parsed.expectFile !== undefined) {
|
|
439
|
+
let text;
|
|
440
|
+
try {
|
|
441
|
+
text = readFileSync(parsed.expectFile, 'utf8');
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
process.stderr.write(`--expect-file: cannot read ${parsed.expectFile}\n`);
|
|
445
|
+
process.exit(1);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
expectation = JSON.parse(text);
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
process.stderr.write(`--expect-file: ${parsed.expectFile} is not valid JSON, so no predicate was read\n`);
|
|
453
|
+
process.exit(1);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
370
457
|
const reticleRoot = join(process.cwd(), ReticleDir.ROOT);
|
|
371
458
|
const projectName = basename(process.cwd()) || DEFAULT_PROJECT_NAME;
|
|
372
459
|
const ports = {
|
|
@@ -389,20 +476,32 @@ export function handleVerify(parsed) {
|
|
|
389
476
|
// this used to reach the user as a raw node stack rather than as an answer.
|
|
390
477
|
void (async () => {
|
|
391
478
|
const port = parsed.port ?? RETICLE_DEFAULT_PORT;
|
|
392
|
-
|
|
393
|
-
|
|
479
|
+
const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
480
|
+
const plan = routeVerify({
|
|
481
|
+
hasPredicate: expectation !== undefined,
|
|
482
|
+
wantsOwnBrowser: true === parsed.explore || !parsed.headless || parsed.storageState !== undefined,
|
|
483
|
+
presence,
|
|
484
|
+
port,
|
|
485
|
+
});
|
|
486
|
+
switch (plan.route) {
|
|
487
|
+
// Either the busy port, or a predicate this state cannot answer. Both are refusals that name
|
|
488
|
+
// what was asked for — never a report about a check nobody requested.
|
|
489
|
+
case VerifyRoute.REFUSE:
|
|
490
|
+
ports.fail(plan.message);
|
|
491
|
+
ports.exit(EXIT_FAIL);
|
|
492
|
+
return;
|
|
394
493
|
// A daemon owning the port is the NORMAL state after a working install, and it used to be the
|
|
395
494
|
// end of the road: `verify` refused, the other verdict paths need saved flows a first-install
|
|
396
495
|
// project does not have, and stopping the daemon cuts the agent's own MCP link. An agent could
|
|
397
496
|
// hold a live, correctly-wired app and have no way to reach a verdict at all.
|
|
398
497
|
//
|
|
399
|
-
// With `--expect` there is
|
|
400
|
-
//
|
|
401
|
-
|
|
498
|
+
// With `--expect` there is a way, and it does not need the port: ask the daemon that already
|
|
499
|
+
// owns it. See runAdhocVerdict.
|
|
500
|
+
case VerifyRoute.ADHOC: {
|
|
402
501
|
const verdict = await runAdhocVerdict({
|
|
403
502
|
port,
|
|
404
503
|
...(parsed.url !== undefined && '' !== parsed.url ? { url: parsed.url } : {}),
|
|
405
|
-
predicate:
|
|
504
|
+
predicate: expectation,
|
|
406
505
|
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
407
506
|
...((t) => (t === undefined || 0 === t.length ? {} : { token: t }))(readOrCreatePairingTokenSync(defaultPairingTokenDir())),
|
|
408
507
|
});
|
|
@@ -411,16 +510,29 @@ export function handleVerify(parsed) {
|
|
|
411
510
|
ports.exit(verdict.code);
|
|
412
511
|
return;
|
|
413
512
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
513
|
+
// No predicate, a daemon on the port, and saved flows to run: the other half of the same
|
|
514
|
+
// dead end. The whole promise of a saved suite is that it runs again without an agent.
|
|
515
|
+
case VerifyRoute.ADHOC_SUITE: {
|
|
516
|
+
const suite = await runAdhocSuite({
|
|
517
|
+
port,
|
|
518
|
+
...(parsed.select === undefined ? {} : { select: parsed.select }),
|
|
519
|
+
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
520
|
+
...((t) => (t === undefined || 0 === t.length ? {} : { token: t }))(readOrCreatePairingTokenSync(defaultPairingTokenDir())),
|
|
521
|
+
});
|
|
522
|
+
for (const line of suite.lines)
|
|
523
|
+
ports.out(line);
|
|
524
|
+
ports.exit(suite.code);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
case VerifyRoute.FLOWS:
|
|
528
|
+
await runVerify({
|
|
529
|
+
url: parsed.url,
|
|
530
|
+
timeoutMs: parsed.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS,
|
|
531
|
+
...(true === parsed.explore ? { explore: true } : {}),
|
|
532
|
+
...(parsed.persona === undefined ? {} : { persona: parsed.persona }),
|
|
533
|
+
...(parsed.select === undefined ? {} : { select: parsed.select }),
|
|
534
|
+
}, ports);
|
|
535
|
+
return;
|
|
417
536
|
}
|
|
418
|
-
await runVerify({
|
|
419
|
-
url: parsed.url,
|
|
420
|
-
timeoutMs: parsed.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS,
|
|
421
|
-
...(true === parsed.explore ? { explore: true } : {}),
|
|
422
|
-
...(parsed.persona === undefined ? {} : { persona: parsed.persona }),
|
|
423
|
-
...(parsed.select === undefined ? {} : { select: parsed.select }),
|
|
424
|
-
}, ports);
|
|
425
537
|
})();
|
|
426
538
|
}
|