@reticlehq/server 2.7.0 → 2.9.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/README.md +14 -5
- package/SKILL.md +130 -778
- package/dist/bridge/bridge.d.ts +23 -0
- package/dist/bridge/bridge.js +80 -2
- package/dist/bridge/bridge.test-harness.d.ts +5 -0
- package/dist/bridge/bridge.test-harness.js +6 -4
- package/dist/capsule/causal-summary.d.ts +36 -1
- package/dist/capsule/causal-summary.js +33 -7
- package/dist/capsule/divergence.js +7 -1
- package/dist/cli/chromium-hint.d.ts +33 -5
- package/dist/cli/chromium-hint.js +82 -12
- package/dist/cli/cli-doctor.js +65 -34
- package/dist/cli/cli-flow-commands.js +8 -8
- package/dist/cli/cli-kill.d.ts +58 -0
- package/dist/cli/cli-kill.js +162 -0
- package/dist/cli/cli-parse.d.ts +16 -1
- package/dist/cli/cli-parse.js +32 -9
- package/dist/cli/cli-port.d.ts +32 -0
- package/dist/cli/cli-port.js +82 -35
- package/dist/cli/cli-verify.d.ts +21 -0
- package/dist/cli/cli-verify.js +47 -3
- package/dist/cli/config-discovery.d.ts +35 -0
- package/dist/cli/config-discovery.js +163 -0
- package/dist/cli/doctor-daemon-line.js +4 -1
- package/dist/cli/doctor-project-line.d.ts +37 -0
- package/dist/cli/doctor-project-line.js +35 -0
- package/dist/cli/doctor-rows.d.ts +51 -0
- package/dist/cli/doctor-rows.js +54 -0
- package/dist/cli/doctor-sessions-line.d.ts +12 -0
- package/dist/cli/doctor-sessions-line.js +69 -0
- package/dist/cli/drive-attach.d.ts +99 -0
- package/dist/cli/drive-attach.js +203 -0
- package/dist/cli/drive-command.d.ts +13 -0
- package/dist/cli/drive-command.js +103 -0
- package/dist/cli/open-note.d.ts +20 -0
- package/dist/cli/open-note.js +37 -0
- package/dist/cli/port-holder.d.ts +10 -0
- package/dist/cli/port-holder.js +26 -2
- package/dist/cli/status-next-action.d.ts +37 -0
- package/dist/cli/status-next-action.js +61 -0
- package/dist/cli.js +119 -39
- package/dist/crawl/crawl-tools.js +12 -2
- package/dist/crawl/crawl.d.ts +13 -0
- package/dist/crawl/crawl.js +50 -2
- package/dist/events/contradictions.d.ts +46 -0
- package/dist/events/contradictions.js +35 -22
- package/dist/events/declared.d.ts +45 -0
- package/dist/events/declared.js +73 -0
- package/dist/events/echo-mismatch.d.ts +8 -2
- package/dist/events/echo-mismatch.js +33 -5
- package/dist/events/event-filters.d.ts +1 -1
- package/dist/events/event-filters.js +7 -4
- package/dist/events/predicate-eval.d.ts +17 -72
- package/dist/events/predicate-eval.js +117 -236
- package/dist/events/predicate-parse.js +31 -3
- package/dist/events/predicate-route.d.ts +32 -0
- package/dist/events/predicate-route.js +102 -0
- package/dist/events/predicate-schema.d.ts +99 -0
- package/dist/events/predicate-schema.js +309 -0
- package/dist/events/predicate.d.ts +8 -0
- package/dist/events/predicate.js +296 -22
- package/dist/flows/annotate-tools.js +3 -0
- package/dist/flows/annotate.js +18 -4
- package/dist/flows/coverage.d.ts +38 -5
- package/dist/flows/coverage.js +30 -4
- package/dist/flows/decision.d.ts +15 -0
- package/dist/flows/decision.js +1 -1
- package/dist/flows/flow-classify.js +1 -1
- package/dist/flows/flow-replay-run.js +8 -1
- package/dist/flows/flow-tools.js +21 -5
- package/dist/flows/heal-run.js +1 -1
- package/dist/flows/replay.js +3 -0
- package/dist/honesty/accepted-write.d.ts +3 -0
- package/dist/honesty/accepted-write.js +8 -3
- package/dist/honesty/already-true.js +15 -0
- package/dist/honesty/blind-spots.js +10 -2
- package/dist/honesty/honesty.d.ts +11 -0
- package/dist/honesty/honesty.js +1 -0
- package/dist/honesty/uncaptured-bodies.d.ts +4 -2
- package/dist/honesty/uncaptured-bodies.js +9 -2
- package/dist/honesty/unread-outcome.d.ts +8 -0
- package/dist/honesty/unread-outcome.js +36 -9
- package/dist/honesty/unsettled.d.ts +49 -0
- package/dist/honesty/unsettled.js +82 -0
- package/dist/honesty/verified.d.ts +36 -3
- package/dist/honesty/verified.js +54 -11
- package/dist/http-server.d.ts +19 -0
- package/dist/http-server.js +202 -4
- package/dist/impact/impact-recorder.d.ts +23 -0
- package/dist/impact/impact-recorder.js +76 -0
- package/dist/impact/impact-store.d.ts +47 -0
- package/dist/impact/impact-store.js +171 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +47 -10
- package/dist/init/agent-rules.d.ts +36 -0
- package/dist/init/agent-rules.js +132 -22
- package/dist/init/astro-patch.d.ts +2 -1
- package/dist/init/astro-patch.js +38 -6
- package/dist/init/capabilities.d.ts +22 -0
- package/dist/init/capabilities.js +76 -0
- package/dist/init/confirm.d.ts +114 -0
- package/dist/init/confirm.js +165 -0
- package/dist/init/connect-steps.d.ts +2 -0
- package/dist/init/connect-steps.js +50 -0
- package/dist/init/cra.js +3 -2
- package/dist/init/csp-check.d.ts +38 -0
- package/dist/init/csp-check.js +92 -0
- package/dist/init/csp-doctor.d.ts +25 -0
- package/dist/init/csp-doctor.js +49 -0
- package/dist/init/cursor.js +6 -9
- package/dist/init/detect.d.ts +8 -0
- package/dist/init/detect.js +33 -3
- package/dist/init/mcp-clients.d.ts +1 -0
- package/dist/init/mcp-clients.js +51 -7
- package/dist/init/mcp.d.ts +15 -0
- package/dist/init/mcp.js +19 -0
- package/dist/init/non-js-project.js +29 -5
- package/dist/init/plan-framework.d.ts +10 -0
- package/dist/init/plan-framework.js +78 -16
- package/dist/init/plan.d.ts +47 -6
- package/dist/init/plan.js +228 -69
- package/dist/init/run.d.ts +24 -2
- package/dist/init/run.js +157 -45
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +54 -6
- package/dist/init/snippets.d.ts +45 -5
- package/dist/init/snippets.js +194 -42
- package/dist/init/vite-config.js +53 -5
- package/dist/input/network-mock-tools.d.ts +19 -0
- package/dist/input/network-mock-tools.js +37 -5
- package/dist/input/scroll-find.d.ts +6 -0
- package/dist/input/scroll-find.js +17 -2
- package/dist/input/scroll-tools.js +4 -0
- package/dist/journal/attach-journal.d.ts +9 -1
- package/dist/journal/attach-journal.js +13 -2
- package/dist/journal/journal-query.d.ts +13 -0
- package/dist/journal/journal-query.js +28 -3
- package/dist/journal/workspace-gitignore.d.ts +10 -0
- package/dist/journal/workspace-gitignore.js +59 -0
- package/dist/license/license.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +35 -30
- package/dist/mcp/mcp-proxy.js +306 -104
- package/dist/mcp/mcp.d.ts +10 -1
- package/dist/mcp/mcp.js +70 -34
- package/dist/mcp/proxy-handshake.d.ts +82 -1
- package/dist/mcp/proxy-handshake.js +109 -2
- package/dist/mcp/proxy-lifecycle.d.ts +11 -2
- package/dist/mcp/proxy-lifecycle.js +12 -3
- package/dist/mcp/proxy-log.d.ts +63 -0
- package/dist/mcp/proxy-log.js +132 -0
- package/dist/mcp/server-instructions.d.ts +17 -0
- package/dist/mcp/server-instructions.js +56 -0
- package/dist/mcp/shared-params.d.ts +27 -0
- package/dist/mcp/shared-params.js +36 -0
- package/dist/pool/browser-pool.d.ts +22 -2
- package/dist/pool/browser-pool.js +44 -4
- package/dist/project/learned-routes.d.ts +24 -0
- package/dist/project/learned-routes.js +63 -0
- package/dist/project/project-store.d.ts +6 -0
- package/dist/project/project-store.js +25 -1
- package/dist/project/reticle-dir.d.ts +2 -0
- package/dist/project/reticle-dir.js +1 -0
- package/dist/session/connection-memory.d.ts +59 -0
- package/dist/session/connection-memory.js +108 -0
- package/dist/session/dev-command.d.ts +26 -0
- package/dist/session/dev-command.js +89 -0
- package/dist/session/dev-server-probe.d.ts +68 -0
- package/dist/session/dev-server-probe.js +102 -7
- package/dist/session/last-act.d.ts +26 -0
- package/dist/session/last-act.js +35 -1
- package/dist/session/live-control-tools.js +42 -5
- package/dist/session/no-session-diagnosis.d.ts +67 -2
- package/dist/session/no-session-diagnosis.js +236 -27
- package/dist/session/no-session-next-action.d.ts +46 -0
- package/dist/session/no-session-next-action.js +85 -0
- package/dist/session/no-session-watch.d.ts +40 -1
- package/dist/session/no-session-watch.js +174 -8
- package/dist/session/pending-commands.d.ts +12 -0
- package/dist/session/pending-commands.js +13 -1
- package/dist/session/ref-provenance.d.ts +54 -0
- package/dist/session/ref-provenance.js +61 -0
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +4 -0
- package/dist/session/session-manager.js +35 -5
- package/dist/session/session.d.ts +10 -1
- package/dist/session/session.js +146 -5
- package/dist/telemetry/app-instrumented.d.ts +8 -0
- package/dist/telemetry/app-instrumented.js +10 -0
- package/dist/telemetry/automation-hint.d.ts +17 -0
- package/dist/telemetry/automation-hint.js +71 -0
- package/dist/telemetry/bug-found.d.ts +15 -17
- package/dist/telemetry/bug-found.js +52 -21
- package/dist/telemetry/cli-telemetry.js +7 -1
- package/dist/telemetry/daemon-telemetry.js +24 -0
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/init-telemetry.js +9 -1
- package/dist/telemetry/install-source.d.ts +56 -0
- package/dist/telemetry/install-source.js +74 -0
- package/dist/telemetry/instrumentation-stall.d.ts +50 -0
- package/dist/telemetry/instrumentation-stall.js +90 -0
- package/dist/telemetry/run-telemetry.js +4 -1
- package/dist/telemetry/session-metrics.d.ts +8 -0
- package/dist/telemetry/session-metrics.js +15 -0
- package/dist/telemetry/telemetry.d.ts +7 -1
- package/dist/telemetry/telemetry.js +26 -2
- package/dist/telemetry/tool-refused.d.ts +19 -0
- package/dist/telemetry/tool-refused.js +55 -0
- package/dist/telemetry/verification-of.js +46 -7
- package/dist/temp-dir.d.ts +27 -0
- package/dist/temp-dir.js +46 -0
- package/dist/tools/act-tools.js +209 -28
- package/dist/tools/assert-grade.d.ts +1 -1
- package/dist/tools/assert-grade.js +27 -5
- package/dist/tools/assert-verdict.js +49 -7
- package/dist/tools/browser-tools.js +10 -0
- package/dist/tools/contract-tools.js +4 -0
- package/dist/tools/dynamic-tools.js +16 -7
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +73 -9
- package/dist/tools/feedback-invite.js +1 -1
- package/dist/tools/invoke-tool.js +159 -26
- package/dist/tools/lease-hint.d.ts +30 -8
- package/dist/tools/lease-hint.js +65 -14
- package/dist/tools/lease-tools.d.ts +40 -1
- package/dist/tools/lease-tools.js +181 -83
- package/dist/tools/navigate-result.js +1 -1
- package/dist/tools/observe-tools.js +10 -4
- package/dist/tools/observed-nothing.js +1 -1
- package/dist/tools/react-grace.d.ts +52 -0
- package/dist/tools/react-grace.js +90 -0
- package/dist/tools/read-tools.js +67 -5
- package/dist/tools/resolve-target.d.ts +33 -0
- package/dist/tools/resolve-target.js +53 -0
- package/dist/tools/settle-in-flight.d.ts +94 -0
- package/dist/tools/settle-in-flight.js +146 -0
- package/dist/tools/snapshot-delta.d.ts +10 -0
- package/dist/tools/snapshot-delta.js +194 -9
- package/dist/tools/tool-kit.d.ts +8 -0
- package/dist/tools/tool-surface.d.ts +60 -0
- package/dist/tools/tool-surface.js +86 -1
- package/dist/tools/tools.js +61 -8
- package/dist/tools/unadvertised-help.js +9 -6
- package/docs/README.md +55 -11
- package/docs/actions.mdx +120 -0
- package/docs/agent-cheatsheet.md +95 -29
- package/docs/architecture.md +52 -35
- package/docs/benchmarks.md +127 -75
- package/docs/best-practices.mdx +216 -0
- package/docs/capabilities.mdx +534 -0
- package/docs/cli/affected.mdx +53 -0
- package/docs/cli/capsules.mdx +70 -0
- package/docs/cli/cloud.mdx +153 -0
- package/docs/cli/doctor.mdx +109 -0
- package/docs/cli/drive.mdx +74 -0
- package/docs/cli/feedback.mdx +67 -0
- package/docs/cli/gate.mdx +64 -0
- package/docs/cli/help.mdx +94 -0
- package/docs/cli/hunt.mdx +73 -0
- package/docs/cli/identify.mdx +76 -0
- package/docs/cli/init.mdx +121 -0
- package/docs/cli/kill.mdx +91 -0
- package/docs/cli/license.mdx +48 -0
- package/docs/cli/mcp.mdx +61 -0
- package/docs/cli/open.mdx +74 -0
- package/docs/cli/restart.mdx +60 -0
- package/docs/cli/rollback.mdx +35 -0
- package/docs/cli/serve.mdx +69 -0
- package/docs/cli/status.mdx +88 -0
- package/docs/cli/stop.mdx +64 -0
- package/docs/cli/telemetry.mdx +57 -0
- package/docs/cli/update.mdx +52 -0
- package/docs/cli/verify.mdx +76 -0
- package/docs/cli/version.mdx +39 -0
- package/docs/cli/watch.mdx +62 -0
- package/docs/cli.mdx +241 -0
- package/docs/debugging.md +16 -10
- package/docs/deploy-checks.md +45 -12
- package/docs/desktop.mdx +384 -0
- package/docs/docs.json +364 -0
- package/docs/enterprise.md +22 -16
- package/docs/faq.mdx +102 -0
- package/docs/fixtures.md +13 -7
- package/docs/flows.md +29 -23
- package/docs/for-agents.md +71 -0
- package/docs/frameworks.mdx +238 -0
- package/docs/gate-plan.md +48 -42
- package/docs/gates.md +28 -22
- package/docs/getting-started.md +129 -77
- package/docs/human-control.md +24 -18
- package/docs/index.mdx +143 -0
- package/docs/install-agentic.mdx +224 -0
- package/docs/install-manual.mdx +196 -0
- package/docs/instrumentation.mdx +238 -0
- package/docs/integration-patterns.md +41 -35
- package/docs/local-registry.md +19 -13
- package/docs/multi-agent-testing.md +19 -13
- package/docs/packages/babel-plugin.mdx +79 -0
- package/docs/packages/browser.mdx +111 -0
- package/docs/packages/core.mdx +116 -0
- package/docs/packages/electron.mdx +85 -0
- package/docs/packages/eslint-plugin.mdx +86 -0
- package/docs/packages/next.mdx +89 -0
- package/docs/packages/react.mdx +91 -0
- package/docs/packages/server.mdx +98 -0
- package/docs/packages/tauri.mdx +63 -0
- package/docs/packages/test.mdx +119 -0
- package/docs/packages/vite-plugin.mdx +94 -0
- package/docs/packages.mdx +218 -0
- package/docs/platform-integration.md +35 -26
- package/docs/predicates.mdx +248 -0
- package/docs/quickstart.mdx +257 -0
- package/docs/recipes.mdx +282 -0
- package/docs/skill-file.mdx +107 -0
- package/docs/state-management.mdx +199 -0
- package/docs/style.css +14 -0
- package/docs/system-map.md +50 -33
- package/docs/telemetry-contract.md +106 -52
- package/docs/telemetry-events.mdx +475 -0
- package/docs/telemetry.md +63 -55
- package/docs/testing.md +44 -6
- package/docs/token-efficiency.md +37 -27
- package/docs/tools-act-and-wait.mdx +173 -0
- package/docs/tools-act-sequence.mdx +137 -0
- package/docs/tools-act.mdx +95 -0
- package/docs/tools-assert.mdx +129 -0
- package/docs/tools-console.mdx +63 -0
- package/docs/tools-inspect.mdx +88 -0
- package/docs/tools-navigate.mdx +99 -0
- package/docs/tools-network.mdx +119 -0
- package/docs/tools-observe.mdx +145 -0
- package/docs/tools-overview.mdx +160 -0
- package/docs/tools-query.mdx +126 -0
- package/docs/tools-session-and-feedback.mdx +112 -0
- package/docs/tools-sessions.mdx +94 -0
- package/docs/tools-snapshot.mdx +129 -0
- package/docs/tools-state.mdx +104 -0
- package/docs/tools-tools-and-run.mdx +156 -0
- package/docs/tools-wait-for.mdx +85 -0
- package/docs/troubleshooting.mdx +263 -0
- package/docs/usage.md +209 -165
- package/docs/vs-chrome-devtools-mcp.mdx +123 -0
- package/docs/vs-playwright-mcp.mdx +125 -0
- package/docs/vs-screenshots.mdx +137 -0
- package/docs/what-is-reticle.mdx +63 -0
- package/docs/why-reticle.mdx +109 -0
- package/package.json +5 -5
- package/docs/desktop-apps.md +0 -256
- package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
- package/docs/matrix/2.5.0/codex-machine.json +0 -20
- package/docs/matrix/2.5.0/cursor-machine.json +0 -22
- package/docs/matrix/2.5.0/gemini-machine.json +0 -22
- package/docs/matrix/2.5.0/opencode-machine.json +0 -22
- package/docs/matrix/2.5.0/vscode-machine.json +0 -22
- package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
- package/docs/matrix/MATRIX.md +0 -26
- package/docs/matrix/README.md +0 -59
- package/docs/mint.json +0 -61
package/dist/cli/cli-doctor.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { ReticleEnv } from '@reticlehq/core';
|
|
4
4
|
import { readPid, reticleStateHome } from '../daemon/daemon.js';
|
|
@@ -6,37 +6,22 @@ import { PortPresence, probePresence } from '../daemon/port-presence.js';
|
|
|
6
6
|
import { probeDaemon } from '../mcp/mcp-proxy.js';
|
|
7
7
|
import { fetchStatus } from './cli-launch.js';
|
|
8
8
|
import { daemonLine } from './doctor-daemon-line.js';
|
|
9
|
-
import {
|
|
9
|
+
import { projectWiringLine } from './doctor-project-line.js';
|
|
10
|
+
import { hasProjectConnectedBefore } from '../session/connection-memory.js';
|
|
11
|
+
import { sessionsLine } from './doctor-sessions-line.js';
|
|
12
|
+
import { captureLookup, describeForeignHolder, findPortHolder } from './port-holder.js';
|
|
10
13
|
import { chromiumHint, probeChromium } from './chromium-hint.js';
|
|
11
|
-
import { spawnSync } from 'node:child_process';
|
|
12
14
|
import { SERVER_VERSION } from '../version/server-version.js';
|
|
13
15
|
import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
|
|
14
16
|
import { diagnoseDesktop, isDesktopProject } from '../init/desktop-doctor.js';
|
|
17
|
+
import { diagnoseWebCsp } from '../init/csp-doctor.js';
|
|
18
|
+
import { RETICLE_CONFIG_BASENAME, diagnosePortMismatch, readProjectId, readProjectPort, } from './cli-port.js';
|
|
19
|
+
import { DoctorRow, doctorRow } from './doctor-rows.js';
|
|
15
20
|
/**
|
|
16
21
|
* `reticle doctor` — collapse the ~6 independent first-run failure modes into one command. Checks the
|
|
17
22
|
* Chromium install (the #1 silent failure), whether a daemon is up on the resolved bridge port, and
|
|
18
23
|
* reminds the user which port the app must dial. Human-readable to stdout (not the JSON log).
|
|
19
24
|
*/
|
|
20
|
-
/**
|
|
21
|
-
* Run a lookup and return its stdout, or null if it could not run.
|
|
22
|
-
*
|
|
23
|
-
* Null on ANY failure — no lsof (every Windows user, a slim container), a non-zero exit, a throw.
|
|
24
|
-
* This is a diagnostic nicety inside the command people run when things are already broken; it must
|
|
25
|
-
* never be the reason `doctor` fails. `describeForeignHolder` handles the null by saying it could
|
|
26
|
-
* not identify the holder and naming our recorded pid, rather than asserting the holder is not ours
|
|
27
|
-
* — a claim it has no evidence for, and one that was wrong for every Windows user.
|
|
28
|
-
*/
|
|
29
|
-
function runCapture(command, args) {
|
|
30
|
-
try {
|
|
31
|
-
const result = spawnSync(command, [...args], { encoding: 'utf8', timeout: LOOKUP_TIMEOUT_MS });
|
|
32
|
-
return 0 === result.status && 'string' === typeof result.stdout ? result.stdout : null;
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
/** Bound on the holder lookup. A hung `lsof` must not hang the command that diagnoses hangs. */
|
|
39
|
-
const LOOKUP_TIMEOUT_MS = 2_000;
|
|
40
25
|
/** Narrow the `/status` payload to the two fields the daemon line reads. */
|
|
41
26
|
function asIdentity(payload) {
|
|
42
27
|
if (typeof payload !== 'object' || null === payload)
|
|
@@ -60,8 +45,11 @@ export async function handleDoctor(port) {
|
|
|
60
45
|
process.stdout.write(`${s}\n`);
|
|
61
46
|
};
|
|
62
47
|
line('reticle doctor');
|
|
63
|
-
line(
|
|
64
|
-
|
|
48
|
+
line(doctorRow(DoctorRow.NODE, process.version));
|
|
49
|
+
// Filled in only on the daemon branch — there is nothing to ask when no daemon is answering, and
|
|
50
|
+
// the branches below already say so in their own terms.
|
|
51
|
+
let sessions;
|
|
52
|
+
line(doctorRow(DoctorRow.CHROMIUM, chromiumHint(await probeChromium())));
|
|
65
53
|
// Ask the PORT, not just the pid file. "not running on :4400" has been printed about a port that
|
|
66
54
|
// was demonstrably occupied, which sends the reader to start a daemon that cannot bind. The three
|
|
67
55
|
// states are genuinely different problems with different fixes, so doctor names which one it is.
|
|
@@ -71,14 +59,19 @@ export async function handleDoctor(port) {
|
|
|
71
59
|
// Name WHICH daemon. The /status payload already carries version + contract, and doctor was
|
|
72
60
|
// throwing both away — while skew is invisible everywhere else, reaching the agent as a bare
|
|
73
61
|
// -32000 naming no version. This is the command a human runs at exactly that moment.
|
|
74
|
-
const
|
|
62
|
+
const payload = await fetchStatus(port);
|
|
63
|
+
const status = asIdentity(payload);
|
|
75
64
|
const built = daemonLine(port, pid, status, {
|
|
76
65
|
version: SERVER_VERSION,
|
|
77
66
|
contract: CONTRACT_FINGERPRINT,
|
|
78
67
|
});
|
|
79
68
|
line(built.text);
|
|
80
69
|
if (built.skew !== undefined)
|
|
81
|
-
line(
|
|
70
|
+
line(doctorRow(DoctorRow.VERSION, `✗ ${built.skew}`));
|
|
71
|
+
// The same payload already carries whether anything has CONNECTED, and doctor was reading
|
|
72
|
+
// `version` off it and dropping the rest. Without this the command is silent on the one step the
|
|
73
|
+
// funnel stalls on: wired correctly, daemon up, and no page has ever dialled in.
|
|
74
|
+
sessions = sessionsLine('object' === typeof payload && null !== payload ? payload : {});
|
|
82
75
|
}
|
|
83
76
|
else if (presence === PortPresence.FOREIGN) {
|
|
84
77
|
// Name the holder when we can. `doctor` exists for exactly this moment, and "another process"
|
|
@@ -86,18 +79,43 @@ export async function handleDoctor(port) {
|
|
|
86
79
|
// pipeline that also kills the agent's own MCP proxy.
|
|
87
80
|
// `pid` is what our pid file recorded for this port. When it matches the process actually
|
|
88
81
|
// holding it, this is our OWN daemon wedged, not a stranger — and the fix is different.
|
|
89
|
-
line(
|
|
82
|
+
line(doctorRow(DoctorRow.DAEMON, `✗ ${describeForeignHolder(port, findPortHolder(port, captureLookup), pid)}`));
|
|
90
83
|
}
|
|
91
84
|
else {
|
|
92
|
-
line(
|
|
85
|
+
line(doctorRow(DoctorRow.DAEMON, `✗ not running on :${port} — your agent runs \`reticle mcp\` (or \`reticle serve\`)`));
|
|
93
86
|
}
|
|
94
|
-
|
|
87
|
+
if (sessions !== undefined)
|
|
88
|
+
line(sessions.text);
|
|
89
|
+
line(doctorRow(DoctorRow.BRIDGE_PORT, `${port} (your app must dial THIS port, not your dev-server port)`));
|
|
90
|
+
const projectPort = readProjectPort(process.cwd());
|
|
91
|
+
const mismatch = diagnosePortMismatch(port, projectPort);
|
|
92
|
+
if (mismatch !== undefined)
|
|
93
|
+
line(doctorRow(DoctorRow.PORT_CHECK, `✗ ${mismatch}`));
|
|
94
|
+
// The check this checklist was missing: is the APP wired, not just the tools. Everything above can
|
|
95
|
+
// be green in a project that has never been through `init`, and that combination is precisely the
|
|
96
|
+
// one `doctor` gets run to explain.
|
|
97
|
+
const projectId = readProjectId(process.cwd());
|
|
98
|
+
line(projectWiringLine({
|
|
99
|
+
projectId,
|
|
100
|
+
previouslyConnected: hasProjectConnectedBefore(reticleStateHome(), port, projectId),
|
|
101
|
+
// An absent config and a corrupt one both read back as "no id"; only this tells them apart,
|
|
102
|
+
// and telling somebody a file is missing while it sits in front of them is its own dead end.
|
|
103
|
+
configPresent: existsSync(join(process.cwd(), RETICLE_CONFIG_BASENAME)),
|
|
104
|
+
}));
|
|
95
105
|
// Where to LOOK when something is wrong. The daemon has always written a structured log here and
|
|
96
106
|
// nothing ever said so, so the first move in every investigation was reading source instead of
|
|
97
107
|
// reading the log. `RETICLE_TRACE=1` turns the same stream into a per-stage trace — see
|
|
98
108
|
// docs/debugging.md.
|
|
99
|
-
line(
|
|
100
|
-
line(
|
|
109
|
+
line(doctorRow(DoctorRow.DAEMON_LOG, join(reticleStateHome(), `daemon-${String(port)}.log`)));
|
|
110
|
+
line(doctorRow(DoctorRow.TRACING, `${ReticleEnv.TRACE}=1 on the daemon for per-stage timings in that log`));
|
|
111
|
+
// Below the checklist rather than inline: it is a paragraph, and a paragraph in the middle of a
|
|
112
|
+
// column of one-line checks buries the checks under it. This is the daemon's own no-session
|
|
113
|
+
// diagnosis, the same one an agent gets from an empty reticle_sessions.
|
|
114
|
+
if (sessions?.why !== undefined) {
|
|
115
|
+
line('');
|
|
116
|
+
line(' nothing has connected. What the daemon can tell from here:');
|
|
117
|
+
line(` ${sessions.why}`);
|
|
118
|
+
}
|
|
101
119
|
// Desktop setup RCA. Every one of these fails SILENTLY — a Tauri app with the default CSP runs
|
|
102
120
|
// perfectly and never connects; an Electron app without the preload line reports zero network
|
|
103
121
|
// activity forever, which reads as "makes no backend calls" rather than "you are blind to them".
|
|
@@ -109,10 +127,23 @@ export async function handleDoctor(port) {
|
|
|
109
127
|
return undefined;
|
|
110
128
|
}
|
|
111
129
|
};
|
|
130
|
+
// The web sibling of the desktop findings below: a `connect-src` that excludes the bridge makes
|
|
131
|
+
// the browser refuse the WebSocket and report it in ITS console only, so every check above passes
|
|
132
|
+
// at an app that can never connect. Named, with the exact text to paste.
|
|
133
|
+
const csp = diagnoseWebCsp(readProjectFile, port);
|
|
134
|
+
if (csp.length > 0) {
|
|
135
|
+
line('');
|
|
136
|
+
line(` csp ✗ a Content-Security-Policy is blocking the Reticle bridge:`);
|
|
137
|
+
for (const finding of csp) {
|
|
138
|
+
line(` ${finding.file}`);
|
|
139
|
+
line(` ${finding.problem}`);
|
|
140
|
+
line(` fix: ${finding.fix}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
112
143
|
const desktop = diagnoseDesktop(readProjectFile, port);
|
|
113
144
|
if (desktop.length > 0) {
|
|
114
145
|
line('');
|
|
115
|
-
line(
|
|
146
|
+
line(doctorRow(DoctorRow.DESKTOP, `✗ ${String(desktop.length)} issue(s) — the app will look fine and not work:`));
|
|
116
147
|
for (const finding of desktop) {
|
|
117
148
|
line(` ${finding.file}`);
|
|
118
149
|
line(` ${finding.problem}`);
|
|
@@ -122,6 +153,6 @@ export async function handleDoctor(port) {
|
|
|
122
153
|
else if (isDesktopProject(readProjectFile)) {
|
|
123
154
|
// Say so explicitly. Silence would read as "not checked", which is the same ambiguity the
|
|
124
155
|
// findings above exist to remove.
|
|
125
|
-
line('
|
|
156
|
+
line(doctorRow(DoctorRow.DESKTOP, '✓ desktop wiring looks right'));
|
|
126
157
|
}
|
|
127
158
|
}
|
|
@@ -19,7 +19,7 @@ import { formatBuddyStatus } from '../flows/buddy-status.js';
|
|
|
19
19
|
import { CapsuleStore } from '../capsule/capsule-store.js';
|
|
20
20
|
import { AssertionTiersStore } from '../flows/assertion-tiers-store.js';
|
|
21
21
|
import { detectDowngrades } from '../flows/assertion-integrity.js';
|
|
22
|
-
import { computeCoverage } from '../flows/coverage.js';
|
|
22
|
+
import { computeCoverage, flowCoverageReport } from '../flows/coverage.js';
|
|
23
23
|
import { createWatchBatcher } from '../flows/watch-batcher.js';
|
|
24
24
|
import { watch } from 'node:fs';
|
|
25
25
|
import { log } from '../log.js';
|
|
@@ -184,19 +184,19 @@ export async function handleGate(files, since) {
|
|
|
184
184
|
const result = gateDecision({ affected, passing, flaky, downgraded, deleted });
|
|
185
185
|
// Verified-surface coverage over flows: how much of the saved suite this run actually exercised.
|
|
186
186
|
const coverage = computeCoverage({ testids: [], signals: [], flows: allFlows.map((f) => f.name) }, { testids: [], signals: [], flows: passing });
|
|
187
|
+
// A gate over an empty suite has not passed — it had nothing to check. Same rule the suite
|
|
188
|
+
// verdict already applies to `reticle verify`; see flowCoverageReport.
|
|
189
|
+
const flowCoverage = flowCoverageReport(coverage.flows);
|
|
190
|
+
const pass = result.pass && flowCoverage.outcome === undefined;
|
|
187
191
|
log('reticle_gate', {
|
|
188
|
-
pass
|
|
192
|
+
pass,
|
|
189
193
|
uncovered: result.uncovered,
|
|
190
194
|
quarantined: result.quarantined,
|
|
191
195
|
...(result.downgraded.length > 0 ? { downgraded: result.downgraded } : {}),
|
|
192
196
|
...(result.deleted.length > 0 ? { deletedCoverage: result.deleted } : {}),
|
|
193
|
-
coverage:
|
|
194
|
-
pct: coverage.flows.pct,
|
|
195
|
-
covered: coverage.flows.covered,
|
|
196
|
-
total: coverage.flows.total,
|
|
197
|
-
},
|
|
197
|
+
coverage: flowCoverage,
|
|
198
198
|
});
|
|
199
|
-
if (!
|
|
199
|
+
if (!pass)
|
|
200
200
|
process.exitCode = 1;
|
|
201
201
|
}
|
|
202
202
|
catch (error) {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle kill` — free the bridge port without taking the agent's transport with it.
|
|
3
|
+
*
|
|
4
|
+
* The obvious incantation, `lsof -ti tcp:4400 | xargs kill -9`, also kills `reticle mcp`: the proxy
|
|
5
|
+
* holds a CLIENT socket on that port, `-ti` does not filter to listeners, and so the one command
|
|
6
|
+
* people reach for when the bridge is stuck destroys the channel they were trying to fix. What
|
|
7
|
+
* follows is worse than an error — the client gets no reply at all, and nothing reaches the proxy log
|
|
8
|
+
* because the process that writes it is the one that died. That is the mechanism behind most
|
|
9
|
+
* "my MCP disconnected" reports (#114, #110).
|
|
10
|
+
*
|
|
11
|
+
* The safe form was written down, and a documented incantation is a worse answer than a command: it
|
|
12
|
+
* has to be found, remembered, and typed correctly at the moment someone is already stuck. So this is
|
|
13
|
+
* the command, and it signals exactly one pid — the LISTENER.
|
|
14
|
+
*
|
|
15
|
+
* It is not a rename of `stop`. `stop` reads the pid file, which is the right source when the daemon
|
|
16
|
+
* we started is the daemon that is wedged, and no source at all when the pid file is stale, was never
|
|
17
|
+
* written, or belongs to a daemon from another checkout. `kill` asks the PORT, which is the thing the
|
|
18
|
+
* user actually wants freed, and falls back to the pid file only where the lookup cannot run.
|
|
19
|
+
*/
|
|
20
|
+
import { type PortHolder } from './port-holder.js';
|
|
21
|
+
/** What the plan says to do with the port. */
|
|
22
|
+
export declare const KillAction: {
|
|
23
|
+
readonly NOTHING: "nothing";
|
|
24
|
+
readonly KILL: "kill";
|
|
25
|
+
readonly REFUSE: "refuse";
|
|
26
|
+
};
|
|
27
|
+
export type KillAction = (typeof KillAction)[keyof typeof KillAction];
|
|
28
|
+
export interface KillPlan {
|
|
29
|
+
action: KillAction;
|
|
30
|
+
/** The single pid to signal. Present only for `kill`. */
|
|
31
|
+
pid?: number;
|
|
32
|
+
/** True when the target was not identifiable as Reticle's and `--force` overrode the refusal. */
|
|
33
|
+
forced: boolean;
|
|
34
|
+
/** False when the listener lookup could not run, so the pid came from our own records instead. */
|
|
35
|
+
identifiedListener: boolean;
|
|
36
|
+
/** Why we will not act, for `refuse`. */
|
|
37
|
+
reason?: string;
|
|
38
|
+
holder?: PortHolder;
|
|
39
|
+
}
|
|
40
|
+
export declare const FORCE_FLAG = "--force";
|
|
41
|
+
/**
|
|
42
|
+
* Decide what to signal. Pure.
|
|
43
|
+
*
|
|
44
|
+
* `answersStatus` is the strongest evidence available and deliberately outranks the pid file: a
|
|
45
|
+
* daemon started from another checkout owns the port, is a Reticle daemon, and appears nowhere in our
|
|
46
|
+
* records. Refusing it would leave the port held by exactly the process this command exists to clear.
|
|
47
|
+
*/
|
|
48
|
+
export declare function planKill(input: {
|
|
49
|
+
/** The listener on the port, from a `-sTCP:LISTEN` lookup. Null when there is none, or no `lsof`. */
|
|
50
|
+
listener: PortHolder | null;
|
|
51
|
+
/** The pid our records hold for this port, or null when there is none or it is dead. */
|
|
52
|
+
recordedPid: number | null;
|
|
53
|
+
/** Whether the port answers `/status`, i.e. whatever holds it is a Reticle daemon. */
|
|
54
|
+
answersStatus: boolean;
|
|
55
|
+
force: boolean;
|
|
56
|
+
}): KillPlan;
|
|
57
|
+
/** `reticle kill` — free the port. Resolves to whether the port is now free. */
|
|
58
|
+
export declare function runKill(port: number, force: boolean): Promise<boolean>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle kill` — free the bridge port without taking the agent's transport with it.
|
|
3
|
+
*
|
|
4
|
+
* The obvious incantation, `lsof -ti tcp:4400 | xargs kill -9`, also kills `reticle mcp`: the proxy
|
|
5
|
+
* holds a CLIENT socket on that port, `-ti` does not filter to listeners, and so the one command
|
|
6
|
+
* people reach for when the bridge is stuck destroys the channel they were trying to fix. What
|
|
7
|
+
* follows is worse than an error — the client gets no reply at all, and nothing reaches the proxy log
|
|
8
|
+
* because the process that writes it is the one that died. That is the mechanism behind most
|
|
9
|
+
* "my MCP disconnected" reports (#114, #110).
|
|
10
|
+
*
|
|
11
|
+
* The safe form was written down, and a documented incantation is a worse answer than a command: it
|
|
12
|
+
* has to be found, remembered, and typed correctly at the moment someone is already stuck. So this is
|
|
13
|
+
* the command, and it signals exactly one pid — the LISTENER.
|
|
14
|
+
*
|
|
15
|
+
* It is not a rename of `stop`. `stop` reads the pid file, which is the right source when the daemon
|
|
16
|
+
* we started is the daemon that is wedged, and no source at all when the pid file is stale, was never
|
|
17
|
+
* written, or belongs to a daemon from another checkout. `kill` asks the PORT, which is the thing the
|
|
18
|
+
* user actually wants freed, and falls back to the pid file only where the lookup cannot run.
|
|
19
|
+
*/
|
|
20
|
+
import { PortPresence, probePresence } from '../daemon/port-presence.js';
|
|
21
|
+
import { isAlive, readPid, removePid } from '../daemon/daemon.js';
|
|
22
|
+
import { probeDaemon } from '../mcp/mcp-proxy.js';
|
|
23
|
+
import { log } from '../log.js';
|
|
24
|
+
import { fetchStatus } from './cli-launch.js';
|
|
25
|
+
import { captureLookup, findPortHolder } from './port-holder.js';
|
|
26
|
+
/** What the plan says to do with the port. */
|
|
27
|
+
export const KillAction = {
|
|
28
|
+
NOTHING: 'nothing',
|
|
29
|
+
KILL: 'kill',
|
|
30
|
+
REFUSE: 'refuse',
|
|
31
|
+
};
|
|
32
|
+
export const FORCE_FLAG = '--force';
|
|
33
|
+
/**
|
|
34
|
+
* Decide what to signal. Pure.
|
|
35
|
+
*
|
|
36
|
+
* `answersStatus` is the strongest evidence available and deliberately outranks the pid file: a
|
|
37
|
+
* daemon started from another checkout owns the port, is a Reticle daemon, and appears nowhere in our
|
|
38
|
+
* records. Refusing it would leave the port held by exactly the process this command exists to clear.
|
|
39
|
+
*/
|
|
40
|
+
export function planKill(input) {
|
|
41
|
+
const { listener, recordedPid, answersStatus, force } = input;
|
|
42
|
+
if (null === listener) {
|
|
43
|
+
// No listener identified. On Windows and in slim containers that means the lookup could not run
|
|
44
|
+
// rather than that the port is free, so the recorded pid is the best target we have — and the
|
|
45
|
+
// report says which of the two it is rather than implying a lookup we never made.
|
|
46
|
+
if (null === recordedPid)
|
|
47
|
+
return { action: KillAction.NOTHING, forced: false, identifiedListener: false };
|
|
48
|
+
return { action: KillAction.KILL, pid: recordedPid, forced: false, identifiedListener: false };
|
|
49
|
+
}
|
|
50
|
+
const ours = answersStatus || listener.pid === recordedPid;
|
|
51
|
+
if (ours || force) {
|
|
52
|
+
return {
|
|
53
|
+
action: KillAction.KILL,
|
|
54
|
+
pid: listener.pid,
|
|
55
|
+
forced: !ours,
|
|
56
|
+
identifiedListener: true,
|
|
57
|
+
holder: listener,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
action: KillAction.REFUSE,
|
|
62
|
+
forced: false,
|
|
63
|
+
identifiedListener: true,
|
|
64
|
+
holder: listener,
|
|
65
|
+
reason: `pid ${String(listener.pid)} ("${listener.command}") is listening, and it is not a Reticle ` +
|
|
66
|
+
`daemon — it never answered /status and it is not the pid Reticle recorded. Killing it would ` +
|
|
67
|
+
`be killing someone else's process. Run it again with ${FORCE_FLAG} if that is what you ` +
|
|
68
|
+
`meant, or start Reticle on another port.`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** How long the target gets to exit on SIGTERM before we stop asking politely. */
|
|
72
|
+
const GRACEFUL_KILL_MS = 5_000;
|
|
73
|
+
/** How long after SIGKILL before we accept the process is not ours to kill and say so. */
|
|
74
|
+
const FORCED_KILL_MS = 2_000;
|
|
75
|
+
const KILL_POLL_MS = 100;
|
|
76
|
+
/**
|
|
77
|
+
* Signal ONE pid and wait for it to go: SIGTERM, then SIGKILL, then give up and say it survived.
|
|
78
|
+
*
|
|
79
|
+
* The escalation is the reason this exists rather than a bare SIGTERM — there is a field report of a
|
|
80
|
+
* daemon that ignored SIGTERM until a human ran `kill -9` by hand, which is the moment the `lsof -ti`
|
|
81
|
+
* pipeline gets typed and the agent's proxy dies with the daemon.
|
|
82
|
+
*/
|
|
83
|
+
async function terminate(pid) {
|
|
84
|
+
const signal = (name) => {
|
|
85
|
+
try {
|
|
86
|
+
process.kill(pid, name);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Gone between the liveness check and here. The next poll reports it as gone.
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
signal('SIGTERM');
|
|
93
|
+
const started = Date.now();
|
|
94
|
+
let escalated = false;
|
|
95
|
+
for (;;) {
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, KILL_POLL_MS));
|
|
97
|
+
if (!isAlive(pid))
|
|
98
|
+
return { gone: true, escalated };
|
|
99
|
+
const waited = Date.now() - started;
|
|
100
|
+
if (!escalated && waited > GRACEFUL_KILL_MS) {
|
|
101
|
+
escalated = true;
|
|
102
|
+
signal('SIGKILL');
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (waited > GRACEFUL_KILL_MS + FORCED_KILL_MS)
|
|
106
|
+
return { gone: false, escalated };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* What was NOT touched, said out loud on every kill.
|
|
111
|
+
*
|
|
112
|
+
* The command exists because of a pipeline that killed more than the reader intended and never told
|
|
113
|
+
* them, so a report that lists only the casualty repeats half the mistake. An `reticle mcp` proxy on
|
|
114
|
+
* this port survives, and its own recovery from a dead daemon is good: it goes dormant and restarts
|
|
115
|
+
* one on the next request.
|
|
116
|
+
*/
|
|
117
|
+
const PROXY_SPARED_NOTE = 'only the listener was signalled. Any `reticle mcp` proxy on this port holds a client connection, ' +
|
|
118
|
+
'not the port, and was left alone — it goes dormant and starts a fresh daemon on the next tool call.';
|
|
119
|
+
/** `reticle kill` — free the port. Resolves to whether the port is now free. */
|
|
120
|
+
export async function runKill(port, force) {
|
|
121
|
+
const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
122
|
+
const recorded = readPid(port);
|
|
123
|
+
const plan = planKill({
|
|
124
|
+
listener: findPortHolder(port, captureLookup),
|
|
125
|
+
recordedPid: null !== recorded && isAlive(recorded) ? recorded : null,
|
|
126
|
+
answersStatus: presence === PortPresence.DAEMON,
|
|
127
|
+
force,
|
|
128
|
+
});
|
|
129
|
+
if (KillAction.NOTHING === plan.action) {
|
|
130
|
+
removePid(port);
|
|
131
|
+
log('reticle_kill_nothing_to_do', { port, presence });
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (KillAction.REFUSE === plan.action) {
|
|
135
|
+
log('reticle_kill_refused', { port, reason: plan.reason, holder: plan.holder?.command });
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const pid = plan.pid;
|
|
139
|
+
if (pid === undefined) {
|
|
140
|
+
// Unreachable by construction (planKill never returns KILL without a pid, and a test pins that),
|
|
141
|
+
// but the alternative to this branch is a non-null assertion, which this codebase does not use.
|
|
142
|
+
log('reticle_kill_nothing_to_do', { port, presence });
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
const { gone, escalated } = await terminate(pid);
|
|
146
|
+
if (gone)
|
|
147
|
+
removePid(port);
|
|
148
|
+
log(gone ? 'reticle_killed' : 'reticle_kill_survived', {
|
|
149
|
+
port,
|
|
150
|
+
pid,
|
|
151
|
+
escalated,
|
|
152
|
+
...(plan.forced ? { forced: true } : {}),
|
|
153
|
+
// Say where the pid came from. Without `lsof` this is the pid we recorded, not the pid we
|
|
154
|
+
// observed listening, and those are different claims.
|
|
155
|
+
listenerIdentified: plan.identifiedListener,
|
|
156
|
+
note: gone
|
|
157
|
+
? PROXY_SPARED_NOTE
|
|
158
|
+
: `pid ${String(pid)} survived SIGKILL, so it is not yours to kill (permissions) or is an ` +
|
|
159
|
+
`unkillable zombie. The port is still held. ${PROXY_SPARED_NOTE}`,
|
|
160
|
+
});
|
|
161
|
+
return gone;
|
|
162
|
+
}
|
package/dist/cli/cli-parse.d.ts
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { type ParsedFeedback } from './cli-parse-feedback.js';
|
|
8
8
|
export { AGENT_FLAG, BUG_FLAG, FEEDBACK_KINDS, KIND_FLAG, RATING_FLAG, } from './cli-parse-feedback.js';
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The lines below say `reticle <command>`, which is the bin this package installs and is correct
|
|
11
|
+
* once it is on PATH. Copied into `npx`, it is not: `npx reticle` resolves the PACKAGE named
|
|
12
|
+
* `reticle`, which belongs to somebody else, and npx will happily fetch and run it. The docs were
|
|
13
|
+
* telling readers to do exactly that on 110 lines before it was caught, so the invocation now leads
|
|
14
|
+
* the usage block rather than being a footnote somewhere else.
|
|
15
|
+
*/
|
|
16
|
+
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>] (wire Reticle into the project in this directory)\n --app picks WHICH app in a monorepo, when several are found\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> [--headed] [--timeout N] [--storage-state <file>] (one-shot: drive the URL, verify saved flows, exit 0=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 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> \"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 Cloud; runs/flows recorded on the dashboard):\n reticle login --email <e> [--code <c>] [--org <n>] (sign in: mails a code, then exchanges it)\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 (send local run artifacts to the dashboard)\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.";
|
|
10
17
|
/** The `reticle telemetry` sub-actions. Bare `reticle telemetry` means `status`. */
|
|
11
18
|
export declare const TelemetryAction: {
|
|
12
19
|
readonly STATUS: "status";
|
|
@@ -52,6 +59,14 @@ export type CliResult = {
|
|
|
52
59
|
kind: 'stop';
|
|
53
60
|
port: number;
|
|
54
61
|
quiet: boolean;
|
|
62
|
+
} | {
|
|
63
|
+
kind: 'kill';
|
|
64
|
+
port: number;
|
|
65
|
+
force: boolean;
|
|
66
|
+
} | {
|
|
67
|
+
kind: 'restart';
|
|
68
|
+
port: number;
|
|
69
|
+
force: boolean;
|
|
55
70
|
} | {
|
|
56
71
|
kind: 'status';
|
|
57
72
|
port: number;
|
package/dist/cli/cli-parse.js
CHANGED
|
@@ -5,36 +5,47 @@
|
|
|
5
5
|
* imports are unchanged.
|
|
6
6
|
*/
|
|
7
7
|
import { parseFeedbackArgs } from './cli-parse-feedback.js';
|
|
8
|
+
import { FORCE_FLAG } from './cli-kill.js';
|
|
8
9
|
// Re-exported so every existing importer of these flags is unaffected by the file split.
|
|
9
10
|
export { AGENT_FLAG, BUG_FLAG, FEEDBACK_KINDS, KIND_FLAG, RATING_FLAG, } from './cli-parse-feedback.js';
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The lines below say `reticle <command>`, which is the bin this package installs and is correct
|
|
13
|
+
* once it is on PATH. Copied into `npx`, it is not: `npx reticle` resolves the PACKAGE named
|
|
14
|
+
* `reticle`, which belongs to somebody else, and npx will happily fetch and run it. The docs were
|
|
15
|
+
* telling readers to do exactly that on 110 lines before it was caught, so the invocation now leads
|
|
16
|
+
* the usage block rather than being a footnote somewhere else.
|
|
17
|
+
*/
|
|
18
|
+
export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticle <command>\` once the bin is on your PATH)
|
|
19
|
+
|
|
11
20
|
reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>] (wire Reticle into the project in this directory)
|
|
12
21
|
--app picks WHICH app in a monorepo, when several are found
|
|
13
22
|
--no-mcp skips MORE than the server registration: also the agent rule files
|
|
14
23
|
(CLAUDE.md / AGENTS.md / .cursor) and the /reticle command, because all three
|
|
15
24
|
only make sense once the tools are reachable.
|
|
16
25
|
reticle serve [--port N] [--drive <url>] [--headless] [--http] [--http-port N] [--http-token T]
|
|
17
|
-
reticle stop [--port N] [--quiet]
|
|
26
|
+
reticle stop [--port N] [--quiet] (stop the daemon we started, by its recorded pid)
|
|
27
|
+
reticle kill [--port N] [--force] (free the port by its LISTENER, never the agent's mcp proxy)
|
|
28
|
+
reticle restart [--port N] [--force] (kill, then start a daemon and wait for a real bind)
|
|
18
29
|
reticle status [--port N]
|
|
19
|
-
reticle doctor [--port N] (diagnose setup: Chromium, daemon, port
|
|
30
|
+
reticle doctor [--port N] (one command to diagnose setup: Chromium, daemon, port)
|
|
20
31
|
reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)
|
|
21
32
|
reticle verify <url> [--headed] [--timeout N] [--storage-state <file>] (one-shot: drive the URL, verify saved flows, exit 0=pass)
|
|
22
33
|
reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)
|
|
23
34
|
reticle gate [--since <ref>] [file...] (exit non-zero unless passing artifacts cover the affected flows)
|
|
24
35
|
reticle watch [url] (on save, report which saved flows must re-verify)
|
|
25
|
-
reticle drive <url> [--headless] (foreground mode
|
|
26
|
-
reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy
|
|
36
|
+
reticle drive <url> [--headless] (foreground mode, for debugging)
|
|
37
|
+
reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy; auto-starts daemon if needed)
|
|
27
38
|
reticle update (install the latest server version and restart)
|
|
28
39
|
reticle rollback (restore the previous server version and restart)
|
|
29
40
|
reticle license (show enterprise license status: active | eval | missing)
|
|
30
|
-
reticle telemetry [status|enable|disable] (anonymous usage metrics
|
|
31
|
-
reticle feedback [--rating 1-5] [--bug] "message" (tell us what worked and what didn't
|
|
41
|
+
reticle telemetry [status|enable|disable] (anonymous usage metrics; status shows what's sent + the policy)
|
|
42
|
+
reticle feedback [--rating 1-5] [--bug] "message" (tell us what worked and what didn't; prints exactly what it sends)
|
|
32
43
|
reticle feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "message"
|
|
33
44
|
(agents: file from anywhere, including a setup that never finished)
|
|
34
45
|
reticle identify --context company|side_project|open_source|learning [--company N] [--email E] [--forget]
|
|
35
46
|
(OPT-IN: tell us who you are, e.g. for support or an enterprise trial)
|
|
36
47
|
|
|
37
|
-
Cloud (link this project to Reticle Cloud
|
|
48
|
+
Cloud (link this project to Reticle Cloud; runs/flows recorded on the dashboard):
|
|
38
49
|
reticle login --email <e> [--code <c>] [--org <n>] (sign in: mails a code, then exchanges it)
|
|
39
50
|
reticle link [--project <name|id>] (bind this repo: mints a scoped key, writes .reticle/cloud.json)
|
|
40
51
|
reticle whoami (who am I signed in as, and is this repo attached?)
|
|
@@ -44,10 +55,12 @@ Cloud (link this project to Reticle Cloud — runs/flows recorded on the dashboa
|
|
|
44
55
|
reticle runs | regression | share <runId> (read cloud state; regression exits 3 if any flow broke)
|
|
45
56
|
|
|
46
57
|
'drive' shows the browser; everything else is hidden (serve/mcp own the pool behind leases, replay
|
|
47
|
-
and the spec runner
|
|
58
|
+
and the spec runner: batch work). --headed opts any of them in, --headless hides drive, CI hides it.`;
|
|
48
59
|
const INIT_COMMAND = 'init';
|
|
49
60
|
const SERVE_COMMAND = 'serve';
|
|
50
61
|
const STOP_COMMAND = 'stop';
|
|
62
|
+
const KILL_COMMAND = 'kill';
|
|
63
|
+
const RESTART_COMMAND = 'restart';
|
|
51
64
|
const STATUS_COMMAND = 'status';
|
|
52
65
|
const OPEN_COMMAND = 'open';
|
|
53
66
|
const DRIVE_COMMAND = 'drive';
|
|
@@ -90,6 +103,8 @@ const KNOWN_COMMANDS = new Set([
|
|
|
90
103
|
INIT_COMMAND,
|
|
91
104
|
SERVE_COMMAND,
|
|
92
105
|
STOP_COMMAND,
|
|
106
|
+
KILL_COMMAND,
|
|
107
|
+
RESTART_COMMAND,
|
|
93
108
|
STATUS_COMMAND,
|
|
94
109
|
OPEN_COMMAND,
|
|
95
110
|
DRIVE_COMMAND,
|
|
@@ -463,6 +478,14 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
|
|
|
463
478
|
const quiet = rest.includes(QUIET_FLAG);
|
|
464
479
|
return { kind: 'stop', port, quiet };
|
|
465
480
|
}
|
|
481
|
+
case KILL_COMMAND: {
|
|
482
|
+
const port = parsePortFlag(rest, defaultPort);
|
|
483
|
+
return { kind: 'kill', port, force: rest.includes(FORCE_FLAG) };
|
|
484
|
+
}
|
|
485
|
+
case RESTART_COMMAND: {
|
|
486
|
+
const port = parsePortFlag(rest, defaultPort);
|
|
487
|
+
return { kind: 'restart', port, force: rest.includes(FORCE_FLAG) };
|
|
488
|
+
}
|
|
466
489
|
case STATUS_COMMAND: {
|
|
467
490
|
const port = parsePortFlag(rest, defaultPort);
|
|
468
491
|
return { kind: 'status', port };
|
package/dist/cli/cli-port.d.ts
CHANGED
|
@@ -7,6 +7,21 @@
|
|
|
7
7
|
* 3..reticle.json "port" field in the cwd ← per-project isolation
|
|
8
8
|
* 4. RETICLE_DEFAULT_PORT (4400)
|
|
9
9
|
*/
|
|
10
|
+
/** The project config `reticle init` writes. */
|
|
11
|
+
export declare const RETICLE_CONFIG_BASENAME = ".reticle.json";
|
|
12
|
+
/**
|
|
13
|
+
* Find the nearest `.reticle.json` at or above `cwd`, and return its parsed contents.
|
|
14
|
+
*
|
|
15
|
+
* Every reader below used to join the basename onto `cwd` and stop there, so a process whose
|
|
16
|
+
* working directory was one level away from the config behaved exactly like a project that had
|
|
17
|
+
* never been through `init`: no port, no projectId, no journal setting, and a diagnostic that told
|
|
18
|
+
* the user to run an install they had already run. That is the shape of several field reports at
|
|
19
|
+
* once — an app wired in a subdirectory, an agent running from a repo root, a worktree beside its
|
|
20
|
+
* main checkout — and they are all the same missing walk.
|
|
21
|
+
*
|
|
22
|
+
* Returns undefined when nothing is found, which keeps every caller's existing default intact.
|
|
23
|
+
*/
|
|
24
|
+
export declare function findProjectConfig(cwd: string): Record<string, unknown> | undefined;
|
|
10
25
|
/**
|
|
11
26
|
* Ports that belong to a DEV SERVER, not to Reticle's bridge. Next defaults to 3000, Vite to 5173,
|
|
12
27
|
* Astro to 4321, and so on.
|
|
@@ -33,6 +48,15 @@ export declare function readProjectPort(cwd: string): number | undefined;
|
|
|
33
48
|
* undefined if the file is absent/unreadable or has no non-empty string projectId.
|
|
34
49
|
*/
|
|
35
50
|
export declare function readProjectId(cwd: string): string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* The framework `reticle init` stamped into `.reticle.json`, when there is one.
|
|
53
|
+
*
|
|
54
|
+
* Read so the no-session diagnosis can RANK its causes instead of printing one static differential.
|
|
55
|
+
* Nuxt is the case that pays for this: it does not register a newly added plugin on HMR, so a dev
|
|
56
|
+
* server older than the wiring is its single most likely cause — `init` warns about it at install
|
|
57
|
+
* time and the hint the agent reads hours later never mentioned it.
|
|
58
|
+
*/
|
|
59
|
+
export declare function readProjectFramework(cwd: string): string | undefined;
|
|
36
60
|
/**
|
|
37
61
|
* Whether the durable causal journal is enabled. On by default (the journal IS the loop); off only via
|
|
38
62
|
* explicit opt-out — `.reticle.json` `"journal": false`, or `RETICLE_JOURNAL` set to `0`/`false`. The env
|
|
@@ -44,3 +68,11 @@ export declare function readJournalEnabled(cwd: string, env: string | undefined)
|
|
|
44
68
|
* Pass `portFlag` when the user explicitly supplied --port; pass `undefined` to fall through.
|
|
45
69
|
*/
|
|
46
70
|
export declare function resolvePort(portFlag: number | undefined, envPort: number | undefined, projectPort: number | undefined, defaultPort: number): number;
|
|
71
|
+
/**
|
|
72
|
+
* Detect a port mismatch between the daemon and the project's SDK configuration.
|
|
73
|
+
*
|
|
74
|
+
* Returns a diagnostic string when the daemon port differs from what `.reticle.json` declares.
|
|
75
|
+
* The SDK reads `.reticle.json` at build/connect time, so a mismatch means the app will dial a
|
|
76
|
+
* port nothing is listening on — the silent no-connect that #261 documents.
|
|
77
|
+
*/
|
|
78
|
+
export declare function diagnosePortMismatch(daemonPort: number, projectPort: number | undefined): string | undefined;
|