@reticlehq/server 2.12.0 → 2.13.1
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 +67 -109
- package/dist/bridge/auth-failure-reason.d.ts +6 -2
- package/dist/bridge/auth-failure-reason.js +15 -8
- package/dist/bridge/bridge.d.ts +18 -1
- package/dist/bridge/bridge.js +247 -164
- package/dist/bridge/pairing-token.d.ts +5 -0
- package/dist/bridge/pairing-token.js +28 -0
- package/dist/bridge/protocol-skew.d.ts +37 -0
- package/dist/bridge/protocol-skew.js +45 -0
- package/dist/capsule/capsule-store.d.ts +6 -6
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/cli/cli-doctor.js +31 -1
- package/dist/cli/cli-launch.d.ts +11 -1
- package/dist/cli/cli-launch.js +59 -10
- package/dist/cli/cli-parse.d.ts +15 -1
- package/dist/cli/cli-parse.js +212 -8
- package/dist/cli/cli-verify.d.ts +1 -0
- package/dist/cli/cli-verify.js +3 -2
- package/dist/cli/cloud-cli.js +428 -259
- package/dist/cli/cloud-keystore.d.ts +37 -0
- package/dist/cli/cloud-keystore.js +97 -0
- package/dist/cli/cloud-kit.d.ts +37 -0
- package/dist/cli/cloud-kit.js +121 -0
- package/dist/cli/cloud-login.d.ts +17 -0
- package/dist/cli/cloud-login.js +183 -0
- package/dist/cli/cloud-session.d.ts +48 -0
- package/dist/cli/cloud-session.js +53 -0
- package/dist/cli/config-discovery.js +1 -0
- package/dist/cli/daemon-start-options.d.ts +32 -0
- package/dist/cli/daemon-start-options.js +38 -0
- package/dist/cli/device-flow.d.ts +76 -0
- package/dist/cli/device-flow.js +56 -0
- package/dist/cli/doctor-rows.d.ts +9 -0
- package/dist/cli/doctor-rows.js +9 -0
- package/dist/cli/drive-attach.d.ts +13 -0
- package/dist/cli/drive-attach.js +18 -0
- package/dist/cli/drive-command.js +7 -3
- package/dist/cli/mcp-command.js +51 -20
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -0
- package/dist/cli/status-next-action.d.ts +14 -0
- package/dist/cli/status-next-action.js +4 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +134 -80
- package/dist/cloud/cloud-config.d.ts +22 -1
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +5 -5
- package/dist/cloud/cloud-sync.js +33 -2
- package/dist/cloud/sync-cycle.d.ts +110 -0
- package/dist/cloud/sync-cycle.js +270 -0
- package/dist/cloud/sync-daemon.d.ts +69 -0
- package/dist/cloud/sync-daemon.js +260 -0
- package/dist/cloud/sync-disk.d.ts +9 -0
- package/dist/cloud/sync-disk.js +191 -0
- package/dist/cloud/sync-hash.d.ts +2 -0
- package/dist/cloud/sync-hash.js +35 -0
- package/dist/crawl/crawl-tools.js +5 -3
- package/dist/crawl/crawl.d.ts +3 -0
- package/dist/crawl/crawl.js +5 -0
- package/dist/daemon/daemon-resolve.d.ts +43 -0
- package/dist/daemon/daemon-resolve.js +69 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +18 -0
- package/dist/events/contradictions.js +134 -21
- package/dist/events/declared.d.ts +7 -4
- package/dist/events/declared.js +15 -6
- package/dist/events/echo-mismatch.js +64 -14
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/predicate-eval.js +53 -5
- package/dist/events/predicate-precheck.d.ts +8 -0
- package/dist/events/predicate-precheck.js +41 -0
- package/dist/events/predicate-route.d.ts +14 -0
- package/dist/events/predicate-route.js +36 -8
- package/dist/events/predicate.d.ts +5 -0
- package/dist/events/predicate.js +37 -5
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/flake.d.ts +6 -6
- package/dist/flows/flow-memory-consult.d.ts +55 -0
- package/dist/flows/flow-memory-consult.js +38 -0
- package/dist/flows/flow-replay-run.d.ts +18 -7
- package/dist/flows/flow-replay-run.js +239 -22
- package/dist/flows/flow-replay.js +11 -2
- package/dist/flows/flow-step-runners.d.ts +8 -0
- package/dist/flows/flow-step-runners.js +8 -2
- package/dist/flows/flow-store-for-session.d.ts +31 -0
- package/dist/flows/flow-store-for-session.js +33 -0
- package/dist/flows/flow-tools.js +58 -32
- package/dist/flows/flows.d.ts +8 -0
- package/dist/flows/flows.js +468 -2
- package/dist/flows/heal-run.js +5 -2
- package/dist/flows/predicate-to-expect.d.ts +17 -12
- package/dist/flows/predicate-to-expect.js +30 -12
- package/dist/flows/recordings.d.ts +13 -1
- package/dist/flows/recordings.js +17 -2
- package/dist/flows/replay.d.ts +22 -2
- package/dist/flows/replay.js +28 -2
- package/dist/flows/verify-change-tools.js +5 -2
- package/dist/honesty/blind-spots.d.ts +55 -2
- package/dist/honesty/blind-spots.js +63 -3
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +9 -0
- package/dist/honesty/instrumentation-gaps.js +29 -2
- package/dist/honesty/verified.d.ts +2 -1
- package/dist/honesty/verified.js +16 -1
- package/dist/http-server.js +12 -0
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +15 -1
- package/dist/impact/impact-store.js +43 -2
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +13 -6
- package/dist/init/agent-rules.js +23 -12
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -1
- package/dist/init/capabilities.js +22 -2
- package/dist/init/confirm.d.ts +31 -7
- package/dist/init/confirm.js +48 -8
- package/dist/init/cra.d.ts +20 -4
- package/dist/init/cra.js +57 -17
- package/dist/init/csp-check.js +17 -2
- package/dist/init/csp-doctor.d.ts +8 -1
- package/dist/init/csp-doctor.js +8 -3
- package/dist/init/dev-script.d.ts +3 -1
- package/dist/init/dev-script.js +8 -5
- package/dist/init/format-generated.d.ts +18 -0
- package/dist/init/format-generated.js +39 -0
- package/dist/init/install-hint.d.ts +9 -0
- package/dist/init/install-hint.js +29 -0
- package/dist/init/mcp-clients.d.ts +7 -0
- package/dist/init/mcp-clients.js +7 -0
- package/dist/init/mcp.d.ts +1 -1
- package/dist/init/mcp.js +1 -1
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.js +81 -45
- package/dist/init/plan.d.ts +20 -7
- package/dist/init/plan.js +20 -45
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/run.d.ts +32 -9
- package/dist/init/run.js +141 -119
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +41 -25
- package/dist/init/vite-config.d.ts +1 -1
- package/dist/init/vite-config.js +39 -25
- package/dist/init/workspace-apps.d.ts +17 -10
- package/dist/init/workspace-apps.js +110 -0
- package/dist/input/network-mock-tools.js +3 -6
- package/dist/input/real-input.js +25 -5
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +10 -19
- package/dist/intent/inline-intent.js +91 -1
- package/dist/intent/intent-shard-store.d.ts +72 -0
- package/dist/intent/intent-shard-store.js +203 -0
- package/dist/intent/intent-shard.d.ts +411 -0
- package/dist/intent/intent-shard.js +176 -0
- package/dist/intent/intent-store.d.ts +12 -0
- package/dist/intent/intent-store.js +21 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.js +65 -2
- package/dist/intent/open-intents.d.ts +21 -0
- package/dist/intent/open-intents.js +15 -0
- package/dist/license/license-env.d.ts +29 -0
- package/dist/license/license-env.js +148 -0
- package/dist/loopback-agent.d.ts +31 -0
- package/dist/loopback-agent.js +57 -0
- package/dist/mcp/attach-memory.d.ts +18 -2
- package/dist/mcp/attach-memory.js +50 -7
- package/dist/mcp/client-identity.d.ts +31 -0
- package/dist/mcp/client-identity.js +51 -0
- package/dist/mcp/mcp-post-transport.d.ts +26 -0
- package/dist/mcp/mcp-post-transport.js +109 -0
- package/dist/mcp/mcp-proxy.d.ts +2 -1
- package/dist/mcp/mcp-proxy.js +52 -62
- package/dist/mcp/mcp.js +7 -1
- package/dist/mcp/proxy-backoff.d.ts +11 -0
- package/dist/mcp/proxy-backoff.js +13 -0
- package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
- package/dist/mcp/proxy-daemon-probe.js +7 -1
- package/dist/mcp/proxy-handshake.d.ts +23 -0
- package/dist/mcp/proxy-handshake.js +31 -0
- package/dist/mcp/tool-catalog-cache.d.ts +8 -0
- package/dist/mcp/tool-catalog-cache.js +12 -0
- package/dist/memory/memory-tools.d.ts +2 -0
- package/dist/memory/memory-tools.js +82 -0
- package/dist/memory/project-memory.d.ts +34 -0
- package/dist/memory/project-memory.js +96 -0
- package/dist/oracles/propose-consequences.js +12 -3
- package/dist/pool/browser-pool.d.ts +63 -4
- package/dist/pool/browser-pool.js +113 -4
- package/dist/pool/playwright-launcher.js +9 -0
- package/dist/project/project-tools.js +3 -3
- package/dist/project/reticle-dir.d.ts +15 -2
- package/dist/project/reticle-dir.js +21 -4
- package/dist/runs/run-store.d.ts +10 -0
- package/dist/runs/run-store.js +10 -0
- package/dist/runs/verification-sync.d.ts +0 -11
- package/dist/runs/verification-sync.js +22 -1
- package/dist/runs/verify-server.d.ts +8 -1
- package/dist/runs/verify-server.js +14 -2
- package/dist/session/command-timeout.d.ts +5 -2
- package/dist/session/command-timeout.js +5 -4
- package/dist/session/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/no-session-diagnosis.d.ts +23 -0
- package/dist/session/no-session-diagnosis.js +58 -17
- package/dist/session/no-session-next-action.d.ts +7 -0
- package/dist/session/no-session-next-action.js +42 -3
- package/dist/session/no-session-watch.d.ts +10 -6
- package/dist/session/no-session-watch.js +51 -19
- package/dist/session/pending-commands.d.ts +11 -1
- package/dist/session/pending-commands.js +12 -2
- package/dist/session/resolve-within.d.ts +34 -0
- package/dist/session/resolve-within.js +53 -0
- package/dist/session/session-info.d.ts +18 -0
- package/dist/session/session-info.js +11 -0
- package/dist/session/session-manager.d.ts +14 -0
- package/dist/session/session-manager.js +61 -3
- package/dist/session/session-mirror.d.ts +16 -0
- package/dist/session/session-mirror.js +22 -0
- package/dist/session/session-recommendation.d.ts +19 -5
- package/dist/session/session-recommendation.js +25 -9
- package/dist/session/session-successor.js +36 -1
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +22 -2
- package/dist/session/session.js +87 -7
- package/dist/session/wall-clock.d.ts +12 -0
- package/dist/session/wall-clock.js +14 -0
- package/dist/setup/agent-configs.d.ts +96 -0
- package/dist/setup/agent-configs.js +333 -0
- package/dist/setup/agent-io.d.ts +9 -0
- package/dist/setup/agent-io.js +16 -0
- package/dist/setup/agent-writer.d.ts +51 -0
- package/dist/setup/agent-writer.js +115 -0
- package/dist/setup/approval-migration.d.ts +47 -0
- package/dist/setup/approval-migration.js +68 -0
- package/dist/setup/auto-approve.d.ts +85 -0
- package/dist/setup/auto-approve.js +150 -0
- package/dist/setup/bridge-port.d.ts +15 -0
- package/dist/setup/bridge-port.js +24 -0
- package/dist/setup/desktop-shape.d.ts +57 -0
- package/dist/setup/desktop-shape.js +63 -0
- package/dist/setup/dev-server-wait.d.ts +68 -0
- package/dist/setup/dev-server-wait.js +67 -0
- package/dist/setup/drive-agent.d.ts +71 -0
- package/dist/setup/drive-agent.js +171 -0
- package/dist/setup/drive-plan.d.ts +68 -0
- package/dist/setup/drive-plan.js +130 -0
- package/dist/setup/ensure-daemon.d.ts +29 -0
- package/dist/setup/ensure-daemon.js +58 -0
- package/dist/setup/init-runtime.d.ts +47 -0
- package/dist/setup/init-runtime.js +160 -0
- package/dist/setup/interrupt.d.ts +25 -0
- package/dist/setup/interrupt.js +40 -0
- package/dist/setup/license-key.d.ts +39 -0
- package/dist/setup/license-key.js +72 -0
- package/dist/setup/listeners.d.ts +37 -0
- package/dist/setup/listeners.js +85 -0
- package/dist/setup/node-effects.d.ts +60 -0
- package/dist/setup/node-effects.js +210 -0
- package/dist/setup/page-probe.d.ts +39 -0
- package/dist/setup/page-probe.js +49 -0
- package/dist/setup/relaunch.d.ts +36 -0
- package/dist/setup/relaunch.js +57 -0
- package/dist/setup/remaining-steps.d.ts +19 -0
- package/dist/setup/remaining-steps.js +31 -0
- package/dist/setup/run-setup.d.ts +99 -0
- package/dist/setup/run-setup.js +203 -0
- package/dist/setup/session-pick.d.ts +47 -0
- package/dist/setup/session-pick.js +51 -0
- package/dist/setup/setup-command.d.ts +45 -0
- package/dist/setup/setup-command.js +199 -0
- package/dist/setup/setup-options.d.ts +74 -0
- package/dist/setup/setup-options.js +67 -0
- package/dist/setup/transcripts.d.ts +16 -0
- package/dist/setup/transcripts.js +43 -0
- package/dist/status-payload.d.ts +14 -1
- package/dist/status-payload.js +23 -1
- package/dist/telemetry/daemon-telemetry.d.ts +8 -1
- package/dist/telemetry/daemon-telemetry.js +52 -5
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +9 -1
- package/dist/telemetry/feedback-context.js +60 -29
- package/dist/telemetry/feedback-local.d.ts +15 -0
- package/dist/telemetry/feedback-local.js +56 -0
- package/dist/telemetry/feedback.d.ts +11 -0
- package/dist/telemetry/feedback.js +19 -6
- package/dist/telemetry/init-telemetry.d.ts +2 -0
- package/dist/telemetry/init-telemetry.js +2 -0
- package/dist/telemetry/install-source.d.ts +14 -0
- package/dist/telemetry/install-source.js +30 -0
- package/dist/telemetry/license-activation.d.ts +7 -1
- package/dist/telemetry/license-activation.js +9 -5
- package/dist/telemetry/mcp-connection.js +7 -0
- package/dist/telemetry/project-profile.d.ts +12 -1
- package/dist/telemetry/project-profile.js +11 -6
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- package/dist/telemetry/session-metrics.d.ts +12 -0
- package/dist/telemetry/session-metrics.js +35 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +3 -1
- package/dist/telemetry/tool-refused.js +34 -2
- package/dist/tools/act-capsule.d.ts +10 -0
- package/dist/tools/act-capsule.js +8 -2
- package/dist/tools/act-danger.d.ts +2 -1
- package/dist/tools/act-danger.js +11 -3
- package/dist/tools/act-observation.d.ts +41 -0
- package/dist/tools/act-observation.js +36 -0
- package/dist/tools/act-preflight.d.ts +20 -0
- package/dist/tools/act-preflight.js +98 -0
- package/dist/tools/act-sequence-retry.d.ts +45 -0
- package/dist/tools/act-sequence-retry.js +58 -0
- package/dist/tools/act-sequence-tool.d.ts +13 -0
- package/dist/tools/act-sequence-tool.js +135 -0
- package/dist/tools/act-target.d.ts +14 -0
- package/dist/tools/act-target.js +47 -0
- package/dist/tools/act-tools.d.ts +21 -1
- package/dist/tools/act-tools.js +100 -213
- package/dist/tools/assert-verdict.js +22 -16
- package/dist/tools/browser-tools.js +6 -2
- package/dist/tools/dynamic-tools.js +24 -5
- package/dist/tools/error-recovery.d.ts +1 -0
- package/dist/tools/error-recovery.js +8 -0
- package/dist/tools/invoke-tool.js +59 -9
- package/dist/tools/lease-tools.d.ts +30 -4
- package/dist/tools/lease-tools.js +169 -5
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +47 -40
- package/dist/tools/read-tools.js +28 -7
- package/dist/tools/real-input-attempt.d.ts +10 -0
- package/dist/tools/real-input-attempt.js +59 -1
- package/dist/tools/reconcile-tools.js +4 -4
- package/dist/tools/tool-kit.d.ts +12 -0
- package/dist/tools/tool-kit.js +6 -1
- package/dist/tools/tool-names.d.ts +2 -0
- package/dist/tools/tool-names.js +2 -0
- package/dist/tools/tools-helpers.d.ts +19 -0
- package/dist/tools/tools-helpers.js +46 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +56 -8
- package/dist/update/reticle-deps.d.ts +9 -0
- package/dist/update/reticle-deps.js +27 -0
- package/dist/update/sdk-sync.d.ts +1 -2
- package/dist/update/sdk-sync.js +1 -21
- package/dist/update/update-nudge.d.ts +18 -0
- package/dist/update/update-nudge.js +20 -0
- package/dist/version/peer-announce.js +4 -2
- package/dist/version/sdk-fix.d.ts +47 -0
- package/dist/version/sdk-fix.js +127 -0
- package/dist/version/version-nudge.d.ts +14 -0
- package/dist/version/version-nudge.js +21 -0
- package/dist/version/version-skew.d.ts +38 -3
- package/dist/version/version-skew.js +83 -7
- package/dist/visual/visual-store.d.ts +6 -6
- package/dist/visual/visual-store.js +18 -16
- package/dist/visual/visual-tools.js +57 -12
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +1 -1
- package/docs/getting-started.md +19 -3
- package/docs/index.mdx +9 -6
- package/docs/install-agentic.mdx +38 -12
- package/docs/install-manual.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/usage.md +1 -1
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DAEMON_INNER_COMMAND, DRIVE_FLAG, HEADED_FLAG, HTTP_FLAG, HTTP_PORT_FLAG, HTTP_TOKEN_FLAG, PORT_FLAG, } from './cli-parse.js';
|
|
2
|
+
export function daemonStartOptions(parsed) {
|
|
3
|
+
return {
|
|
4
|
+
port: parsed.port,
|
|
5
|
+
headless: parsed.headless,
|
|
6
|
+
...(parsed.driveUrl !== undefined ? { driveUrl: parsed.driveUrl } : {}),
|
|
7
|
+
...(parsed.http
|
|
8
|
+
? {
|
|
9
|
+
httpVerify: true,
|
|
10
|
+
...(parsed.httpPort !== undefined ? { httpVerifyPort: parsed.httpPort } : {}),
|
|
11
|
+
...(parsed.httpToken !== undefined ? { httpVerifyToken: parsed.httpToken } : {}),
|
|
12
|
+
}
|
|
13
|
+
: {}),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The argv a parent hands its detached `_daemon` child.
|
|
18
|
+
*
|
|
19
|
+
* `serve` and `mcp` each built this list by hand and each made the SAME mistake as the options
|
|
20
|
+
* builder above — pushing `--headed` only inside the `driveUrl` branch — so the flag survived
|
|
21
|
+
* parsing, was dropped at the spawn, and the child launched a hidden pool. Three sites, one wrong
|
|
22
|
+
* idea: that headless belongs to the drive URL. It belongs to the pool, which exists either way.
|
|
23
|
+
*/
|
|
24
|
+
export function daemonSpawnArgs(parsed) {
|
|
25
|
+
const args = [DAEMON_INNER_COMMAND, PORT_FLAG, String(parsed.port)];
|
|
26
|
+
if (parsed.driveUrl !== undefined)
|
|
27
|
+
args.push(DRIVE_FLAG, parsed.driveUrl);
|
|
28
|
+
if (!parsed.headless)
|
|
29
|
+
args.push(HEADED_FLAG);
|
|
30
|
+
if (parsed.http) {
|
|
31
|
+
args.push(HTTP_FLAG);
|
|
32
|
+
if (parsed.httpPort !== undefined)
|
|
33
|
+
args.push(HTTP_PORT_FLAG, String(parsed.httpPort));
|
|
34
|
+
if (parsed.httpToken !== undefined)
|
|
35
|
+
args.push(HTTP_TOKEN_FLAG, parsed.httpToken);
|
|
36
|
+
}
|
|
37
|
+
return args;
|
|
38
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser device flow's wire shapes and its hand-off to a real browser.
|
|
3
|
+
*
|
|
4
|
+
* `reticle login` works like `gh auth login`: the CLI asks the cloud for a device code, opens the
|
|
5
|
+
* approval page, and polls until a human confirms it in a session that is already signed in. That
|
|
6
|
+
* confirmation is the whole security of the flow — only somebody already authenticated in the
|
|
7
|
+
* dashboard can turn a device code into a token for their org.
|
|
8
|
+
*
|
|
9
|
+
* Split from `cloud-cli` because it is self-contained: the schemas describe two endpoints and the
|
|
10
|
+
* opener knows only about platforms. Nothing here reads a session, a credential, or a project.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
/** What `/v1/auth/device/start` answers: the codes, where to approve, and how fast to poll. */
|
|
14
|
+
export declare const DeviceStartSchema: z.ZodObject<{
|
|
15
|
+
deviceCode: z.ZodString;
|
|
16
|
+
userCode: z.ZodString;
|
|
17
|
+
verificationUri: z.ZodString;
|
|
18
|
+
verificationUriComplete: z.ZodString;
|
|
19
|
+
interval: z.ZodNumber;
|
|
20
|
+
expiresAt: z.ZodNumber;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
deviceCode: string;
|
|
23
|
+
userCode: string;
|
|
24
|
+
verificationUri: string;
|
|
25
|
+
verificationUriComplete: string;
|
|
26
|
+
interval: number;
|
|
27
|
+
expiresAt: number;
|
|
28
|
+
}, {
|
|
29
|
+
deviceCode: string;
|
|
30
|
+
userCode: string;
|
|
31
|
+
verificationUri: string;
|
|
32
|
+
verificationUriComplete: string;
|
|
33
|
+
interval: number;
|
|
34
|
+
expiresAt: number;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* What `/v1/auth/device/token` answers while polling. Everything past `status` is optional because
|
|
38
|
+
* only an APPROVED poll carries a token, and `org.id` is absent on a cloud older than org-scoped
|
|
39
|
+
* credentials.
|
|
40
|
+
*/
|
|
41
|
+
export declare const DevicePollSchema: z.ZodObject<{
|
|
42
|
+
status: z.ZodString;
|
|
43
|
+
token: z.ZodOptional<z.ZodString>;
|
|
44
|
+
org: z.ZodOptional<z.ZodObject<{
|
|
45
|
+
id: z.ZodOptional<z.ZodString>;
|
|
46
|
+
name: z.ZodString;
|
|
47
|
+
}, "strip", z.ZodTypeAny, {
|
|
48
|
+
name: string;
|
|
49
|
+
id?: string | undefined;
|
|
50
|
+
}, {
|
|
51
|
+
name: string;
|
|
52
|
+
id?: string | undefined;
|
|
53
|
+
}>>;
|
|
54
|
+
}, "strip", z.ZodTypeAny, {
|
|
55
|
+
status: string;
|
|
56
|
+
org?: {
|
|
57
|
+
name: string;
|
|
58
|
+
id?: string | undefined;
|
|
59
|
+
} | undefined;
|
|
60
|
+
token?: string | undefined;
|
|
61
|
+
}, {
|
|
62
|
+
status: string;
|
|
63
|
+
org?: {
|
|
64
|
+
name: string;
|
|
65
|
+
id?: string | undefined;
|
|
66
|
+
} | undefined;
|
|
67
|
+
token?: string | undefined;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* Best-effort open the approval page in the default browser.
|
|
71
|
+
*
|
|
72
|
+
* Every failure is swallowed on purpose: the URL is printed either way, and a headless box, a
|
|
73
|
+
* container, or an SSH session has no opener at all. Refusing to log in because a convenience did
|
|
74
|
+
* not work would break the one environment that most needs the printed fallback.
|
|
75
|
+
*/
|
|
76
|
+
export declare const openBrowser: (target: string) => void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser device flow's wire shapes and its hand-off to a real browser.
|
|
3
|
+
*
|
|
4
|
+
* `reticle login` works like `gh auth login`: the CLI asks the cloud for a device code, opens the
|
|
5
|
+
* approval page, and polls until a human confirms it in a session that is already signed in. That
|
|
6
|
+
* confirmation is the whole security of the flow — only somebody already authenticated in the
|
|
7
|
+
* dashboard can turn a device code into a token for their org.
|
|
8
|
+
*
|
|
9
|
+
* Split from `cloud-cli` because it is self-contained: the schemas describe two endpoints and the
|
|
10
|
+
* opener knows only about platforms. Nothing here reads a session, a credential, or a project.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { spawn } from 'node:child_process';
|
|
14
|
+
import { NodePlatform } from '../platform.js';
|
|
15
|
+
/** What `/v1/auth/device/start` answers: the codes, where to approve, and how fast to poll. */
|
|
16
|
+
export const DeviceStartSchema = z.object({
|
|
17
|
+
deviceCode: z.string(),
|
|
18
|
+
userCode: z.string(),
|
|
19
|
+
verificationUri: z.string(),
|
|
20
|
+
verificationUriComplete: z.string(),
|
|
21
|
+
interval: z.number(),
|
|
22
|
+
expiresAt: z.number(),
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* What `/v1/auth/device/token` answers while polling. Everything past `status` is optional because
|
|
26
|
+
* only an APPROVED poll carries a token, and `org.id` is absent on a cloud older than org-scoped
|
|
27
|
+
* credentials.
|
|
28
|
+
*/
|
|
29
|
+
export const DevicePollSchema = z.object({
|
|
30
|
+
status: z.string(),
|
|
31
|
+
token: z.string().optional(),
|
|
32
|
+
org: z.object({ id: z.string().optional(), name: z.string() }).optional(),
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* Best-effort open the approval page in the default browser.
|
|
36
|
+
*
|
|
37
|
+
* Every failure is swallowed on purpose: the URL is printed either way, and a headless box, a
|
|
38
|
+
* container, or an SSH session has no opener at all. Refusing to log in because a convenience did
|
|
39
|
+
* not work would break the one environment that most needs the printed fallback.
|
|
40
|
+
*/
|
|
41
|
+
export const openBrowser = (target) => {
|
|
42
|
+
const cmd = NodePlatform.MACOS === process.platform
|
|
43
|
+
? 'open'
|
|
44
|
+
: NodePlatform.WINDOWS === process.platform
|
|
45
|
+
? 'cmd'
|
|
46
|
+
: 'xdg-open';
|
|
47
|
+
const args = NodePlatform.WINDOWS === process.platform ? ['/c', 'start', '', target] : [target];
|
|
48
|
+
try {
|
|
49
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
50
|
+
child.on('error', () => undefined);
|
|
51
|
+
child.unref();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* no opener available — the user opens the printed URL manually */
|
|
55
|
+
}
|
|
56
|
+
};
|
|
@@ -31,6 +31,15 @@ export declare const DoctorRow: {
|
|
|
31
31
|
readonly DAEMON: "daemon";
|
|
32
32
|
readonly VERSION: "version";
|
|
33
33
|
readonly SESSIONS: "sessions";
|
|
34
|
+
/**
|
|
35
|
+
* The agent-to-daemon hop, which every other row here is blind to.
|
|
36
|
+
*
|
|
37
|
+
* Reported from the field: a user with the SDK injected, the overlay visible, a live session on
|
|
38
|
+
* the daemon and every tool listed in their client was still told by three separate agents that
|
|
39
|
+
* Reticle was not present. Their words: "four green checkmarks that don't add up to a working
|
|
40
|
+
* verification." Every row in this list checks a COMPONENT; none checked a LINK.
|
|
41
|
+
*/
|
|
42
|
+
readonly AGENT_LINK: "agent link";
|
|
34
43
|
readonly BRIDGE_PORT: "bridge port";
|
|
35
44
|
readonly PORT_CHECK: "port check";
|
|
36
45
|
readonly SIBLING: "sibling";
|
package/dist/cli/doctor-rows.js
CHANGED
|
@@ -31,6 +31,15 @@ export const DoctorRow = {
|
|
|
31
31
|
DAEMON: 'daemon',
|
|
32
32
|
VERSION: 'version',
|
|
33
33
|
SESSIONS: 'sessions',
|
|
34
|
+
/**
|
|
35
|
+
* The agent-to-daemon hop, which every other row here is blind to.
|
|
36
|
+
*
|
|
37
|
+
* Reported from the field: a user with the SDK injected, the overlay visible, a live session on
|
|
38
|
+
* the daemon and every tool listed in their client was still told by three separate agents that
|
|
39
|
+
* Reticle was not present. Their words: "four green checkmarks that don't add up to a working
|
|
40
|
+
* verification." Every row in this list checks a COMPONENT; none checked a LINK.
|
|
41
|
+
*/
|
|
42
|
+
AGENT_LINK: 'agent link',
|
|
34
43
|
BRIDGE_PORT: 'bridge port',
|
|
35
44
|
PORT_CHECK: 'port check',
|
|
36
45
|
SIBLING: 'sibling',
|
|
@@ -78,6 +78,19 @@ export declare function readAttachResponse(port: number, url: string, res: {
|
|
|
78
78
|
export declare function requestDriveSession(port: number, url: string, post?: DrivePost): Promise<DriveAttachResult>;
|
|
79
79
|
/** The success line. Names the session, because that is the handle everything downstream needs. */
|
|
80
80
|
export declare function describeAttached(port: number, url: string, session: DriveSession): string;
|
|
81
|
+
/**
|
|
82
|
+
* What an attach cannot honour: the window.
|
|
83
|
+
*
|
|
84
|
+
* `drive` documents a HEADED browser and parses `--headless` as the opt-out, but the attach path
|
|
85
|
+
* POSTs a url and nothing else — the daemon answers from a pool it launched headless at boot, and no
|
|
86
|
+
* per-request flag can change a browser that is already running. So the run is invisible, which is
|
|
87
|
+
* the half of the field report that reads "the app starts to run headlessly".
|
|
88
|
+
*
|
|
89
|
+
* Saying so is the honest half. The other half is that the run is no longer unwatchable: the HUD
|
|
90
|
+
* feed is mirrored to every other tab of the same app (see Session.setViewers), so a tab the human
|
|
91
|
+
* already has open shows the narration and the counters as the leased context is driven.
|
|
92
|
+
*/
|
|
93
|
+
export declare function driveHeadlessOnAttach(port: number, url: string): string;
|
|
81
94
|
/**
|
|
82
95
|
* A stranger holds the port, in words `drive` can honour.
|
|
83
96
|
*
|
package/dist/cli/drive-attach.js
CHANGED
|
@@ -147,6 +147,24 @@ export function describeAttached(port, url, session) {
|
|
|
147
147
|
return (`attached to the Reticle daemon on :${String(port)} — it is driving ${url} as session ` +
|
|
148
148
|
`${session.sessionId}, which every Reticle tool can address.${life}`);
|
|
149
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* What an attach cannot honour: the window.
|
|
152
|
+
*
|
|
153
|
+
* `drive` documents a HEADED browser and parses `--headless` as the opt-out, but the attach path
|
|
154
|
+
* POSTs a url and nothing else — the daemon answers from a pool it launched headless at boot, and no
|
|
155
|
+
* per-request flag can change a browser that is already running. So the run is invisible, which is
|
|
156
|
+
* the half of the field report that reads "the app starts to run headlessly".
|
|
157
|
+
*
|
|
158
|
+
* Saying so is the honest half. The other half is that the run is no longer unwatchable: the HUD
|
|
159
|
+
* feed is mirrored to every other tab of the same app (see Session.setViewers), so a tab the human
|
|
160
|
+
* already has open shows the narration and the counters as the leased context is driven.
|
|
161
|
+
*/
|
|
162
|
+
export function driveHeadlessOnAttach(port, url) {
|
|
163
|
+
return (`note: this session runs inside the daemon on :${String(port)}, whose browser was launched ` +
|
|
164
|
+
'headless — there is no window to show, and `--headless` is not a per-drive choice. Open ' +
|
|
165
|
+
`${url} in your own browser to watch: any tab of that app mirrors this session's HUD. To get a ` +
|
|
166
|
+
'window instead, stop the daemon and run `reticle drive` again with the port free.');
|
|
167
|
+
}
|
|
150
168
|
/**
|
|
151
169
|
* A stranger holds the port, in words `drive` can honour.
|
|
152
170
|
*
|
|
@@ -13,7 +13,7 @@ import { probeDaemon } from '../mcp/mcp-proxy.js';
|
|
|
13
13
|
import { readPid } from '../daemon/daemon.js';
|
|
14
14
|
import { fetchStatus } from './cli-launch.js';
|
|
15
15
|
import { captureLookup, findPortHolder } from './port-holder.js';
|
|
16
|
-
import { DriveMode, decideDriveMode, describeAttached, driveForeignHolder, driveRaceLost, requestDriveSession, } from './drive-attach.js';
|
|
16
|
+
import { DriveMode, decideDriveMode, describeAttached, driveForeignHolder, driveHeadlessOnAttach, driveRaceLost, requestDriveSession, } from './drive-attach.js';
|
|
17
17
|
export function handleDrive(parsed) {
|
|
18
18
|
void driveWithHonestConflict(parsed);
|
|
19
19
|
}
|
|
@@ -43,7 +43,7 @@ async function driveWithHonestConflict(parsed) {
|
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
if (DriveMode.ATTACH === mode) {
|
|
46
|
-
await attachToRunningDaemon(parsed.port, parsed.driveUrl);
|
|
46
|
+
await attachToRunningDaemon(parsed.port, parsed.driveUrl, parsed.headless);
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
// The probe cannot close the race: something can take the port between here and the bind, and the
|
|
@@ -81,7 +81,7 @@ async function driveWithHonestConflict(parsed) {
|
|
|
81
81
|
* against it renews its lease), and a foreground hold here could only keep it alive by faking tool
|
|
82
82
|
* calls at it.
|
|
83
83
|
*/
|
|
84
|
-
async function attachToRunningDaemon(port, url) {
|
|
84
|
+
async function attachToRunningDaemon(port, url, headless) {
|
|
85
85
|
const result = await requestDriveSession(port, url);
|
|
86
86
|
if (!result.ok) {
|
|
87
87
|
log('reticle_drive_attach_failed', { port, url, reason: result.reason });
|
|
@@ -96,6 +96,10 @@ async function attachToRunningDaemon(port, url) {
|
|
|
96
96
|
ready: result.session.ready,
|
|
97
97
|
});
|
|
98
98
|
process.stderr.write(`${describeAttached(port, url, result.session)}\n`);
|
|
99
|
+
// The command's own default is headed, and this path cannot deliver that. Said out loud rather
|
|
100
|
+
// than left for the user to discover by watching a window that never opens.
|
|
101
|
+
if (!headless)
|
|
102
|
+
process.stderr.write(`${driveHeadlessOnAttach(port, url)}\n`);
|
|
99
103
|
// A tab that never connected is not a drive session, and reporting one is the false green this
|
|
100
104
|
// command exists to avoid.
|
|
101
105
|
if (!result.session.ready)
|
package/dist/cli/mcp-command.js
CHANGED
|
@@ -13,10 +13,16 @@ import { PortPresence, probePresence, presenceIsUsable, describePresence, } from
|
|
|
13
13
|
import { waitForDaemon, startMcpProxy, probeDaemon, proxyLog, setProxyLogPort, } from '../mcp/mcp-proxy.js';
|
|
14
14
|
import { installProxyResilience } from '../daemon/daemon-resilience.js';
|
|
15
15
|
import { readProjectId } from './cli-port.js';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { resolveMcpPort, daemonProjectAt } from '../daemon/daemon-resolve.js';
|
|
17
|
+
import { daemonSpawnArgs } from './daemon-start-options.js';
|
|
18
|
+
import { WakeAction, decideWake } from '../daemon/wake-decision.js';
|
|
18
19
|
import { pickDaemonPortToBind } from '../daemon/free-port.js';
|
|
19
20
|
import { fetchStatus } from './cli-launch.js';
|
|
21
|
+
import { migrateApprovals } from '../setup/approval-migration.js';
|
|
22
|
+
import { ReticleEnv } from '@reticlehq/core';
|
|
23
|
+
import { agentIo } from '../setup/agent-io.js';
|
|
24
|
+
import { SERVER_VERSION } from '../version/server-version.js';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
20
26
|
/**
|
|
21
27
|
* MCP proxy mode: ensures the daemon is running, then bridges Claude Code's
|
|
22
28
|
* stdin/stdout to the daemon's SSE endpoint. This is the recommended way to
|
|
@@ -36,7 +42,25 @@ export async function handleMcp(opts) {
|
|
|
36
42
|
// proxy's transport built around a single fixed number. Making the proxy chase a moving port would
|
|
37
43
|
// mean reworking the reconnect path, which is the one piece whose failure reads to a user as
|
|
38
44
|
// "the MCP server disappeared mid-session".
|
|
39
|
-
|
|
45
|
+
// Read once and reused by the wake path below: the boot resolution and every later wake must be
|
|
46
|
+
// defending the SAME identity, and computing it twice is how they drift apart.
|
|
47
|
+
// Before the port work, because it is the only moment we are guaranteed to get on a machine that
|
|
48
|
+
// installed Reticle before the pre-approval rules existed. Once per version, never a create that
|
|
49
|
+
// supersedes an allowlist we cannot read, and it cannot throw: see approval-migration.
|
|
50
|
+
// A sandboxed state dir means a test, a gate or a fixture, none of which are asking us to rewrite
|
|
51
|
+
// the real user's editor configuration.
|
|
52
|
+
if (undefined === process.env[ReticleEnv.STATE_DIR]) {
|
|
53
|
+
migrateApprovals({
|
|
54
|
+
io: agentIo,
|
|
55
|
+
home: homedir(),
|
|
56
|
+
platform: process.platform,
|
|
57
|
+
stateHome: reticleStateHome(),
|
|
58
|
+
version: SERVER_VERSION,
|
|
59
|
+
log,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const projectId = readProjectId(process.cwd());
|
|
63
|
+
const port = await resolveMcpPort(opts.port, projectId, reticleStateHome(), {
|
|
40
64
|
alive: isAlive,
|
|
41
65
|
daemonPresent: async (p) => presenceIsUsable(await probePresence(p, { tcpOpen: probeDaemon, status: fetchStatus })),
|
|
42
66
|
pickPort: (p) => pickDaemonPortToBind(p),
|
|
@@ -71,29 +95,36 @@ export async function handleMcp(opts) {
|
|
|
71
95
|
// client request woke into the identical non-answer. Rejecting here is what puts the proxy
|
|
72
96
|
// dormant with a reason, instead of pretending the wake succeeded.
|
|
73
97
|
const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
74
|
-
|
|
98
|
+
// Identity, not just presence. `resolveMcpPort` above already refuses to adopt a stranger's
|
|
99
|
+
// daemon at boot; this is the same question asked on every WAKE, which is where it was missing.
|
|
100
|
+
//
|
|
101
|
+
// Reachable by ordinary use, with no error anywhere: a daemon retires on its idle timer and
|
|
102
|
+
// frees the port, another project's daemon binds it, and this project's dormant proxy wakes
|
|
103
|
+
// straight onto it. The agent then drives, asserts and reports a verdict about a DIFFERENT
|
|
104
|
+
// APPLICATION. That is worse than the disconnect it was reintroduced under the cover of, because
|
|
105
|
+
// a disconnect is visible and this is a false green wearing a valid session.
|
|
106
|
+
const wake = decideWake(presence, daemonProjectAt(port, reticleStateHome()), projectId);
|
|
107
|
+
if (WakeAction.USE === wake)
|
|
75
108
|
return;
|
|
76
|
-
if (
|
|
77
|
-
throw new Error(
|
|
109
|
+
if (WakeAction.REFUSE === wake) {
|
|
110
|
+
throw new Error(PortPresence.DAEMON === presence
|
|
111
|
+
? `the daemon on port ${String(port)} belongs to a different project, so this one will ` +
|
|
112
|
+
'not adopt it. Stop it, or start this project on its own port.'
|
|
113
|
+
: describePresence(presence, port));
|
|
114
|
+
}
|
|
78
115
|
const scriptPath = process.argv[1];
|
|
79
116
|
if (scriptPath === undefined) {
|
|
80
117
|
log('reticle_mcp_no_script', {});
|
|
81
118
|
process.exit(1);
|
|
82
119
|
}
|
|
83
|
-
const daemonArgs =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
daemonArgs.push(HTTP_FLAG);
|
|
92
|
-
if (httpPort !== undefined)
|
|
93
|
-
daemonArgs.push(HTTP_PORT_FLAG, String(httpPort));
|
|
94
|
-
if (httpToken !== undefined)
|
|
95
|
-
daemonArgs.push(HTTP_TOKEN_FLAG, httpToken);
|
|
96
|
-
}
|
|
120
|
+
const daemonArgs = daemonSpawnArgs({
|
|
121
|
+
port,
|
|
122
|
+
headless,
|
|
123
|
+
http,
|
|
124
|
+
...(driveUrl !== undefined ? { driveUrl } : {}),
|
|
125
|
+
...(httpPort !== undefined ? { httpPort } : {}),
|
|
126
|
+
...(httpToken !== undefined ? { httpToken } : {}),
|
|
127
|
+
});
|
|
97
128
|
spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
|
|
98
129
|
// Announce the daemon only once the PORT ACCEPTS. This line used to be written the instant the
|
|
99
130
|
// child was spawned, which on a Windows first bootstrap meant `reticle_mcp_daemon_started`
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What project a repo binds to when nobody says.
|
|
3
|
+
*
|
|
4
|
+
* `link` used to answer "default" for every repo on every machine, which is wrong in two directions
|
|
5
|
+
* at once. Measured: two unrelated checkouts linked on one account — a storefront and a billing
|
|
6
|
+
* service — both bound to a single project called "Default" and shared one key, so their runs,
|
|
7
|
+
* issues and impact merged into one bucket and the dashboard's per-project view described nothing.
|
|
8
|
+
* It is also what made two TENANTS collide, because a credential slot built from a project id is
|
|
9
|
+
* only as distinct as the ids are.
|
|
10
|
+
*
|
|
11
|
+
* A repo already knows its name: the directory it lives in. That is not a guess, it is the name its
|
|
12
|
+
* owner chose and the one they will look for in a project list.
|
|
13
|
+
*/
|
|
14
|
+
/** The id used when a directory name yields nothing usable — the historical behaviour, unchanged. */
|
|
15
|
+
export declare const DEFAULT_PROJECT_ID = "default";
|
|
16
|
+
/**
|
|
17
|
+
* Turn a directory name into a project id: lowercase, and anything that is not a letter or digit
|
|
18
|
+
* becomes a single hyphen. Conservative on purpose — this id ends up in URLs, in credential slot
|
|
19
|
+
* keys and in a project list somebody has to read.
|
|
20
|
+
*/
|
|
21
|
+
export declare const slugifyProjectName: (raw: string) => string;
|
|
22
|
+
/**
|
|
23
|
+
* The project a `link` with no `--project` should target.
|
|
24
|
+
*
|
|
25
|
+
* The EXISTING binding wins over the directory name, and that ordering is the whole safety of this
|
|
26
|
+
* change. A repo that is already linked has history in a project; re-running `link` — which people
|
|
27
|
+
* do to rotate a key or repoint an environment — must land in that same project or the history
|
|
28
|
+
* silently splits in two, which is worse than the shared-bucket problem this fixes.
|
|
29
|
+
*/
|
|
30
|
+
export declare const defaultProjectFor: (cwdBasename: string, existingProjectId: string | undefined) => string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What project a repo binds to when nobody says.
|
|
3
|
+
*
|
|
4
|
+
* `link` used to answer "default" for every repo on every machine, which is wrong in two directions
|
|
5
|
+
* at once. Measured: two unrelated checkouts linked on one account — a storefront and a billing
|
|
6
|
+
* service — both bound to a single project called "Default" and shared one key, so their runs,
|
|
7
|
+
* issues and impact merged into one bucket and the dashboard's per-project view described nothing.
|
|
8
|
+
* It is also what made two TENANTS collide, because a credential slot built from a project id is
|
|
9
|
+
* only as distinct as the ids are.
|
|
10
|
+
*
|
|
11
|
+
* A repo already knows its name: the directory it lives in. That is not a guess, it is the name its
|
|
12
|
+
* owner chose and the one they will look for in a project list.
|
|
13
|
+
*/
|
|
14
|
+
/** Cap the slug so a deep or oddly-named checkout cannot produce an unreadable project id. */
|
|
15
|
+
const MAX_SLUG = 48;
|
|
16
|
+
/** The id used when a directory name yields nothing usable — the historical behaviour, unchanged. */
|
|
17
|
+
export const DEFAULT_PROJECT_ID = 'default';
|
|
18
|
+
/**
|
|
19
|
+
* Turn a directory name into a project id: lowercase, and anything that is not a letter or digit
|
|
20
|
+
* becomes a single hyphen. Conservative on purpose — this id ends up in URLs, in credential slot
|
|
21
|
+
* keys and in a project list somebody has to read.
|
|
22
|
+
*/
|
|
23
|
+
export const slugifyProjectName = (raw) => {
|
|
24
|
+
const slug = raw
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
27
|
+
.replace(/^-+|-+$/g, '')
|
|
28
|
+
.slice(0, MAX_SLUG)
|
|
29
|
+
.replace(/-+$/g, '');
|
|
30
|
+
return 0 === slug.length ? DEFAULT_PROJECT_ID : slug;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The project a `link` with no `--project` should target.
|
|
34
|
+
*
|
|
35
|
+
* The EXISTING binding wins over the directory name, and that ordering is the whole safety of this
|
|
36
|
+
* change. A repo that is already linked has history in a project; re-running `link` — which people
|
|
37
|
+
* do to rotate a key or repoint an environment — must land in that same project or the history
|
|
38
|
+
* silently splits in two, which is worse than the shared-bucket problem this fixes.
|
|
39
|
+
*/
|
|
40
|
+
export const defaultProjectFor = (cwdBasename, existingProjectId) => {
|
|
41
|
+
if (existingProjectId !== undefined && existingProjectId.length > 0)
|
|
42
|
+
return existingProjectId;
|
|
43
|
+
return slugifyProjectName(cwdBasename);
|
|
44
|
+
};
|
|
@@ -27,6 +27,20 @@ export interface StatusFacts {
|
|
|
27
27
|
* paths do the first without the second.
|
|
28
28
|
*/
|
|
29
29
|
initialized: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Ports of dev servers that ANNOUNCED themselves — i.e. that have Reticle loaded in the process
|
|
32
|
+
* actually running.
|
|
33
|
+
*
|
|
34
|
+
* `nextActionFor` already ranks on a listening-port list; it just never had one from here, so this
|
|
35
|
+
* command passed `[]` and every wired project with a live dev server was told "the app is probably
|
|
36
|
+
* not running". That is advice contradicting the terminal the reader is looking at, and it sends
|
|
37
|
+
* them to restart something already up.
|
|
38
|
+
*
|
|
39
|
+
* Stronger than the port scan the field was designed for: a scanned port proves something is
|
|
40
|
+
* listening, an announced one proves it is listening AND instrumented. Optional so every existing
|
|
41
|
+
* caller keeps today's behaviour.
|
|
42
|
+
*/
|
|
43
|
+
devServerPorts?: readonly number[];
|
|
30
44
|
}
|
|
31
45
|
/**
|
|
32
46
|
* What to do next, or `undefined` when a session is connected and there is nothing to fix.
|
|
@@ -43,9 +43,9 @@ export function statusNextAction(facts) {
|
|
|
43
43
|
`Reticle tool, and it exits again when idle. ${wiring} Ask your agent to verify something, then ` +
|
|
44
44
|
`run status again.`).replace(/\s+/g, ' ');
|
|
45
45
|
}
|
|
46
|
-
// The daemon is up and no page has connected. `nextActionFor` owns the ranking;
|
|
47
|
-
//
|
|
48
|
-
//
|
|
46
|
+
// The daemon is up and no page has connected. `nextActionFor` owns the ranking; this passes what
|
|
47
|
+
// it has and lets that function say what is missing rather than guessing at it. It CAN see
|
|
48
|
+
// listening ports now — the announced ones — which is what the empty list here used to cost.
|
|
49
49
|
const next = nextActionFor({
|
|
50
50
|
// The durable bit IS this side's `everConnected`. In-process the flag means "this daemon has served
|
|
51
51
|
// a session", which a daemon seconds old cannot know; from the CLI the honest equivalent is "an app
|
|
@@ -53,7 +53,7 @@ export function statusNextAction(facts) {
|
|
|
53
53
|
// is in question at all.
|
|
54
54
|
everConnected: facts.previouslyConnected,
|
|
55
55
|
initialized: facts.initialized,
|
|
56
|
-
listening: [],
|
|
56
|
+
listening: facts.devServerPorts ?? [],
|
|
57
57
|
dev: undefined,
|
|
58
58
|
previouslyConnected: facts.previouslyConnected,
|
|
59
59
|
});
|
package/dist/cli.d.ts
CHANGED