@reticlehq/server 3.8.2 → 3.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/LICENSE +201 -104
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/bin/launch.js +13 -0
- package/bin/package.json +3 -0
- package/bin/reticle.js +66 -8
- package/dist/chromium-launch-options.d.ts +25 -0
- package/dist/chromium-launch-options.js +21 -1
- package/dist/command/cli/adhoc-verdict.js +34 -7
- package/dist/command/cli/answers/gate-exit.d.ts +6 -2
- package/dist/command/cli/answers/gate-exit.js +12 -2
- package/dist/command/cli/ci-hint.d.ts +44 -0
- package/dist/command/cli/ci-hint.js +72 -0
- package/dist/command/cli/cli-doctor.d.ts +6 -1
- package/dist/command/cli/cli-doctor.js +45 -4
- package/dist/command/cli/cli-flow-commands.d.ts +7 -1
- package/dist/command/cli/cli-flow-commands.js +39 -17
- package/dist/command/cli/cli-kill.d.ts +21 -0
- package/dist/command/cli/cli-kill.js +46 -3
- package/dist/command/cli/cli-parse.d.ts +5 -2
- package/dist/command/cli/cli-parse.js +10 -5
- package/dist/command/cli/cli-usage.js +3 -2
- package/dist/command/cli/cli-verify.d.ts +6 -1
- package/dist/command/cli/cli-verify.js +32 -13
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
- package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
- package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
- package/dist/command/cli/doctor/doctor-fix.js +89 -0
- package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/doctor-repairs.d.ts +64 -0
- package/dist/command/cli/doctor-repairs.js +214 -0
- package/dist/command/cli/drive/drive-command.js +3 -7
- package/dist/command/cli/first-use-checks.d.ts +41 -0
- package/dist/command/cli/first-use-checks.js +127 -0
- package/dist/command/cli/first-verdict-node.d.ts +3 -0
- package/dist/command/cli/first-verdict-node.js +189 -0
- package/dist/command/cli/first-verdict.d.ts +126 -0
- package/dist/command/cli/first-verdict.js +267 -0
- package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
- package/dist/command/cli/launch/sdk-version-match.js +3 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +6 -5
- package/dist/command/cli/mcp-command.js +13 -4
- package/dist/command/cli/ports/port-holder.d.ts +2 -0
- package/dist/command/cli/ports/port-holder.js +11 -3
- package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
- package/dist/command/cli/setup-mcp-cli.js +98 -23
- package/dist/command/cli/status/status-lines.d.ts +1 -0
- package/dist/command/cli/status/status-lines.js +50 -17
- package/dist/command/cli/status-fields.js +17 -3
- package/dist/command/cli/try-command.d.ts +7 -0
- package/dist/command/cli/try-command.js +11 -3
- package/dist/command/cli.js +70 -33
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
- package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
- package/dist/command/daemon/daemon-argv.d.ts +9 -0
- package/dist/command/daemon/daemon-argv.js +11 -0
- package/dist/command/daemon/daemon.d.ts +10 -3
- package/dist/command/daemon/daemon.js +17 -6
- package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
- package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
- package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
- package/dist/command/daemon/lifetime/death-cause.js +130 -0
- package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
- package/dist/command/daemon/lifetime/heartbeat.js +4 -13
- package/dist/command/daemon/port-state.d.ts +14 -0
- package/dist/command/daemon/port-state.js +31 -0
- package/dist/command/setup/agent-configs.d.ts +2 -0
- package/dist/command/setup/agent-configs.js +42 -15
- package/dist/command/setup/agent-next-steps.d.ts +77 -0
- package/dist/command/setup/agent-next-steps.js +284 -0
- package/dist/command/setup/agent-writer.js +11 -6
- package/dist/command/setup/init/init-host.js +2 -0
- package/dist/command/setup/init/init-runtime.d.ts +6 -1
- package/dist/command/setup/init/init-runtime.js +57 -21
- package/dist/command/setup/mcp-launcher.d.ts +3 -0
- package/dist/command/setup/mcp-launcher.js +11 -0
- package/dist/command/setup/setup-command.js +30 -12
- package/dist/command/setup/setup-mcp.d.ts +8 -0
- package/dist/command/setup/setup-mcp.js +47 -20
- package/dist/command/setup/start-dev-server.d.ts +8 -0
- package/dist/command/setup/start-dev-server.js +43 -0
- package/dist/command/version/identity/server-version.d.ts +2 -0
- package/dist/command/version/identity/server-version.js +2 -0
- package/dist/command/version/version-skew.d.ts +7 -0
- package/dist/command/version/version-skew.js +13 -2
- package/dist/features/license/license.d.ts +11 -71
- package/dist/features/license/license.js +43 -136
- package/dist/features/visual/desktop-capture.d.ts +33 -0
- package/dist/features/visual/desktop-capture.js +94 -0
- package/dist/features/visual/step-shots.js +139 -35
- package/dist/features/visual/visual-tools.d.ts +0 -18
- package/dist/features/visual/visual-tools.js +2 -81
- package/dist/import-graph.js +5 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +14 -5
- package/dist/judgement/runs/run-ledger.d.ts +7 -5
- package/dist/judgement/runs/run-ledger.js +98 -69
- package/dist/judgement/runs/verification-sync.d.ts +6 -3
- package/dist/judgement/runs/verification-sync.js +21 -10
- package/dist/language/flows/flow-store-for-session.js +7 -5
- package/dist/language/flows/recording/tape/recordings.d.ts +2 -13
- package/dist/language/flows/recording/tape/recordings.js +41 -44
- package/dist/language/flows/replay.d.ts +17 -7
- package/dist/language/flows/replay.js +95 -44
- package/dist/launch-chromium.d.ts +1 -0
- package/dist/launch-chromium.js +49 -12
- package/dist/memory/cloud/sync-screenshots.js +6 -1
- package/dist/portal/bridge/bridge.d.ts +13 -4
- package/dist/portal/bridge/bridge.js +10 -9
- package/dist/portal/input/network-mock.js +3 -2
- package/dist/portal/input/real-input.d.ts +15 -0
- package/dist/portal/input/real-input.js +59 -3
- package/dist/portal/input/viewport-tools.js +6 -3
- package/dist/portal/pool/browser-pool.d.ts +5 -1
- package/dist/portal/pool/browser-pool.js +32 -38
- package/dist/portal/pool/context-permissions.d.ts +20 -17
- package/dist/portal/pool/context-permissions.js +63 -1
- package/dist/portal/pool/csp-widen.d.ts +62 -0
- package/dist/portal/pool/csp-widen.js +132 -0
- package/dist/portal/pool/lease-page-error.d.ts +29 -0
- package/dist/portal/pool/lease-page-error.js +18 -0
- package/dist/portal/pool/page-error-journal.d.ts +57 -0
- package/dist/portal/pool/page-error-journal.js +181 -0
- package/dist/portal/pool/playwright-launcher.js +21 -8
- package/dist/portal/pool/pool-contract.d.ts +22 -0
- package/dist/portal/pool/pool-lease.d.ts +35 -2
- package/dist/portal/pool/pool-lease.js +23 -0
- package/dist/portal/pool/zero-install.js +69 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +8 -0
- package/dist/portal/session/dev-server/dev-command.js +56 -8
- package/dist/portal/session/no-session-diagnosis.js +10 -3
- package/dist/portal/session/no-session-next-action.d.ts +12 -0
- package/dist/portal/session/no-session-next-action.js +44 -4
- package/dist/portal/session/no-session-watch.js +3 -1
- package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
- package/dist/portal/session/page-commands/undeclared-command.js +16 -7
- package/dist/portal/session/recorded-gaps.d.ts +1 -1
- package/dist/portal/session/recorded-gaps.js +5 -2
- package/dist/portal/session/session-manager.js +40 -21
- package/dist/portal/session/session.js +4 -3
- package/dist/portal/session/zero-install-first-call.d.ts +20 -1
- package/dist/portal/session/zero-install-first-call.js +34 -2
- package/dist/start-wiring.d.ts +29 -2
- package/dist/start-wiring.js +73 -9
- package/dist/surface/mcp/mcp-proxy.js +10 -2
- package/dist/surface/mcp/mcp.js +5 -3
- package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
- package/dist/surface/mcp/peer/agents-connected.js +58 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
- package/dist/surface/tools/act/act-preflight.js +37 -17
- package/dist/surface/tools/act/act-target.js +3 -1
- package/dist/surface/tools/act/after-match.d.ts +7 -0
- package/dist/surface/tools/act/after-match.js +33 -0
- package/dist/surface/tools/act/resolve-target.d.ts +2 -0
- package/dist/surface/tools/act/resolve-target.js +35 -8
- package/dist/surface/tools/act/sequence-grade.d.ts +3 -1
- package/dist/surface/tools/act/sequence-grade.js +8 -1
- package/dist/surface/tools/act-sequence-tool.js +3 -0
- package/dist/surface/tools/act-tools.js +10 -10
- package/dist/surface/tools/assert/assert-verdict.js +28 -1
- package/dist/surface/tools/browser-tools.js +75 -7
- package/dist/surface/tools/dynamic-tools.js +2 -1
- package/dist/surface/tools/empty-session-list.d.ts +2 -0
- package/dist/surface/tools/empty-session-list.js +4 -2
- package/dist/surface/tools/error-recovery.js +43 -22
- package/dist/surface/tools/invoke-tool.js +27 -20
- package/dist/surface/tools/lease-hint.d.ts +7 -0
- package/dist/surface/tools/lease-hint.js +10 -3
- package/dist/surface/tools/lease-readiness.d.ts +21 -0
- package/dist/surface/tools/lease-readiness.js +47 -0
- package/dist/surface/tools/lease-seed.d.ts +2 -0
- package/dist/surface/tools/lease-seed.js +2 -1
- package/dist/surface/tools/lease-tools.js +43 -15
- package/dist/surface/tools/repeat-hint.d.ts +40 -0
- package/dist/surface/tools/repeat-hint.js +171 -0
- package/dist/surface/tools/run-report.d.ts +2 -2
- package/dist/surface/tools/run-report.js +27 -15
- package/dist/surface/tools/tool-kit.d.ts +12 -0
- package/dist/surface/tools/tool-kit.js +7 -0
- package/dist/surface/tools/tools.js +26 -13
- package/dist/telemetry/argument-shape.js +16 -7
- package/dist/telemetry/cli-telemetry.d.ts +10 -1
- package/dist/telemetry/cli-telemetry.js +15 -0
- package/dist/telemetry/init-telemetry.d.ts +2 -1
- package/dist/telemetry/init-telemetry.js +47 -2
- package/dist/telemetry/install-trace.js +23 -4
- package/dist/telemetry/onboarding-funnel.d.ts +19 -2
- package/dist/telemetry/onboarding-funnel.js +25 -3
- package/dist/telemetry/proxy-telemetry.d.ts +2 -1
- package/dist/telemetry/proxy-telemetry.js +2 -2
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +67 -27
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +60 -28
- package/dist/telemetry/tool-refused.d.ts +2 -2
- package/dist/telemetry/tool-refused.js +9 -2
- package/docs/architecture.md +3 -6
- package/docs/cli/doctor.mdx +41 -2
- package/docs/cli/help.mdx +4 -2
- package/docs/cli/license.mdx +4 -5
- package/docs/cli/setup.mdx +13 -2
- package/docs/cli/status.mdx +4 -0
- package/docs/cli.mdx +1 -1
- package/docs/deploy-checks.md +25 -0
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +15 -20
- package/docs/environment.mdx +1 -0
- package/docs/first-drive.md +1 -1
- package/docs/install-manual.mdx +16 -1
- package/docs/packages/engine.mdx +1 -1
- package/docs/packages/server.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/test.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/platform-integration.md +1 -1
- package/docs/skill.md +1 -1
- package/docs/telemetry.md +4 -2
- package/package.json +13 -9
- package/LICENSE-ENTERPRISE +0 -37
- package/dist/features/ee/audit-log.d.ts +0 -14
- package/dist/features/ee/audit-log.js +0 -11
|
@@ -138,20 +138,24 @@ const Env = {
|
|
|
138
138
|
};
|
|
139
139
|
const isDisabled = (env, cwd = process.cwd()) => {
|
|
140
140
|
const off = new Set(['0', 'false', 'off', 'no']);
|
|
141
|
-
if (off.has((env[Env.DISABLE] ?? '').toLowerCase()))
|
|
141
|
+
if (off.has((env[Env.DISABLE] ?? '').toLowerCase())) {
|
|
142
142
|
return true;
|
|
143
|
+
}
|
|
143
144
|
// Recording to a local file is not "phoning home", and the guards below exist to stop us phoning
|
|
144
145
|
// home. Chief among them is the source-checkout rule — which is exactly where a release sweep is
|
|
145
146
|
// driven from, so a sink that inherited it would record nothing and look like it had worked.
|
|
146
|
-
if ((env[Env.FILE] ?? '') !== '')
|
|
147
|
+
if ((env[Env.FILE] ?? '') !== '') {
|
|
147
148
|
return false;
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
}
|
|
150
|
+
if (env[Env.VITEST] !== undefined) {
|
|
151
|
+
return true;
|
|
152
|
+
} // a test run is not a user
|
|
150
153
|
// Developing Reticle is not using Reticle. The `.env` carrying RETICLE_TELEMETRY=0 is gitignored,
|
|
151
154
|
// so it only exists on the machine that made it — a fresh clone would phone home on a
|
|
152
155
|
// contributor's first `reticle serve`. The repo marker is committed, so this guarantee travels.
|
|
153
|
-
if (isReticleSourceCheckout(cwd))
|
|
156
|
+
if (isReticleSourceCheckout(cwd)) {
|
|
154
157
|
return true;
|
|
158
|
+
}
|
|
155
159
|
const dnt = env[Env.DO_NOT_TRACK];
|
|
156
160
|
return 'string' === typeof dnt && dnt !== '' && dnt !== '0';
|
|
157
161
|
};
|
|
@@ -191,19 +195,22 @@ const readIdFile = (file) => {
|
|
|
191
195
|
export const resolveIdentity = (file = ID_FILE) => {
|
|
192
196
|
try {
|
|
193
197
|
const existing = readIdFile(file);
|
|
194
|
-
if (existing !== undefined)
|
|
198
|
+
if (existing !== undefined) {
|
|
195
199
|
return { anonymousId: existing, firstRun: false };
|
|
200
|
+
}
|
|
196
201
|
const id = randomUUID();
|
|
197
202
|
mkdirSync(dirname(file), { recursive: true });
|
|
198
203
|
try {
|
|
199
204
|
writeFileSync(file, id, { encoding: 'utf8', flag: 'wx' });
|
|
200
205
|
}
|
|
201
206
|
catch (error) {
|
|
202
|
-
if (!isAlreadyThere(error))
|
|
207
|
+
if (!isAlreadyThere(error)) {
|
|
203
208
|
throw error;
|
|
209
|
+
}
|
|
204
210
|
const raced = readIdFile(file);
|
|
205
|
-
if (raced !== undefined)
|
|
211
|
+
if (raced !== undefined) {
|
|
206
212
|
return { anonymousId: raced, firstRun: false };
|
|
213
|
+
}
|
|
207
214
|
// An empty file left by a crash: replace it, as the old code did.
|
|
208
215
|
writeFileSync(file, id, 'utf8');
|
|
209
216
|
}
|
|
@@ -235,30 +242,40 @@ export const projectFingerprint = (cwd) => {
|
|
|
235
242
|
// stable across every directory inside one machine's checkout, a package.json root is the same for
|
|
236
243
|
// a non-git project — and the raw cwd is stable across nothing, which is why it is last. See
|
|
237
244
|
// `project-identity.test.ts` for what a wrong order costs.
|
|
238
|
-
const { key, source } = origin
|
|
239
|
-
? { key: origin, source: ProjectIdSource.GIT_ORIGIN }
|
|
240
|
-
: root !== undefined
|
|
241
|
-
? { key: root, source: ProjectIdSource.GIT_ROOT }
|
|
242
|
-
: packageRoot(cwd) !== null
|
|
243
|
-
? { key: packageRoot(cwd) ?? cwd, source: ProjectIdSource.PACKAGE_ROOT }
|
|
244
|
-
: { key: cwd, source: ProjectIdSource.CWD };
|
|
245
|
+
const { key, source } = projectKey(cwd, origin, root);
|
|
245
246
|
return {
|
|
246
247
|
projectId: createHash('sha256').update(key).digest('hex').slice(0, 32),
|
|
247
248
|
// Reported alongside so the analytics knows whether this id is comparable to another machine's.
|
|
248
249
|
source,
|
|
249
250
|
};
|
|
250
251
|
};
|
|
252
|
+
/** The most stable key this directory has, and what it came from. */
|
|
253
|
+
function projectKey(cwd, origin, root) {
|
|
254
|
+
if (origin !== undefined) {
|
|
255
|
+
return { key: origin, source: ProjectIdSource.GIT_ORIGIN };
|
|
256
|
+
}
|
|
257
|
+
if (root !== undefined) {
|
|
258
|
+
return { key: root, source: ProjectIdSource.GIT_ROOT };
|
|
259
|
+
}
|
|
260
|
+
const pkg = packageRoot(cwd);
|
|
261
|
+
if (pkg !== null) {
|
|
262
|
+
return { key: pkg, source: ProjectIdSource.PACKAGE_ROOT };
|
|
263
|
+
}
|
|
264
|
+
return { key: cwd, source: ProjectIdSource.CWD };
|
|
265
|
+
}
|
|
251
266
|
/** Nearest ancestor holding a package.json — the project boundary when there is no git. */
|
|
252
267
|
const packageRoot = (cwd) => {
|
|
253
268
|
let dir = cwd;
|
|
254
269
|
// Same bound as the git walk: deep monorepos are well inside 20, and an unbounded loop on a
|
|
255
270
|
// pathological path would hang the CLI's startup.
|
|
256
271
|
for (let depth = 0; depth < 20; depth += 1) {
|
|
257
|
-
if (existsSync(join(dir, 'package.json')))
|
|
272
|
+
if (existsSync(join(dir, 'package.json'))) {
|
|
258
273
|
return dir;
|
|
274
|
+
}
|
|
259
275
|
const parent = dirname(dir);
|
|
260
|
-
if (parent === dir)
|
|
276
|
+
if (parent === dir) {
|
|
261
277
|
return null;
|
|
278
|
+
}
|
|
262
279
|
dir = parent;
|
|
263
280
|
}
|
|
264
281
|
return null;
|
|
@@ -329,18 +346,21 @@ export const createTelemetry = (opts) => {
|
|
|
329
346
|
const cwd = opts.cwd ?? process.cwd();
|
|
330
347
|
// A source checkout silences metrics but must not silence FEEDBACK — see onlyBlockedBySourceCheckout.
|
|
331
348
|
const feedbackOnly = onlyBlockedBySourceCheckout(env, cwd);
|
|
332
|
-
if (isDisabled(env, cwd) && !feedbackOnly)
|
|
349
|
+
if (isDisabled(env, cwd) && !feedbackOnly) {
|
|
333
350
|
return NOOP;
|
|
351
|
+
}
|
|
334
352
|
try {
|
|
335
|
-
if (existsSync(join(reticleDir, OPT_OUT_FILE_NAME)))
|
|
336
|
-
return NOOP;
|
|
353
|
+
if (existsSync(join(reticleDir, OPT_OUT_FILE_NAME))) {
|
|
354
|
+
return NOOP;
|
|
355
|
+
} // the persistent `reticle telemetry disable` opt-out
|
|
337
356
|
}
|
|
338
357
|
catch {
|
|
339
358
|
/* unreadable home dir — fall through; the env-var opt-outs above still apply */
|
|
340
359
|
}
|
|
341
360
|
const apiKey = env[Env.KEY] ?? POSTHOG_KEY;
|
|
342
|
-
if ('' === apiKey)
|
|
343
|
-
return NOOP;
|
|
361
|
+
if ('' === apiKey) {
|
|
362
|
+
return NOOP;
|
|
363
|
+
} // no key baked in (dev/test build) — nowhere to send, stay silent
|
|
344
364
|
const now = opts.now ?? (() => Date.now());
|
|
345
365
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
346
366
|
const url = `${(env[Env.URL] ?? DEFAULT_URL).replace(/\/+$/, '')}${TELEMETRY_PATH}`;
|
|
@@ -375,8 +395,9 @@ export const createTelemetry = (opts) => {
|
|
|
375
395
|
// How this process was launched: a property of the process, so read once. Only the bucket leaves.
|
|
376
396
|
const launch = launchMethodOf(process.argv[1]);
|
|
377
397
|
const home = opts.home ?? homedir();
|
|
378
|
-
if (!feedbackOnly)
|
|
398
|
+
if (!feedbackOnly) {
|
|
379
399
|
showNoticeOnce(reticleDir);
|
|
400
|
+
}
|
|
380
401
|
const spawnDetached = opts.spawnImpl ??
|
|
381
402
|
((command, args, input) => {
|
|
382
403
|
const bodyFile = join(tmpdir(), `reticle-telemetry-${randomUUID()}.json`);
|
|
@@ -397,8 +418,9 @@ export const createTelemetry = (opts) => {
|
|
|
397
418
|
const emit = async (kind, extra) => {
|
|
398
419
|
// In a Reticle source checkout the emitter carries FEEDBACK and nothing else: somebody typed the
|
|
399
420
|
// feedback, and no metric is worth phoning home from a contributor's clone.
|
|
400
|
-
if (feedbackOnly && TelemetryEventKind.FEEDBACK_SUBMITTED !== kind)
|
|
421
|
+
if (feedbackOnly && TelemetryEventKind.FEEDBACK_SUBMITTED !== kind) {
|
|
401
422
|
return false;
|
|
423
|
+
}
|
|
402
424
|
// One clock read for the whole event: `ts` and the licence check must agree, and two calls to
|
|
403
425
|
// now() can straddle an expiry.
|
|
404
426
|
const eventTs = now();
|
|
@@ -470,6 +492,7 @@ export const createTelemetry = (opts) => {
|
|
|
470
492
|
...(extra?.proxyExit !== undefined ? { proxyExit: extra.proxyExit } : {}),
|
|
471
493
|
...(extra?.daemonDeath !== undefined ? { daemonDeath: extra.daemonDeath } : {}),
|
|
472
494
|
...(extra?.wakeRefusal !== undefined ? { wakeRefusal: extra.wakeRefusal } : {}),
|
|
495
|
+
...(extra?.gate !== undefined ? { gate: extra.gate } : {}),
|
|
473
496
|
// A SCALAR: the event build and the wire schema, no `blocks` entry.
|
|
474
497
|
...(extra?.spawnMethod !== undefined ? { spawnMethod: extra.spawnMethod } : {}),
|
|
475
498
|
};
|
|
@@ -484,7 +507,7 @@ export const createTelemetry = (opts) => {
|
|
|
484
507
|
// PostHog filters and breakdowns operate on top-level properties, so a nested `feedback.stack`
|
|
485
508
|
// would be invisible to the exact "which stacks report the most bugs" question this exists to
|
|
486
509
|
// answer. One prefix keeps them grouped in the UI without a nested path.
|
|
487
|
-
const { anonymousId: distinctId, event: name, ts, sessionId: eventSessionId, feedback, session, project, verification, versionChange, crash, identity, connection, init, onboarding, bug, refusal, outage, instrumentation, hud, link, harness, proxyExit, daemonDeath, wakeRefusal, ...rest } = event;
|
|
510
|
+
const { anonymousId: distinctId, event: name, ts, sessionId: eventSessionId, feedback, session, project, verification, versionChange, crash, identity, connection, init, onboarding, bug, refusal, outage, instrumentation, hud, link, harness, proxyExit, daemonDeath, wakeRefusal, gate, ...rest } = event;
|
|
488
511
|
// `$session_id` is PostHog's OWN session property, so sending ours under that name lights up
|
|
489
512
|
// its built-in session views and funnels for free rather than requiring custom HogQL everywhere.
|
|
490
513
|
// `sessionId` rides only on events that happened inside a daemon run. A one-shot CLI command
|
|
@@ -522,6 +545,7 @@ export const createTelemetry = (opts) => {
|
|
|
522
545
|
proxyExit,
|
|
523
546
|
daemonDeath,
|
|
524
547
|
wakeRefusal,
|
|
548
|
+
gate,
|
|
525
549
|
};
|
|
526
550
|
for (const [prefix, block] of Object.entries(blocks)) {
|
|
527
551
|
for (const [key, value] of Object.entries(block ?? {})) {
|
|
@@ -583,8 +607,9 @@ export const createTelemetry = (opts) => {
|
|
|
583
607
|
return response.ok !== false;
|
|
584
608
|
}
|
|
585
609
|
catch (error) {
|
|
586
|
-
if (attempt === attempts - 1)
|
|
610
|
+
if (attempt === attempts - 1) {
|
|
587
611
|
throw error;
|
|
612
|
+
}
|
|
588
613
|
}
|
|
589
614
|
}
|
|
590
615
|
return false;
|
|
@@ -602,10 +627,17 @@ export const createTelemetry = (opts) => {
|
|
|
602
627
|
*/
|
|
603
628
|
let invalidDropped = 0;
|
|
604
629
|
export const droppedInvalidEvents = () => invalidDropped;
|
|
605
|
-
/** `RETICLE_INTERNAL
|
|
630
|
+
/** The values of `RETICLE_INTERNAL` that mark a run, after trim and lower case. Anything else is not. */
|
|
631
|
+
const INTERNAL_ON = new Set(['1', 'true', 'yes', 'on']);
|
|
632
|
+
/**
|
|
633
|
+
* The one decision for the `internal` field on every event: `RETICLE_INTERNAL` set to an "on" value,
|
|
634
|
+
* or the machine-wide marker file. An explicit signal only, never read from a name, email or path.
|
|
635
|
+
* Unreadable home → not internal.
|
|
636
|
+
*/
|
|
606
637
|
const isInternal = (env, dir) => {
|
|
607
|
-
if (
|
|
638
|
+
if (INTERNAL_ON.has((env[Env.INTERNAL] ?? '').trim().toLowerCase())) {
|
|
608
639
|
return true;
|
|
640
|
+
}
|
|
609
641
|
try {
|
|
610
642
|
return existsSync(join(dir, INTERNAL_MARKER_FILE));
|
|
611
643
|
}
|
|
@@ -15,7 +15,7 @@ import { RefusalReason, type NoSessionReason } from '@reticlehq/core/telemetry';
|
|
|
15
15
|
export declare function notePendingNoSessionReason(reason: NoSessionReason | undefined): void;
|
|
16
16
|
/** A call that was served. Breaks the retry chain: what follows it is not a retry of anything. */
|
|
17
17
|
export declare function noteToolServed(): void;
|
|
18
|
-
/** One refused call, reported with its cause
|
|
19
|
-
export declare function reportToolRefused(tool: string, reason: RefusalReason): void;
|
|
18
|
+
/** One refused call, reported with its cause, whether it was a retry, and the names of its arguments. */
|
|
19
|
+
export declare function reportToolRefused(tool: string, reason: RefusalReason, args?: Record<string, unknown>): void;
|
|
20
20
|
/** Tests only — the counters are process-lifetime, so each case has to start from zero. */
|
|
21
21
|
export declare function resetToolRefusals(): void;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* covered without anybody remembering to cover it.
|
|
12
12
|
*/
|
|
13
13
|
import { RefusalReason, TelemetryActor, TelemetryEventKind, } from '@reticlehq/core/telemetry';
|
|
14
|
+
import { describeToolParams } from './argument-shape.js';
|
|
14
15
|
import { getTelemetry } from './telemetry.js';
|
|
15
16
|
/**
|
|
16
17
|
* Refusal events one daemon run will send.
|
|
@@ -54,8 +55,10 @@ export function notePendingNoSessionReason(reason) {
|
|
|
54
55
|
export function noteToolServed() {
|
|
55
56
|
lastRefusedTool = undefined;
|
|
56
57
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
58
|
+
/** How many argument names one refusal carries. A call with more is malformed in its own way. */
|
|
59
|
+
const MAX_REFUSAL_PARAMS = 16;
|
|
60
|
+
/** One refused call, reported with its cause, whether it was a retry, and the names of its arguments. */
|
|
61
|
+
export function reportToolRefused(tool, reason, args = {}) {
|
|
59
62
|
const retried = lastRefusedTool === tool;
|
|
60
63
|
lastRefusedTool = tool;
|
|
61
64
|
// Read and clear whatever the throw left, whether or not this refusal can use it: a reason left
|
|
@@ -71,6 +74,10 @@ export function reportToolRefused(tool, reason) {
|
|
|
71
74
|
tool,
|
|
72
75
|
reason,
|
|
73
76
|
retried,
|
|
77
|
+
...(() => {
|
|
78
|
+
const params = describeToolParams(args).slice(0, MAX_REFUSAL_PARAMS);
|
|
79
|
+
return 0 === params.length ? {} : { params };
|
|
80
|
+
})(),
|
|
74
81
|
// Only where it means something. On any other refusal reason this field would be answering
|
|
75
82
|
// a question nobody asked of it.
|
|
76
83
|
...(RefusalReason.NO_SESSION === reason && noSessionReason !== undefined
|
package/docs/architecture.md
CHANGED
|
@@ -115,14 +115,11 @@ This is plain, reviewable, version-controllable data, not a black box.
|
|
|
115
115
|
|
|
116
116
|
---
|
|
117
117
|
|
|
118
|
-
##
|
|
118
|
+
## Licensing
|
|
119
119
|
|
|
120
|
-
-
|
|
121
|
-
- The embeddable **SDK** (`@reticlehq/browser`, `@reticlehq/react` and the build adapters) is **Apache-2.0**, safe to ship inside your own app.
|
|
122
|
-
- The **server / CLI** is under the **Functional Source License (FSL-1.1, Apache-2.0 future)**: source-available, converts to Apache-2.0 over time.
|
|
123
|
-
- Enterprise-only features live behind a license gate and are clearly separated.
|
|
120
|
+
Every npm package is **Apache-2.0**: the protocol and the rules (`open-verification`, `@reticlehq/core`, `@reticlehq/engine`), the embeddable SDK and its adapters, and the server, CLI, `init` and spec runner. The `reticle-tauri` crate is MIT.
|
|
124
121
|
|
|
125
|
-
See [LICENSE](https://github.com/reticlehq/reticle/blob/main/LICENSE) and each package's own `LICENSE` file.
|
|
122
|
+
See [LICENSE](https://github.com/reticlehq/reticle/blob/main/LICENSE) and each package's own `LICENSE` file.
|
|
126
123
|
|
|
127
124
|
---
|
|
128
125
|
|
package/docs/cli/doctor.mdx
CHANGED
|
@@ -7,13 +7,14 @@ icon: stethoscope
|
|
|
7
7
|
`reticle doctor` collapses the setup failure modes into one command. It is the first thing to run when something is wrong.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx @reticlehq/server doctor [--port N]
|
|
10
|
+
npx @reticlehq/server doctor [--fix] [--port N]
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Flags
|
|
14
14
|
|
|
15
15
|
| Flag | Type | Default | What it does |
|
|
16
16
|
| --- | --- | --- | --- |
|
|
17
|
+
| `--fix` | boolean | off | Repair what Reticle can repair safely, then run the checks again. See [Repair with --fix](#repair-with---fix) |
|
|
17
18
|
| `--port N` | number | `RETICLE_PORT`, else the port in `.reticle.json`, else the daemon already serving this project, else `4400` | Which bridge port to diagnose |
|
|
18
19
|
|
|
19
20
|
## What it prints
|
|
@@ -50,10 +51,12 @@ When nothing has connected, the `sessions` line fails and the daemon's own diagn
|
|
|
50
51
|
| Line | What it checks |
|
|
51
52
|
| --- | --- |
|
|
52
53
|
| `node` | The Node version running the CLI |
|
|
54
|
+
| `mcp` | Which coding agents found on this machine have Reticle's MCP server registered. Fails when an agent is found without it, and names the agent and `reticle setup mcp` |
|
|
53
55
|
| `chromium` | Whether the exact Chromium revision the bundled Playwright wants is on disk. The most common silent failure |
|
|
54
56
|
| `daemon` | Whether the port holds a Reticle daemon, a foreign process (named where possible), or nothing. Adds a `version` line when the daemon's version or contract fingerprint disagrees with the CLI's |
|
|
55
57
|
| `sessions` | Whether any page has actually connected. Zero is a FAILING check rather than a neutral note, because it is the state the setup most often stalls in: wired correctly, daemon up, nothing dialled in. When it is zero the daemon's own no-session diagnosis prints below the checklist |
|
|
56
58
|
| `agent link` | Whether an MCP client has listed Reticle's tools on this port **and actually called one**. Every other row here checks a component; this one checks the hop between your agent and the daemon. It is possible for the SDK to be injected, a session to be live, and your client to show every tool enabled, while no request has ever crossed from the agent. That state passes every other check and is the one people lose hours to |
|
|
59
|
+
| `launcher` | Only when an agent config starts Reticle from an installed copy (`node <path> mcp`) and that file does not exist, for example after `npm uninstall -g`. Names the agent, the path, and the command that writes a working entry |
|
|
57
60
|
| `loopback` | Which of `localhost`'s two answers reaches the bridge. Printed **only when they disagree**. The daemon binds `127.0.0.1`, and a best-effort alias forwards `[::1]` to it so the name works everywhere. When that alias cannot open (IPv6 disabled, or something already holding `[::1]` on the port) the daemon serves IPv4 perfectly and a page told to dial `localhost` still cannot reach it, because Windows Chrome tries the IPv6 answer first. Every other row here goes green in that state |
|
|
58
61
|
| `bridge port` | The port your app must dial, spelled out because mixing it up with the dev server port is the most common setup failure |
|
|
59
62
|
| `port check` | Only when your project's configured port disagrees with the one the daemon is on. Prints the mismatch rather than leaving you to spot it across two lines |
|
|
@@ -113,9 +116,45 @@ The wanted revision and the root are both read back off the path Playwright itse
|
|
|
113
116
|
before. That loop is why the line names the version to pin to.
|
|
114
117
|
</Warning>
|
|
115
118
|
|
|
119
|
+
## Repair with --fix
|
|
120
|
+
|
|
121
|
+
When a row has a safe repair, a plain `doctor` run ends with one more line:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
Run `reticle doctor --fix` to repair 2 of these.
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`reticle doctor --fix` prints the checks, applies the safe repairs in a fixed order, runs the checks again, and prints one line for each repair and one for each row that still fails. A repair changes only Reticle's own state:
|
|
128
|
+
|
|
129
|
+
| Order | Row | Repair | Done only when |
|
|
130
|
+
| --- | --- | --- | --- |
|
|
131
|
+
| 1 | `daemon` | Delete the pid file of a daemon that is not running | The process that the file names does not exist |
|
|
132
|
+
| 2 | `daemon` | Stop a Reticle daemon that holds the port and does not answer, as `reticle restart` does | The holder's command line is a Reticle daemon, and it is still silent after a grace period. A different program and the agent's own `reticle mcp` proxy are never stopped |
|
|
133
|
+
| 3 | `chromium` | Install the pinned Chromium of the bundled Playwright (a download) | No Chrome or Edge can stand in, and `RETICLE_CHROMIUM_PATH` is not set |
|
|
134
|
+
| 4 | `launcher` | Run `reticle setup mcp`, which replaces Reticle's own entry | An agent config starts Reticle from an installed copy that does not exist. `setup mcp` keeps an existing Claude Code entry, so for Claude Code the step stays `claude mcp remove reticle -s user`, then `setup mcp` |
|
|
135
|
+
| 5 | `mcp` | Run `reticle setup mcp` | An agent on this machine does not have Reticle registered |
|
|
136
|
+
|
|
137
|
+
Each repair checks again before it acts, so a second `--fix` finds nothing to do and changes nothing.
|
|
138
|
+
|
|
139
|
+
`--fix` never repairs a row that needs a change to your app, a process that Reticle cannot prove is its own daemon, or an action only you can do. For those rows it prints the step that the row names: for example `reticle init` for `project`, starting your agent for a stopped `daemon`, `reticle hook install` for `prompts`, a CSP or desktop change, or a version skew (restart the daemon when no other agent uses it).
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
reticle doctor --fix
|
|
143
|
+
daemon pid file of a stopped daemon: fixed
|
|
144
|
+
daemon a Reticle daemon that does not answer: nothing to do
|
|
145
|
+
chromium Playwright's own Chromium: nothing to do
|
|
146
|
+
launcher a registered launcher that does not exist: nothing to do
|
|
147
|
+
mcp agent registrations: fixed
|
|
148
|
+
still needs: sync ✗ off: this project is not linked: run `reticle connect`; no push yet
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
A registration takes effect when the agent reads its config again, so restart your agent after `mcp` is fixed.
|
|
152
|
+
|
|
116
153
|
## Exit codes
|
|
117
154
|
|
|
118
|
-
|
|
155
|
+
A plain `doctor` always exits `0`. It reports; it does not gate.
|
|
156
|
+
|
|
157
|
+
`doctor --fix` exits `0` only when no row fails after the repairs, and `1` otherwise.
|
|
119
158
|
|
|
120
159
|
## Worked example
|
|
121
160
|
|
package/docs/cli/help.mdx
CHANGED
|
@@ -33,7 +33,7 @@ Get started
|
|
|
33
33
|
try Watch Reticle drive a URL once and say which journeys work
|
|
34
34
|
|
|
35
35
|
Check your app
|
|
36
|
-
verify Check
|
|
36
|
+
verify Check saved flows, or one claim with --expect, against the running app; exit 0 = pass
|
|
37
37
|
gate Fail unless passing checks cover the flows your changes touch
|
|
38
38
|
|
|
39
39
|
Reticle on this machine
|
|
@@ -85,5 +85,7 @@ Two audiences, two channels: the structured line for logs and scripts, the plain
|
|
|
85
85
|
| `1` | Help was printed because something else was wrong |
|
|
86
86
|
|
|
87
87
|
<Note>
|
|
88
|
-
A bare `reticle` with no arguments is **not** help.
|
|
88
|
+
A bare `reticle` with no arguments is **not** help. In a wired project it is [`reticle
|
|
89
|
+
serve`](/cli/serve). In a folder that Reticle has not wired, it gives a first verdict on the app:
|
|
90
|
+
see [the CLI reference](/cli).
|
|
89
91
|
</Note>
|
package/docs/cli/license.mdx
CHANGED
|
@@ -20,17 +20,16 @@ One `reticle_license` JSON line, on stderr. On a checkout with no issuer key con
|
|
|
20
20
|
{
|
|
21
21
|
"t": "2026-08-15T18:42:26.758Z",
|
|
22
22
|
"event": "reticle_license",
|
|
23
|
-
"gated": ["audit-log"],
|
|
24
23
|
"contact": "hey@reticle.sh",
|
|
25
24
|
"status": "eval",
|
|
26
|
-
"detail": "evaluation mode
|
|
25
|
+
"detail": "evaluation mode (no issuer key configured)"
|
|
27
26
|
}
|
|
28
27
|
```
|
|
29
28
|
|
|
30
29
|
| `status` | Meaning |
|
|
31
30
|
| --------- | --------------------------------------------------- |
|
|
32
31
|
| `active` | A valid license is present |
|
|
33
|
-
| `eval` | Evaluation mode.
|
|
32
|
+
| `eval` | Evaluation mode. No issuer key is configured |
|
|
34
33
|
| `missing` | No license, and the build expects one |
|
|
35
34
|
| `expired` | The key verified, but its expiry has passed |
|
|
36
35
|
| `invalid` | The key was rejected, or the build is misconfigured |
|
|
@@ -47,6 +46,6 @@ Always `0`.
|
|
|
47
46
|
npx @reticlehq/server license 2>&1 | jq -r .status
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
<Card title="
|
|
51
|
-
|
|
49
|
+
<Card title="Enterprise" icon="building" href="/enterprise">
|
|
50
|
+
License keys, the roadmap, and the licensing model.
|
|
52
51
|
</Card>
|
package/docs/cli/setup.mdx
CHANGED
|
@@ -14,10 +14,21 @@ No flags, and nothing is asked. It works for your user account, not for one proj
|
|
|
14
14
|
|
|
15
15
|
## What it prints
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
A `Your agents:` block, with one line for each agent and the one next step for that agent:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
Your agents:
|
|
21
|
+
Claude Code plugin active: type /reload-plugins if it is open
|
|
22
|
+
Cursor added: restart Cursor, then turn Reticle on in Settings › MCP
|
|
23
|
+
Codex added: restart Codex, then /mcp shows reticle
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`already set up` means that Reticle was there already. `registration failed` gives the command to retry, and the command then exits `1`. `not added: add the reticle entry to <path> by hand` is for a config that it does not rewrite. If it finds no agent, it names every agent it looked for.
|
|
27
|
+
|
|
28
|
+
In a terminal, and not in CI, it then waits up to 60 seconds for each agent to connect. Then it prints `Connected: <agent> ✓`, or the step that is still missing for that agent. Press Ctrl+C to stop the wait. [`reticle status`](/cli/status) shows the same lines later.
|
|
18
29
|
|
|
19
30
|
## How you know it worked
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
Do the next step for your agent. Most agents read their server list only when they start. The Claude Code plugin loads with `/reload-plugins`. Then the `reticle_*` tools are listed, and `reticle status` shows `Connected: <agent> ✓`.
|
|
22
33
|
|
|
23
34
|
Codex CLI keeps a TOML config. When the `codex` binary is on your PATH, Reticle registers through `codex mcp add`. Otherwise it prints the hand-edit lines above. Reticle then adds one line, `default_tools_approval_mode = "approve"`, under `[mcp_servers.reticle]` in `~/.codex/config.toml`. This line approves Reticle's own tools, so Codex does not ask before each call. Reticle changes no other line, and it keeps a mode that you set. To undo it, change that line to `default_tools_approval_mode = "prompt"`. Reticle does not change a mode that is already there.
|
package/docs/cli/status.mdx
CHANGED
|
@@ -35,8 +35,12 @@ reticle status
|
|
|
35
35
|
http://localhost:5173/ (throttled, hidden)
|
|
36
36
|
http://localhost:7699/ (throttled, hidden)
|
|
37
37
|
agent link ✓ your agent has read and used Reticle's tools
|
|
38
|
+
agents Connected: Claude Code ✓
|
|
39
|
+
Cursor not connected yet: restart Cursor, then turn Reticle on in Settings › MCP
|
|
38
40
|
```
|
|
39
41
|
|
|
42
|
+
The `agents` row has one line for each coding agent on this machine. A line shows `✓` when that agent connected to Reticle at least one time. Otherwise it shows the step that is still missing. `reticle setup mcp` prints the same lines.
|
|
43
|
+
|
|
40
44
|
And with no daemon on the port:
|
|
41
45
|
|
|
42
46
|
```text
|
package/docs/cli.mdx
CHANGED
|
@@ -197,7 +197,7 @@ policy https://github.com/reticlehq/reticle/blob/main/docs/telemetry.md
|
|
|
197
197
|
| `reticle drive <url>` | Foreground mode, for debugging |
|
|
198
198
|
| `reticle mcp` | The MCP stdio proxy. Auto-starts the daemon if needed. This is what your agent runs |
|
|
199
199
|
|
|
200
|
-
A bare `reticle` with no arguments is not help.
|
|
200
|
+
A bare `reticle` with no arguments is not help. In a project that `init` wired, it is `reticle serve`. In a folder that Reticle has not wired, it gives a first verdict with no agent and no `init`. It finds the folder's web app (it starts the dev script when the app does not run), or the one dev server that runs on this computer. It opens the page in a Reticle browser and checks four things: the page loads, no console error, no failed request, and each same-origin link opens (8 at most). It prints `verified: yes` or `verified: no` with each fault, and exits 0 only for `yes`. A page that goes to a sign-in page is verified up to there. It stops the dev server it started.
|
|
201
201
|
|
|
202
202
|
<Note>
|
|
203
203
|
When the Reticle SDK installed in the project is a different **major** version from the CLI you ran,
|
package/docs/deploy-checks.md
CHANGED
|
@@ -12,6 +12,8 @@ That makes the non-developer story complete: the SDK is auto-injected by the bui
|
|
|
12
12
|
|
|
13
13
|
> **Recipe A works today; Recipe B does not exist yet.** `reticle verify` exits 0/1 and persists a run artifact right now. What is _not_ built is a hosted Reticle app that registers itself as a Vercel or Netlify check provider. Until it exists, use the CI recipe.
|
|
14
14
|
|
|
15
|
+
Using Reticle in CI or in a pipeline? Talk to a founder: [book a call](https://calendar.app.google/h9NRDbBBQetyTzWM6) or write to [hey@reticle.sh](mailto:hey@reticle.sh).
|
|
16
|
+
|
|
15
17
|
## The shape
|
|
16
18
|
|
|
17
19
|
```
|
|
@@ -54,6 +56,29 @@ The job's own pass/fail becomes the PR check. Nothing else is required.
|
|
|
54
56
|
|
|
55
57
|
**Non-loopback previews need pairing.** For a real preview URL (not `localhost`), Reticle injects `reticle.connect()` with a one-time token and allow-lists the preview origin, so the app does not need to be rebuilt per environment. Confirm the SDK actually runs on the deployed build: a production build that tree-shakes the dev-only SDK will connect to nothing, and `verify` will (correctly) fail with _"no app connected."_
|
|
56
58
|
|
|
59
|
+
## The Reticle GitHub Action
|
|
60
|
+
|
|
61
|
+
The Action replays your saved flows on each preview and posts one check with a verdict for each flow. On `deployment_status`, it reads the preview URL from the event, so you type no URL.
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
# .github/workflows/reticle.yml
|
|
65
|
+
name: reticle
|
|
66
|
+
on: [deployment_status]
|
|
67
|
+
jobs:
|
|
68
|
+
verify:
|
|
69
|
+
if: github.event.deployment_status.state == 'success'
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@v4
|
|
73
|
+
- uses: reticlehq/reticle/action@v3.9.0
|
|
74
|
+
with:
|
|
75
|
+
api-key: ${{ secrets.RETICLE_API_KEY }}
|
|
76
|
+
# Only for a Vercel preview behind Deployment Protection.
|
|
77
|
+
vercel-bypass-secret: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
A Vercel preview behind Deployment Protection needs a [Protection Bypass for Automation](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation) secret. Netlify has no bypass secret for its password protection, so turn off protection for deploy previews. The [Action README](https://github.com/reticlehq/reticle/tree/main/action) gives each input.
|
|
81
|
+
|
|
57
82
|
## Recipe B: the Checks API pattern (what a hosted Reticle would do)
|
|
58
83
|
|
|
59
84
|
Both providers expose the same shape, which is why this is one integration rather than two:
|
package/docs/desktop.mdx
CHANGED
package/docs/enterprise.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Enterprise
|
|
3
|
-
description: '
|
|
3
|
+
description: 'License keys and activation, the enterprise roadmap, the security and data-handling posture, and the licensing model.'
|
|
4
4
|
icon: building
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Every Reticle package is Apache-2.0. An enterprise customer gets a signed license key, which you set as `RETICLE_LICENSE_KEY`. Activation is verified locally with Ed25519 and makes **no network call**. Usage metrics are separate: they are on by default, and on a licensed build they carry your licence id. Turn them off with `RETICLE_TELEMETRY=0`. See [telemetry](/telemetry). Run `npx @reticlehq/server license` to see whether you are `active`, `eval`, `missing`, `expired` or `invalid`.
|
|
8
8
|
|
|
9
9
|
> For how to connect a platform to Reticle, see [Platform integration](/platform-integration).
|
|
10
10
|
|
|
11
11
|
> **Already have a key and just need to switch it on?** [Activating a license key](./license-activation) is the step-by-step version: where to set it on each platform, the editor and CI cases, how to read every status, and what to do when it does not work.
|
|
12
12
|
|
|
13
|
-
## How
|
|
13
|
+
## How a license key works (offline, no phone-home)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
No feature in the open packages needs a key. A key identifies your organisation to Reticle. Activation is verified locally with Ed25519 and sends nothing. Usage metrics, which you can turn off, carry your licence id on a licensed build.
|
|
16
16
|
|
|
17
17
|
**The flow, end to end:**
|
|
18
18
|
|
|
19
|
-
1. **Buy.** Contact **[hey@reticle.sh](mailto:hey@reticle.sh)**; we issue you a signed license key (license id, org, plan, expiry
|
|
19
|
+
1. **Buy.** Contact **[hey@reticle.sh](mailto:hey@reticle.sh)**; we issue you a signed license key (license id, org, plan, expiry). The **license id** is the stable handle on your subscription: it survives a rename, and it is what a renewal continues.
|
|
20
20
|
2. **Install.** Set it on the machine running the Reticle server:
|
|
21
21
|
```bash
|
|
22
22
|
export RETICLE_LICENSE_KEY="<your key>"
|
|
@@ -26,25 +26,21 @@ Enterprise (`ee/`) features ship **inside the open package**; they're source-ava
|
|
|
26
26
|
|
|
27
27
|
```
|
|
28
28
|
active licensed to Acme Corp (enterprise), expires 2027-06-20T00:00:00.000Z
|
|
29
|
-
eval evaluation mode
|
|
30
|
-
missing set RETICLE_LICENSE_KEY to activate
|
|
31
|
-
expired license expired. Renew with hey@reticle.sh
|
|
29
|
+
eval evaluation mode (no issuer key configured)
|
|
30
|
+
missing set RETICLE_LICENSE_KEY to activate a license. Request a key from hey@reticle.sh
|
|
31
|
+
expired license expired. Renew with hey@reticle.sh
|
|
32
32
|
invalid license key rejected (<reason>). hey@reticle.sh can re-issue it
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Every status reports `contact`, so a reader who is stuck always has somebody to write to.
|
|
35
|
+
Every status reports `contact`, so a reader who is stuck always has somebody to write to. A published release has the issuer key baked in. Only a build from source, with no issuer key, reports `eval`.
|
|
36
36
|
|
|
37
|
-
4. **
|
|
38
|
-
5. **Renew.** Keys carry an expiry, shown as `expiresAt`; once it passes, the status is `expired`.
|
|
37
|
+
4. **Renew.** Keys carry an expiry, shown as `expiresAt`; once it passes, the status is `expired`.
|
|
39
38
|
|
|
40
39
|
> Procuring a license: contact **[hey@reticle.sh](mailto:hey@reticle.sh)**. Keys are issued offline and signed with Ed25519; the activation you run (`reticle license`) verifies them locally with no network call.
|
|
41
40
|
|
|
42
|
-
##
|
|
41
|
+
## Roadmap
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- **Today:** the activation gate + one gated feature (audit event recording, `audit-log`).
|
|
47
|
-
- **Roadmap** (the reliably enterprise-only set): **SSO/SAML, SCIM, RBAC / team permissions, audit logs, multi-org management, verify-before-merge policy gates, and the hosted control-plane connectors.** These are the things a security/compliance org pays for; the core verification engine stays free forever.
|
|
43
|
+
**SSO/SAML, SCIM, RBAC / team permissions, audit logs, multi-org management, verify-before-merge policy gates, and the hosted control-plane connectors** are planned for the hosted Reticle platform, not for the open packages. The core verification engine stays free.
|
|
48
44
|
|
|
49
45
|
> For pricing, write to [hey@reticle.sh](mailto:hey@reticle.sh). This page describes the mechanism, not the price list.
|
|
50
46
|
|
|
@@ -62,15 +58,14 @@ The honest one-pager a security review needs. Reticle is built so the answer to
|
|
|
62
58
|
| Leak risk downstream? | The **`prod-preview` profile** redacts source `file:line`, raw bodies, and app-state values. |
|
|
63
59
|
| Path safety | Run/flow ids are validated as single path segments on read **and** write (no traversal). |
|
|
64
60
|
|
|
65
|
-
**Verify it yourself:** the SDK
|
|
61
|
+
**Verify it yourself:** the SDK and server are open source, so read the tree-shaking, the `127.0.0.1` bind, and the offline license verify. `SECURITY.md` has the disclosure process; an SBOM is available on request. SOC 2 is not in place; the posture above is the honest current state.
|
|
66
62
|
|
|
67
|
-
## Licensing model
|
|
63
|
+
## Licensing model
|
|
68
64
|
|
|
69
65
|
| Scope | License | Why |
|
|
70
66
|
| --- | --- | --- |
|
|
71
67
|
| The protocol and the rules (`open-verification`, `core`, `engine`) | **Apache-2.0** | the part that DECIDES a verdict, auditable and reimplementable by anyone |
|
|
72
68
|
| Embeddable SDK (`browser`, `react`, `babel-plugin`, `next`, `vite-plugin`, `electron`, `eslint-plugin`) | **Apache-2.0** | safe to ship inside your customers' apps; explicit patent grant |
|
|
73
|
-
| Server / CLI / MCP (`server`, `init`, `test`) | **
|
|
74
|
-
| Enterprise features (`server/src/features/ee/`) | **Reticle Enterprise License** | source-available; free for dev/eval, license key required in production |
|
|
69
|
+
| Server / CLI / MCP (`server`, `init`, `test`) | **Apache-2.0** | use, change and redistribute it anywhere |
|
|
75
70
|
|
|
76
71
|
Embedding / OEM / enterprise: **[hey@reticle.sh](mailto:hey@reticle.sh)**.
|
package/docs/environment.mdx
CHANGED
|
@@ -53,6 +53,7 @@ Reticle works with none of these set. Each one changes a single behaviour, and m
|
|
|
53
53
|
| Variable | Read by | What it does |
|
|
54
54
|
| --- | --- | --- |
|
|
55
55
|
| `RETICLE_SKIP_GATE` | `reticle gate --hook` | Lets an agent's stop hook proceed past a failing gate. The skip is printed and recorded, never silent. It does not affect `reticle gate` without `--hook`, which is what CI runs. |
|
|
56
|
+
| `RETICLE_NO_CI_HINT` | `reticle gate`, `reticle verify`, `reticle affected` | `1` stops the one line these commands write to stderr at the end of a run in a CI runner or a container. The line names the pull request check and `https://reticle.sh/ci`. It never prints in an interactive terminal or for `gate --hook`, and it makes no network call. |
|
|
56
57
|
| `RETICLE_VERIFY_TOKEN` | `reticle serve --http` | The bearer token the optional HTTP verify endpoint requires. |
|
|
57
58
|
| `RETICLE_SECRET_<FIELD>` | replay, verify, crawl | The value typed into a password or secret field at replay. A recorded flow never stores it. See [what is recorded](/what-is-recorded). |
|
|
58
59
|
|
package/docs/first-drive.md
CHANGED
|
@@ -12,7 +12,7 @@ Everything here is written from a run of these exact steps, including the places
|
|
|
12
12
|
## Before you start
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
node --version # 22.
|
|
15
|
+
node --version # 22.15 or newer
|
|
16
16
|
pnpm --version # this is a pnpm workspace; npm will not resolve it
|
|
17
17
|
```
|
|
18
18
|
|