openmerit 0.1.9 → 0.1.10
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/CHANGELOG.md +7 -0
- package/dist/core/src/index.d.ts +7 -7
- package/dist/pi/src/index.js +4 -4
- package/dist/protocol/src/index.d.ts +15 -15
- package/dist/terminal/scout-update.json +1 -0
- package/dist/terminal/src/cli.mjs +2 -1
- package/dist/terminal/src/install.mjs +49 -20
- package/dist/terminal/src/telemetry.mjs +12 -11
- package/dist/terminal/src/updates.d.ts +62 -0
- package/dist/terminal/src/updates.js +395 -0
- package/dist/terminal/src/updates.ts +312 -0
- package/docs/inference-terminal.md +2 -0
- package/docs/scout-data-policy.md +7 -5
- package/docs/scout.md +49 -10
- package/docs/security.md +5 -1
- package/docs/testing.md +1 -1
- package/package.json +12 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.10 — 2026-10-26
|
|
4
|
+
|
|
5
|
+
- Add default-on Claude Code background checks for compatible Scout skill/startup-rule updates, with daily checks, 15-minute failure backoff, non-blocking failure notices with the “update openmerit” fallback, manual `openmerit update`, persistent opt-out and a natural-language update instruction.
|
|
6
|
+
- Verify npm package integrity and read a bounded instruction bundle without executing downloaded code. Preserve edited files, credentials, MCP configuration, privacy settings and project policy; serialize maintenance and recover interrupted instruction writes.
|
|
7
|
+
- Disclose setup behavior, retain other Claude hooks/settings and require a new session to reliably load updated instructions. Existing installations need a one-time installer rerun after publication; incompatible releases require a reviewed manual upgrade. Hosted Scout and npm remain separate releases.
|
|
8
|
+
- Enable Scout setup usage/installation diagnostics by default, honoring telemetry, DNT and account-unlink opt-outs. Terminal/evaluation analytics remain opt-in; MCP prompt-content capture remains off.
|
|
9
|
+
|
|
3
10
|
## 0.1.9 — 2026-10-24
|
|
4
11
|
|
|
5
12
|
- Add opt-in coding-agent installation milestones, component/connection diagnostics and consent-gated setup/installation correlation retained across resumed approval and upgrades. Carry browser usage and unlink choices into setup; keep standalone CLI analytics default-off and MCP prompt-content capture off.
|
package/dist/core/src/index.d.ts
CHANGED
|
@@ -7,13 +7,13 @@ export interface HarnessAdapter {
|
|
|
7
7
|
scheduleWakeup?(request: HarnessWakeupRequest): Promise<HarnessDispatchReceipt>;
|
|
8
8
|
}
|
|
9
9
|
export interface IntentFactoryOptions {
|
|
10
|
-
readonly taskProfileId?: string;
|
|
10
|
+
readonly taskProfileId?: string | undefined;
|
|
11
11
|
readonly authorizationPolicyId?: string;
|
|
12
|
-
readonly policy?: SupervisedGraduationPolicy;
|
|
12
|
+
readonly policy?: SupervisedGraduationPolicy | undefined;
|
|
13
13
|
readonly id?: string;
|
|
14
14
|
readonly requestedAt?: string;
|
|
15
|
-
readonly evaluationSpend?: number;
|
|
16
|
-
readonly targetId?: string;
|
|
15
|
+
readonly evaluationSpend?: number | undefined;
|
|
16
|
+
readonly targetId?: string | undefined;
|
|
17
17
|
readonly additionalConstraints?: Readonly<Record<string, JsonValue>>;
|
|
18
18
|
}
|
|
19
19
|
export declare function createOpenMeritIntent(kind: IntentKind, options?: IntentFactoryOptions): OpenMeritIntent;
|
|
@@ -28,8 +28,8 @@ export interface VerificationResult {
|
|
|
28
28
|
}
|
|
29
29
|
export interface ResultInterpretation extends VerificationResult {
|
|
30
30
|
readonly nextStage?: ImprovementCycleState["stage"];
|
|
31
|
-
readonly config?: OpenMeritProjectConfig;
|
|
32
|
-
readonly cyclePatch?: Partial<ImprovementCycleState
|
|
31
|
+
readonly config?: OpenMeritProjectConfig | undefined;
|
|
32
|
+
readonly cyclePatch?: Partial<ImprovementCycleState> | undefined;
|
|
33
33
|
}
|
|
34
34
|
export declare function validateObservabilityCoverage(profile: TaskProfile, coverage: ObservabilityCoverage, requireReady: boolean): readonly string[];
|
|
35
35
|
export declare function interpretIntentResult(intent: OpenMeritIntent, result: AnyIntentResult, cycle: ImprovementCycleState, config: OpenMeritProjectConfig): ResultInterpretation;
|
|
@@ -52,7 +52,7 @@ export interface IssuedIntent {
|
|
|
52
52
|
readonly receipt: HarnessDispatchReceipt;
|
|
53
53
|
}
|
|
54
54
|
export interface CoordinatorIssueOptions {
|
|
55
|
-
readonly additionalConstraints?: Readonly<Record<string, JsonValue
|
|
55
|
+
readonly additionalConstraints?: Readonly<Record<string, JsonValue>> | undefined;
|
|
56
56
|
}
|
|
57
57
|
export interface AcceptedIntentResult {
|
|
58
58
|
readonly intentKind: IntentKind;
|
package/dist/pi/src/index.js
CHANGED
|
@@ -536,7 +536,7 @@ export default function openMeritExtension(pi) {
|
|
|
536
536
|
await dispatchIntent(pi, ctx, state, store, "establish_evals", registerCompletionTool, {
|
|
537
537
|
applicationTargetRequired: true,
|
|
538
538
|
detectedApplicationTargets: detectedTargetsConstraint(detectedApplicationTargets),
|
|
539
|
-
...(detectedApplicationTargets.length === 1 && detectedApplicationTargets[0]
|
|
539
|
+
...(detectedApplicationTargets.length === 1 && detectedApplicationTargets[0]?.configuredModelId ? { expectedIncumbentModelId: detectedApplicationTargets[0]?.configuredModelId } : {}),
|
|
540
540
|
});
|
|
541
541
|
});
|
|
542
542
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -573,7 +573,7 @@ export default function openMeritExtension(pi) {
|
|
|
573
573
|
await dispatchIntent(pi, ctx, state, store, "establish_evals", registerCompletionTool, {
|
|
574
574
|
applicationTargetRequired: true,
|
|
575
575
|
detectedApplicationTargets: detectedTargetsConstraint(detectedApplicationTargets),
|
|
576
|
-
...(detectedApplicationTargets.length === 1 && detectedApplicationTargets[0]
|
|
576
|
+
...(detectedApplicationTargets.length === 1 && detectedApplicationTargets[0]?.configuredModelId ? { expectedIncumbentModelId: detectedApplicationTargets[0]?.configuredModelId } : {}),
|
|
577
577
|
});
|
|
578
578
|
}
|
|
579
579
|
}
|
|
@@ -626,7 +626,7 @@ export default function openMeritExtension(pi) {
|
|
|
626
626
|
await dispatchIntent(pi, ctx, state, store, "establish_evals", registerCompletionTool, {
|
|
627
627
|
applicationTargetRequired: true,
|
|
628
628
|
detectedApplicationTargets: detectedTargetsConstraint(detectedApplicationTargets),
|
|
629
|
-
...(detectedApplicationTargets.length === 1 && detectedApplicationTargets[0]
|
|
629
|
+
...(detectedApplicationTargets.length === 1 && detectedApplicationTargets[0]?.configuredModelId ? { expectedIncumbentModelId: detectedApplicationTargets[0]?.configuredModelId } : {}),
|
|
630
630
|
});
|
|
631
631
|
}
|
|
632
632
|
else {
|
|
@@ -652,7 +652,7 @@ export default function openMeritExtension(pi) {
|
|
|
652
652
|
await dispatchIntent(pi, ctx, state, store, "establish_evals", registerCompletionTool, {
|
|
653
653
|
applicationTargetRequired: true,
|
|
654
654
|
detectedApplicationTargets: detectedTargetsConstraint(detectedApplicationTargets),
|
|
655
|
-
...(detectedApplicationTargets.length === 1 && detectedApplicationTargets[0]
|
|
655
|
+
...(detectedApplicationTargets.length === 1 && detectedApplicationTargets[0]?.configuredModelId ? { expectedIncumbentModelId: detectedApplicationTargets[0]?.configuredModelId } : {}),
|
|
656
656
|
});
|
|
657
657
|
}
|
|
658
658
|
return;
|
|
@@ -361,16 +361,16 @@ export declare const REQUIRED_CAPABILITIES: Readonly<Record<IntentKind, readonly
|
|
|
361
361
|
export type ImprovementStage = "unconfigured" | "establishing_evidence" | "collecting_baseline" | "baseline_ready" | "discovering_candidates" | "candidates_ready" | "running_challenger_trials" | "challengers_ready" | "calculating_frontier" | "frontier_ready" | "awaiting_swap_approval" | "swap_approved" | "applying_swap" | "swap_applied" | "verifying_swap" | "swap_verified" | "verification_failed" | "rolling_back" | "monitoring";
|
|
362
362
|
export interface ImprovementCycleState {
|
|
363
363
|
readonly id: string;
|
|
364
|
-
readonly targetId?: string;
|
|
365
|
-
readonly taskProfileId?: string;
|
|
364
|
+
readonly targetId?: string | undefined;
|
|
365
|
+
readonly taskProfileId?: string | undefined;
|
|
366
366
|
readonly stage: ImprovementStage;
|
|
367
|
-
readonly activeCandidateId?: string;
|
|
368
|
-
readonly previousCandidateId?: string;
|
|
369
|
-
readonly proposedCandidateId?: string;
|
|
367
|
+
readonly activeCandidateId?: string | undefined;
|
|
368
|
+
readonly previousCandidateId?: string | undefined;
|
|
369
|
+
readonly proposedCandidateId?: string | undefined;
|
|
370
370
|
readonly verifiedSwapCount: number;
|
|
371
371
|
readonly baselineEvidenceSufficient: boolean;
|
|
372
372
|
readonly observabilityReady?: boolean;
|
|
373
|
-
readonly policy?: SupervisedGraduationPolicy;
|
|
373
|
+
readonly policy?: SupervisedGraduationPolicy | undefined;
|
|
374
374
|
}
|
|
375
375
|
export interface OpenMeritProjectConfig {
|
|
376
376
|
readonly schemaVersion: 1;
|
|
@@ -382,13 +382,13 @@ export interface OpenMeritProjectConfig {
|
|
|
382
382
|
export interface OpenMeritProjectState {
|
|
383
383
|
readonly schemaVersion: 1;
|
|
384
384
|
readonly cycle: ImprovementCycleState;
|
|
385
|
-
readonly activeIntent?: OpenMeritIntent;
|
|
386
|
-
readonly lastResult?: IntentResult;
|
|
385
|
+
readonly activeIntent?: OpenMeritIntent | undefined;
|
|
386
|
+
readonly lastResult?: IntentResult | undefined;
|
|
387
387
|
/** Durable handoffs between discovery, trials, and frontier calculation. */
|
|
388
|
-
readonly candidates?: readonly CandidateDescriptor[];
|
|
389
|
-
readonly assessments?: readonly CandidateAssessment[];
|
|
390
|
-
readonly experimentManifest?: EvidenceReference;
|
|
391
|
-
readonly observability?: ObservabilityCoverage;
|
|
388
|
+
readonly candidates?: readonly CandidateDescriptor[] | undefined;
|
|
389
|
+
readonly assessments?: readonly CandidateAssessment[] | undefined;
|
|
390
|
+
readonly experimentManifest?: EvidenceReference | undefined;
|
|
391
|
+
readonly observability?: ObservabilityCoverage | undefined;
|
|
392
392
|
readonly automation: {
|
|
393
393
|
readonly paused?: boolean;
|
|
394
394
|
readonly setupNudgeShown: boolean;
|
|
@@ -397,9 +397,9 @@ export interface OpenMeritProjectState {
|
|
|
397
397
|
readonly modelCatalogFingerprint?: string;
|
|
398
398
|
readonly modelCatalogChangedAt?: string;
|
|
399
399
|
readonly processedSignalIds?: readonly string[];
|
|
400
|
-
readonly pendingIntentKind?: IntentKind;
|
|
401
|
-
readonly pendingIntentReason?: string;
|
|
402
|
-
readonly lastAssessmentAt?: string;
|
|
400
|
+
readonly pendingIntentKind?: IntentKind | undefined;
|
|
401
|
+
readonly pendingIntentReason?: string | undefined;
|
|
402
|
+
readonly lastAssessmentAt?: string | undefined;
|
|
403
403
|
readonly baselineStartedAt?: string;
|
|
404
404
|
readonly lastReassessmentTaskCount?: number;
|
|
405
405
|
readonly lastReassessmentAt?: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"format":1,"contract":"scout-recommend-v1","version":"0.1.10","skill":"---\nname: openmerit-scout\ndescription: Consults OpenMerit Scout before choosing, adding, downloading, replacing or configuring any application inference model, including implicit choices while building features, pretrained browser/local models, SDK defaults, embeddings, OCR, speech, classification, detection, segmentation, LLMs and agents. Not for unrelated edits, ordinary inference calls or this coding agent's own model.\n---\n\n# OpenMerit · Scout\n\nBefore introducing, recommending, replacing or configuring an application\ninference model, call the `scout_models` tool on the `openmerit` MCP server.\nThis applies even when the user never mentions models or Scout. Downloading local\nweights, using a pretrained browser model or accepting an SDK default is a model\ndecision: consult Scout before model-specific code, installs or downloads.\nPreserve the user's explicit model choice; assess it rather than silently replacing\nit. Leave unchanged, approved application selections in place.\n\n## Project policy\n\nRead `.openmerit/scout.json` before searching. If it is absent, ask once whether this\nproject uses **existing-only** (only endpoints it can already reach) or\n**existing-first** (prefer them, ask before searching the whole catalogue), and\nwhich public catalogue endpoint IDs it can access. Save the answer:\n\n```sh\nnpx -y openmerit@latest project --policy existing-only --endpoint-id <id> ...\n```\n\nNever infer access from a model name, SDK or environment-variable name. Never\nrecord credentials, private URLs, prompts or customer data. An empty inventory\nis not permission to search all models.\n\n## Searching\n\nCall `scout_models` with a non-confidential task description and\n`endpointIds` set to the saved inventory, including `[]` when empty.\nExisting-only never broadens. For existing-first, if no existing candidate fits,\nexplain the gap and ask before a separate search without `endpointIds`. Send\nonly task requirements, never source files, secrets or real customer records.\n\nCompare model AND endpoint, evidence, reference cost/time, unknowns and\nwarnings. For multiple tasks read `tasks[].slots[0].recommendations`; the\ntop-level list is empty. Prefer the cheapest usable option the quality evidence\nsupports, without claiming benchmarks prove application accuracy. Use the\nreturned provider model ID and the project's actual SDK and protocol.\n\n## Boundaries\n\nAsk before adding providers or credentials, spending, deploying or external\nwrites. Report recommendation, endpoint connectivity and application checks as\nseparate results, each only after it succeeds. Do not switch this coding agent's\nown model.\n\nIf Scout fails or has no suitable candidate/evidence for the required deployment,\nsay so and pause the model decision; do not substitute a remembered recommendation\nor SDK default. Continue only model-independent work. Local/browser models are not\nexempt when the catalogue lacks coverage. If it reports that the device key is missing,\nexpired or revoked, run `npx -y openmerit@latest login` and then\n`npx -y openmerit@latest install` again. Never ask the user to paste a key.\n","rule":"# Application model decisions · OpenMerit Scout\n\nBefore choosing, recommending, adding, downloading, replacing or configuring\nANY application inference model, load the `openmerit-scout` skill and consult\n`scout_models` on the `openmerit` MCP server before committing to a model.\nThis applies even when the user only asks you to build a feature, never mentions\nmodels or Scout, or names a model themselves (assess it; do not silently replace it).\n\nA pretrained model, local weights, browser inference, an SDK's default model,\nembeddings, OCR, speech, image classification, detection and segmentation all\ncount as model choices, not implementation details. Identify this decision before\nwriting model-specific code, installing a model package or downloading weights.\n\nFollow the skill's project access policy. If it is missing, ask and save it first.\nIf Scout is unavailable, fails or has no suitable evidence/candidate, disclose the\ngap and pause the model decision; do not fall back to a remembered or SDK-default\nmodel. You may continue work that does not commit to a model.\n\nDo not consult Scout for unrelated edits, normal inference calls using an unchanged\napproved model, or this coding agent's own model. Do not reselect approved models\nunless the task introduces or changes a model decision.\n\nWhen the user says \"update openmerit\", run\n`npx -y openmerit@latest update --scope user` (use `--scope project` if this\nrule is in the project's .claude/rules directory). Report the result, including\nany conflict or manual-upgrade requirement. Do not delete edited files, bypass\npermissions or rerun login automatically. A new session loads updated instructions.\n"}
|
|
@@ -11,6 +11,7 @@ const help = `OpenMerit · Inference Terminal
|
|
|
11
11
|
|
|
12
12
|
openmerit dash [path] [options]
|
|
13
13
|
openmerit install --help Connect OpenMerit to your coding agent
|
|
14
|
+
openmerit update --help Update installed Scout instructions
|
|
14
15
|
openmerit eval --help Evaluate CLI changes across coding agents
|
|
15
16
|
|
|
16
17
|
--port <number> Require a specific local port (default: 4310 or a free port)
|
|
@@ -29,7 +30,7 @@ Coding-harness usage is outside this terminal's scope.
|
|
|
29
30
|
|
|
30
31
|
async function main() {
|
|
31
32
|
const args = process.argv.slice(2);
|
|
32
|
-
if (['install', 'status', 'remove', 'login', 'logout', 'project', 'doctor'].includes(args[0])) { const { main } = await import('./install.mjs'); process.exitCode = await main(args); return; }
|
|
33
|
+
if (['install', 'status', 'remove', 'login', 'logout', 'project', 'doctor', 'update'].includes(args[0])) { const { main } = await import('./install.mjs'); process.exitCode = await main(args); return; }
|
|
33
34
|
if (args[0] === 'eval') { const { main } = await import('../../eval/src/cli.mjs'); await main(args.slice(1)); return; }
|
|
34
35
|
if (!args.length || args.includes('--help') || args.includes('-h')) { console.log(help); return; }
|
|
35
36
|
if (args.shift() !== 'dash') throw new Error('Unknown command. Run openmerit --help.');
|
|
@@ -5,7 +5,8 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
5
5
|
import { execFile } from 'node:child_process';
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
7
|
import { openBrowser } from './browser.mjs';
|
|
8
|
-
import { clientVersion, setupHeaders, setupUUID, sendInstallationEvent, setupCorrelationEnabled } from './telemetry.mjs';
|
|
8
|
+
import { clientVersion, setupHeaders, setupUUID, sendInstallationEvent, setupCorrelationEnabled, telemetryEnabled } from './telemetry.mjs';
|
|
9
|
+
import { configureUpdates, inspectUpdates, recoverUpdates, removeUpdates, runUpdate, withUpdateLock } from './updates.js';
|
|
9
10
|
|
|
10
11
|
// One-command coding-agent setup: device sign-in in the browser, then the MCP
|
|
11
12
|
// server, startup rule and Scout skill. Built to be run by a person or by a coding agent:
|
|
@@ -39,6 +40,12 @@ model. You may continue work that does not commit to a model.
|
|
|
39
40
|
Do not consult Scout for unrelated edits, normal inference calls using an unchanged
|
|
40
41
|
approved model, or this coding agent's own model. Do not reselect approved models
|
|
41
42
|
unless the task introduces or changes a model decision.
|
|
43
|
+
|
|
44
|
+
When the user says "update openmerit", run
|
|
45
|
+
\`npx -y openmerit@latest update --scope user\` (use \`--scope project\` if this
|
|
46
|
+
rule is in the project's .claude/rules directory). Report the result, including
|
|
47
|
+
any conflict or manual-upgrade requirement. Do not delete edited files, bypass
|
|
48
|
+
permissions or rerun login automatically. A new session loads updated instructions.
|
|
42
49
|
`;
|
|
43
50
|
|
|
44
51
|
export const skill = `---
|
|
@@ -102,16 +109,21 @@ expired or revoked, run \`npx -y openmerit@latest login\` and then
|
|
|
102
109
|
|
|
103
110
|
export const help = `OpenMerit · coding-agent setup
|
|
104
111
|
|
|
105
|
-
openmerit install [--client claude-code] [--scope user|project] [--setup-id UUID] [--plan] [--json] [--no-open]
|
|
112
|
+
openmerit install [--client claude-code] [--scope user|project] [--setup-id UUID] [--auto-update on|off] [--plan] [--json] [--no-open]
|
|
106
113
|
openmerit status [--client claude-code] [--scope user|project] [--check] [--json]
|
|
107
114
|
openmerit remove [--client claude-code] [--scope user|project] [--plan] [--json]
|
|
108
115
|
openmerit login [--setup-id UUID] [--json] [--no-open] Replace this device's key
|
|
109
116
|
openmerit logout [--json] Revoke this device's key
|
|
110
117
|
openmerit project --policy existing-only|existing-first [--endpoint-id <id> ...] [--plan]
|
|
111
118
|
openmerit doctor [--json]
|
|
119
|
+
openmerit update [--scope user|project] [--auto on|off] [--json]
|
|
112
120
|
|
|
113
121
|
install signs this device in through the browser (one Approve click), adds the
|
|
114
122
|
OpenMerit MCP server, startup rule and Scout skill, then verifies the connection.
|
|
123
|
+
Automatic Scout instruction updates default on; --auto-update off opts out.
|
|
124
|
+
Background SessionStart/Stop hooks check npm at most daily (15-minute failure
|
|
125
|
+
retry), verify package integrity, and preserve edited files and credentials.
|
|
126
|
+
Start a new Claude Code session to reliably load updated instructions.
|
|
115
127
|
It opens the approval page and waits up to 9 minutes for the Approve click
|
|
116
128
|
(30 seconds when no browser could be opened). If it exits with approval
|
|
117
129
|
pending, rerun the same command: it resumes the same request.
|
|
@@ -196,12 +208,13 @@ async function claude(args, root, key) {
|
|
|
196
208
|
async function inspect(root, scope) {
|
|
197
209
|
const paths = claudePaths(root, scope);
|
|
198
210
|
const [record, config, installedSkill, installedRule, legacyRule] = await Promise.all([object(paths.record), object(paths.mcpFile), optional(paths.skill), optional(paths.rule), optional(paths.legacyRule)]);
|
|
211
|
+
const updates = await inspectUpdates(dirname(paths.record), scope, record);
|
|
199
212
|
const owned = [2, 3].includes(record.version) && record.scope === scope;
|
|
200
213
|
const current = configuredServer(config, root, scope);
|
|
201
214
|
const otherScope = configuredServer(config, root, scope === 'user' ? 'project' : 'user');
|
|
202
215
|
const projectFile = await object(join(root, '.mcp.json'));
|
|
203
216
|
return {
|
|
204
|
-
paths, record, owned, current, installedSkill, installedRule,
|
|
217
|
+
paths, record, owned, current, installedSkill, installedRule, updates,
|
|
205
218
|
// A full pending hash covers interruption without accepting edited headers.
|
|
206
219
|
// Keep pendingAuth compatibility for records from the earlier installer.
|
|
207
220
|
serverOwned: !current || owned && (serverHash(current) === record.serverHash || serverHash(current) === record.pendingServerHash || !!record.pendingAuth && hash(current.headers?.Authorization ?? '') === record.pendingAuth),
|
|
@@ -214,7 +227,7 @@ async function inspect(root, scope) {
|
|
|
214
227
|
}
|
|
215
228
|
|
|
216
229
|
function conflicts(state) {
|
|
217
|
-
const found = [];
|
|
230
|
+
const found = [...state.updates.conflicts];
|
|
218
231
|
if (!state.serverOwned) found.push('Claude Code already has an MCP server named "openmerit" that this installer did not create or that was edited. Remove it with "claude mcp remove openmerit", then retry.');
|
|
219
232
|
if (!state.skillOwned) found.push(`${state.paths.skill} exists and was not created by this installer or was edited. Move or delete it, then retry.`);
|
|
220
233
|
if (!state.ruleOwned) found.push(`${state.paths.rule} exists and was not created by this installer or was edited. Move or delete it, then retry.`);
|
|
@@ -250,13 +263,12 @@ async function login({ client, scope, open, log, wait, telemetry }) {
|
|
|
250
263
|
}
|
|
251
264
|
const approvalURL = new URL(pending.url);
|
|
252
265
|
// Rebuild consent on every resume, so old query parameters cannot override a
|
|
253
|
-
// later refusal. Only
|
|
266
|
+
// later refusal. Only analytics-enabled, linked setup IDs reach the page.
|
|
254
267
|
for (const key of ['setup_id', 'analytics', 'analytics_account']) approvalURL.searchParams.delete(key);
|
|
255
268
|
if (setupCorrelationEnabled() && setupUUID(telemetry.properties.setup_id)) approvalURL.searchParams.set('setup_id', telemetry.properties.setup_id);
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
if (process.env.
|
|
259
|
-
if (process.env.OPENMERIT_ANALYTICS_ACCOUNT === 'off' || process.env.DO_NOT_TRACK === '1') approvalURL.searchParams.set('analytics_account', 'off');
|
|
269
|
+
// Preserve explicit refusals across surfaces; unlinking must not disable usage.
|
|
270
|
+
if (!telemetryEnabled(true)) approvalURL.searchParams.set('analytics', 'off');
|
|
271
|
+
if (process.env.OPENMERIT_ANALYTICS_ACCOUNT === 'off' || !telemetryEnabled(true)) approvalURL.searchParams.set('analytics_account', 'off');
|
|
260
272
|
pending = { ...pending, url: approvalURL.href, setup_id: telemetry.properties.setup_id, installation_id: telemetry.properties.installation_id };
|
|
261
273
|
await save(pendingPath(), pending);
|
|
262
274
|
log(`Approve this device in your browser (opening it now):\n\n ${pending.url}\n\n Code: ${pending.userCode} (expires ${new Date(pending.expiresAt).toLocaleTimeString()})\n`);
|
|
@@ -315,18 +327,22 @@ async function verify(key, telemetry) {
|
|
|
315
327
|
return connection;
|
|
316
328
|
}
|
|
317
329
|
|
|
318
|
-
async function install(root, { client, scope, plan, open, log, wait, telemetry }) {
|
|
330
|
+
async function install(root, { client, scope, plan, open, log, wait, telemetry, autoUpdate, json }) {
|
|
319
331
|
const state = await inspect(root, scope);
|
|
332
|
+
const enabled = autoUpdate === undefined ? state.updates.saved?.enabled ?? true : autoUpdate === 'on';
|
|
320
333
|
const found = conflicts(state);
|
|
321
334
|
const changes = [
|
|
322
335
|
`${state.current ? 'Update' : 'Add'} MCP server "openmerit" in Claude Code (${state.paths.mcpScope} scope, ${state.paths.mcpFile})`,
|
|
323
336
|
`${state.installedSkill === null ? 'Create' : 'Update'} ${state.paths.skill}`,
|
|
324
337
|
`${state.installedRule === null ? 'Create' : 'Update'} ${state.paths.rule} (loaded at session startup)`,
|
|
338
|
+
`Automatic Scout instruction updates ${enabled ? 'on' : 'off'} (SessionStart/Stop background hooks; disable with openmerit update --auto off)`,
|
|
339
|
+
`Write the owned updater at ${state.updates.paths.runner}; merge its hooks in ${state.updates.paths.settings}`,
|
|
325
340
|
...(state.legacyRule === 'remove' ? [`Remove the unchanged earlier instructions at ${state.paths.legacyRule}`] : []),
|
|
326
341
|
...(await credentials() ? [] : [`Sign this device in through the browser and store its key in ${credentialsPath()} (mode 600)`]),
|
|
327
342
|
];
|
|
328
343
|
if (plan) return { action: 'install', applied: false, client, scope, changes, conflicts: found };
|
|
329
344
|
if (found.length) throw new Stop(found.join('\n'), exitCodes.conflict, 'Nothing was changed.');
|
|
345
|
+
if (!json) log(changes.find(change => change.startsWith('Automatic Scout')));
|
|
330
346
|
await claude(['--version'], root);
|
|
331
347
|
if (setupCorrelationEnabled()) telemetry.properties.installation_id ??= randomUUID();
|
|
332
348
|
await telemetry.step('preflight', 'ok');
|
|
@@ -338,7 +354,7 @@ async function install(root, { client, scope, plan, open, log, wait, telemetry }
|
|
|
338
354
|
const next = serverConfig(key, telemetry);
|
|
339
355
|
const ids = { setup_id: telemetry.properties.setup_id, installation_id: telemetry.properties.installation_id };
|
|
340
356
|
// Record intent first so an interrupted install is repaired by rerunning.
|
|
341
|
-
await save(state.paths.record, { version: 3, scope, client, ...ids, serverHash: serverHash(state.current), skillHash: state.installedSkill === null ? null : hash(state.installedSkill), ruleHash: state.installedRule === null ? null : hash(state.installedRule), pendingServerHash: serverHash(next), pendingSkill: hash(skill), pendingRule: hash(rule) });
|
|
357
|
+
await save(state.paths.record, { version: 3, scope, client, ...ids, updates: state.record.updates, pendingUpdates: state.record.pendingUpdates, serverHash: serverHash(state.current), skillHash: state.installedSkill === null ? null : hash(state.installedSkill), ruleHash: state.installedRule === null ? null : hash(state.installedRule), pendingServerHash: serverHash(next), pendingSkill: hash(skill), pendingRule: hash(rule) });
|
|
342
358
|
telemetry.properties.applied = true;
|
|
343
359
|
if (serverHash(state.current) !== serverHash(next)) {
|
|
344
360
|
if (state.current) await claude(['mcp', 'remove', '--scope', state.paths.mcpScope, 'openmerit'], root, key);
|
|
@@ -350,7 +366,8 @@ async function install(root, { client, scope, plan, open, log, wait, telemetry }
|
|
|
350
366
|
// Record what Claude actually wrote, so later edits are detected precisely.
|
|
351
367
|
const written = configuredServer(await object(state.paths.mcpFile), root, scope);
|
|
352
368
|
if (!written) throw new Stop('Claude Code did not save the "openmerit" MCP server.', exitCodes.error, 'Run "openmerit doctor".');
|
|
353
|
-
await
|
|
369
|
+
const updates = await configureUpdates(dirname(state.paths.record), scope, state.record, clientVersion, enabled);
|
|
370
|
+
await save(state.paths.record, { version: 3, scope, client, ...ids, serverHash: serverHash(written), skillHash: hash(skill), ruleHash: hash(rule), updates, installedAt: new Date().toISOString() });
|
|
354
371
|
await telemetry.step('configuration', 'ok', diagnostics(await inspect(root, scope)));
|
|
355
372
|
const connection = await verify(key, telemetry);
|
|
356
373
|
if (connection !== 'connected') telemetry.outcome = 'error';
|
|
@@ -370,6 +387,8 @@ async function status(root, { client, scope, check, telemetry }) {
|
|
|
370
387
|
await telemetry.step('status', telemetry.outcome, { ...details, connection });
|
|
371
388
|
return { action: 'status', client, scope, installed: details.installed, signedIn: !!saved,
|
|
372
389
|
server: details.server_status, skill: details.skill_status, rule: details.rule_status,
|
|
390
|
+
autoUpdates: state.updates.saved?.enabled ?? false, instructionVersion: state.updates.saved?.version ?? null,
|
|
391
|
+
updateHooks: state.updates.saved?.enabled ? state.updates.active && !state.updates.conflicts.length ? 'ready' : 'incomplete' : 'disabled',
|
|
373
392
|
conflicts: found, connection };
|
|
374
393
|
}
|
|
375
394
|
|
|
@@ -396,13 +415,16 @@ async function remove(root, { client, scope, plan, telemetry }) {
|
|
|
396
415
|
}
|
|
397
416
|
const changes = [...(state.current && state.serverOwned ? [`Remove MCP server "openmerit" (${state.paths.mcpScope} scope)`] : []),
|
|
398
417
|
...(state.installedSkill !== null && state.skillOwned ? [`Delete ${state.paths.skill}`] : []),
|
|
399
|
-
...(state.installedRule !== null && state.ruleOwned ? [`Delete ${state.paths.rule}`] : []),
|
|
418
|
+
...(state.installedRule !== null && state.ruleOwned ? [`Delete ${state.paths.rule}`] : []),
|
|
419
|
+
...(state.updates.saved ? ['Remove unchanged owned update hooks and updater; retain edited hooks and their runner'] : []), 'Revoke and delete this device key'];
|
|
400
420
|
const kept = [...(state.serverOwned ? [] : ['the edited "openmerit" MCP server']), ...(state.skillOwned ? [] : [state.paths.skill]), ...(state.ruleOwned ? [] : [state.paths.rule]), policyPath];
|
|
421
|
+
kept.push(...state.updates.conflicts);
|
|
401
422
|
if (plan) return { action: 'remove', applied: false, client, scope, changes, keeps: kept };
|
|
402
423
|
telemetry.properties.applied = true;
|
|
403
424
|
if (state.current && state.serverOwned) await claude(['mcp', 'remove', '--scope', state.paths.mcpScope, 'openmerit'], root);
|
|
404
425
|
if (state.installedSkill !== null && state.skillOwned) await rm(dirname(state.paths.skill), { recursive: true, force: true });
|
|
405
426
|
if (state.installedRule !== null && state.ruleOwned) await rm(state.paths.rule, { force: true });
|
|
427
|
+
await removeUpdates(dirname(state.paths.record), scope, state.record);
|
|
406
428
|
await rm(state.paths.record, { force: true });
|
|
407
429
|
const revoked = await revokeKey(telemetry);
|
|
408
430
|
await telemetry.step('remove', telemetry.outcome, diagnostics(await inspect(root, scope)));
|
|
@@ -433,6 +455,7 @@ async function doctor(root, { scope, telemetry }) {
|
|
|
433
455
|
if (saved && reach.status === 200) { const connection = await verify(saved.key, telemetry); add('mcp', connection === 'connected', `MCP check: ${connection}`); }
|
|
434
456
|
const state = await inspect(root, scope).catch(() => null);
|
|
435
457
|
if (state) add('install', state.owned && !!state.current && state.installedSkill !== null && state.installedRule !== null && !conflicts(state).length, state.owned ? conflicts(state).join(' ') || (state.current && state.installedSkill !== null && state.installedRule !== null ? `Installed (${scope} scope)` : 'Installation incomplete. Rerun "openmerit install".') : `Not installed in ${scope} scope`);
|
|
458
|
+
if (state?.updates.saved?.enabled) add('updater', state.updates.active && !state.updates.conflicts.length, state.updates.active && !state.updates.conflicts.length ? 'Automatic instruction hooks installed' : 'Automatic instruction hooks missing or edited. Rerun install after reviewing conflicts.');
|
|
436
459
|
telemetry.outcome = checks.every(check => check.ok) ? 'ok' : 'error';
|
|
437
460
|
await telemetry.step('status', telemetry.outcome, { ...(state ? diagnostics(state) : {}), ...(reach.status === 200 ? {} : { code: 'openmerit_unreachable', connection: 'failed' }) });
|
|
438
461
|
return { action: 'doctor', ok: checks.every(check => check.ok), checks };
|
|
@@ -440,7 +463,8 @@ async function doctor(root, { scope, telemetry }) {
|
|
|
440
463
|
|
|
441
464
|
function render(result) {
|
|
442
465
|
if (result.action === 'doctor') return result.checks.map(c => `${c.ok ? '✓' : '✗'} ${c.name}: ${c.detail}`).join('\n');
|
|
443
|
-
if (result.action === 'status') return [`OpenMerit in ${clients[result.client]} (${result.scope} scope): ${result.installed ? 'installed' : 'not installed'}`, `Signed in: ${result.signedIn ? 'yes' : 'no'} · MCP server: ${result.server} · skill: ${result.skill} · rule: ${result.rule} · connection: ${result.connection}`, ...result.conflicts].join('\n');
|
|
466
|
+
if (result.action === 'status') return [`OpenMerit in ${clients[result.client]} (${result.scope} scope): ${result.installed ? 'installed' : 'not installed'}`, `Signed in: ${result.signedIn ? 'yes' : 'no'} · MCP server: ${result.server} · skill: ${result.skill} · rule: ${result.rule} · connection: ${result.connection}`, `Automatic instruction updates: ${result.autoUpdates ? 'on' : 'off'} · hooks: ${result.updateHooks} · instruction version: ${result.instructionVersion ?? 'bootstrap required'}`, ...result.conflicts].join('\n');
|
|
467
|
+
if (result.action === 'update') return result.message;
|
|
444
468
|
if (result.action === 'project') return `${result.applied ? 'Saved' : 'Would save'} ${result.path}: ${result.policy.policy}, ${result.policy.endpointIds.length} endpoint ID(s). ${result.note}`;
|
|
445
469
|
if (result.action === 'login') return result.note;
|
|
446
470
|
if (result.action === 'logout') return result.note;
|
|
@@ -459,7 +483,7 @@ export async function main(args, dependencies = {}) {
|
|
|
459
483
|
this.stage = stage;
|
|
460
484
|
if (outcome === 'ok') delete this.properties.code;
|
|
461
485
|
Object.assign(this.properties, properties);
|
|
462
|
-
await sendInstallationEvent({ ...this.properties, stage, outcome });
|
|
486
|
+
if (args[0] !== 'update') await sendInstallationEvent({ ...this.properties, stage, outcome });
|
|
463
487
|
},
|
|
464
488
|
async fail(error) {
|
|
465
489
|
this.outcome = ({ 10: 'pending', 11: 'denied', 20: 'conflict' })[error.exit] ?? 'error';
|
|
@@ -482,8 +506,8 @@ async function runMain(args, { root = process.cwd(), print = console.log, printE
|
|
|
482
506
|
const [action, ...rest] = args;
|
|
483
507
|
if (!action || rest.includes('--help') || rest.includes('-h')) { print(help); return exitCodes.ok; }
|
|
484
508
|
const options = { client: undefined, scope: 'user', plan: false, check: false, json: false, open: true, policy: undefined, ids: [], telemetry };
|
|
485
|
-
const allowed = { install: ['--client', '--scope', '--setup-id', '--plan', '--json', '--no-open'], status: ['--client', '--scope', '--check', '--json'], remove: ['--client', '--scope', '--plan', '--json'],
|
|
486
|
-
login: ['--setup-id', '--json', '--no-open'], logout: ['--json'], project: ['--policy', '--endpoint-id', '--plan', '--json'], doctor: ['--scope', '--json'] }[action];
|
|
509
|
+
const allowed = { install: ['--client', '--scope', '--setup-id', '--plan', '--json', '--no-open', '--auto-update'], status: ['--client', '--scope', '--check', '--json'], remove: ['--client', '--scope', '--plan', '--json'],
|
|
510
|
+
login: ['--setup-id', '--json', '--no-open'], logout: ['--json'], project: ['--policy', '--endpoint-id', '--plan', '--json'], doctor: ['--scope', '--json'], update: ['--scope', '--auto', '--json'] }[action];
|
|
487
511
|
if (!allowed) throw new Error('Unknown command. Run openmerit --help.');
|
|
488
512
|
for (let i = 0; i < rest.length; i++) {
|
|
489
513
|
const option = rest[i];
|
|
@@ -494,6 +518,7 @@ async function runMain(args, { root = process.cwd(), print = console.log, printE
|
|
|
494
518
|
else if (option === '--setup-id') { options.setup_id = value(); if (!setupUUID(options.setup_id)) throw new Error('--setup-id must be a UUID v4.'); }
|
|
495
519
|
else if (option === '--policy') options.policy = value();
|
|
496
520
|
else if (option === '--endpoint-id') options.ids.push(value());
|
|
521
|
+
else if (option === '--auto-update' || option === '--auto') { const setting = value(); if (!['on', 'off'].includes(setting)) throw new Error(`${option} needs on or off.`); options.autoUpdate = setting; }
|
|
497
522
|
else if (option === '--no-open') options.open = false;
|
|
498
523
|
else options[option.slice(2)] = true;
|
|
499
524
|
}
|
|
@@ -529,16 +554,20 @@ async function runMain(args, { root = process.cwd(), print = console.log, printE
|
|
|
529
554
|
}
|
|
530
555
|
await telemetry.step('started', 'started');
|
|
531
556
|
await telemetry.step('preflight', 'started');
|
|
532
|
-
const
|
|
557
|
+
const base = dirname(claudePaths(root, options.scope).record);
|
|
558
|
+
const maintenance = work => options.plan ? work() : withUpdateLock(base, async () => { await recoverUpdates(base, options.scope); return work(); });
|
|
559
|
+
const result = action === 'install' ? await maintenance(() => install(root, { ...options, log, wait }))
|
|
533
560
|
: action === 'status' ? await status(root, options)
|
|
534
|
-
: action === 'remove' ? await remove(root, options)
|
|
561
|
+
: action === 'remove' ? await maintenance(() => remove(root, options))
|
|
562
|
+
: action === 'update' ? { action, ...await runUpdate(base, options.scope, options.autoUpdate === undefined ? {} : { enabled: options.autoUpdate === 'on' }) }
|
|
535
563
|
: action === 'project' ? await project(root, options)
|
|
536
564
|
: action === 'doctor' ? await doctor(root, options)
|
|
537
565
|
: action === 'logout' ? { action, revoked: await revokeKey(telemetry), note: 'Signed out. This device key was revoked and deleted. Agents configured with it stop working until you run install again.' }
|
|
538
566
|
: (await revokeKey(telemetry), await login({ client: 'claude-code', scope: 'user', open: options.open, log, wait, telemetry }), { action, signedIn: true, note: 'Signed in. Run "openmerit install" again to give your coding agents the new key.' });
|
|
539
567
|
if (result.conflicts?.length && telemetry.outcome !== 'error') { telemetry.outcome = 'conflict'; telemetry.properties.code = 'install_conflict'; }
|
|
540
568
|
if (options.plan) await telemetry.step(telemetry.stage, telemetry.outcome, { applied: false });
|
|
541
|
-
print(options.json ? JSON.stringify({ ok:
|
|
569
|
+
print(options.json ? JSON.stringify({ ok: action !== 'update' || !['conflict', 'retry', 'manual_required'].includes(result.status), ...result }, null, 2) : render(result));
|
|
570
|
+
if (action === 'update') return result.status === 'conflict' ? exitCodes.conflict : result.status === 'retry' ? exitCodes.unreachable : result.status === 'manual_required' ? exitCodes.error : exitCodes.ok;
|
|
542
571
|
return action === 'doctor' && !result.ok ? exitCodes.error : exitCodes.ok;
|
|
543
572
|
} catch (error) {
|
|
544
573
|
if (!(error instanceof Stop)) throw error;
|
|
@@ -30,19 +30,19 @@ export function errorProperties(error) {
|
|
|
30
30
|
const environment = () => ({ ...(clientVersion ? { client_version: clientVersion } : {}), platform: ['darwin', 'linux', 'win32'].includes(process.platform) ? process.platform : 'unknown', runtime_major: Number(process.versions.node.split('.')[0]) });
|
|
31
31
|
|
|
32
32
|
const anonymousId = randomUUID();
|
|
33
|
-
export const telemetryEnabled = () => process.env.OPENMERIT_TELEMETRY === '1' && process.env.DO_NOT_TRACK !== '1';
|
|
34
|
-
export const setupCorrelationEnabled = () => telemetryEnabled() && process.env.OPENMERIT_ANALYTICS_ACCOUNT !== 'off';
|
|
33
|
+
export const telemetryEnabled = (defaultEnabled = false) => (process.env.OPENMERIT_TELEMETRY ? process.env.OPENMERIT_TELEMETRY === '1' : defaultEnabled) && process.env.DO_NOT_TRACK !== '1';
|
|
34
|
+
export const setupCorrelationEnabled = () => telemetryEnabled(true) && process.env.OPENMERIT_ANALYTICS_ACCOUNT !== 'off';
|
|
35
35
|
export const setupUUID = value => typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
36
|
-
//
|
|
37
|
-
//
|
|
36
|
+
// Scout setup defaults to analytics on; explicit privacy refusals must survive
|
|
37
|
+
// device approval and future agent sessions. Prompt content stays off.
|
|
38
38
|
export function setupHeaders(ids = {}, client = 'cli') {
|
|
39
39
|
return {
|
|
40
40
|
'X-Scout-Client': client,
|
|
41
41
|
'X-Scout-Agent': 'claude-code',
|
|
42
42
|
...(clientVersion ? { 'X-Scout-Client-Version': clientVersion } : {}),
|
|
43
43
|
'X-Scout-Analytics-Content': 'off',
|
|
44
|
-
...(client === 'cli' ? { 'X-Scout-Analytics': telemetryEnabled() ? 'on' : 'off' } :
|
|
45
|
-
...(process.env.OPENMERIT_ANALYTICS_ACCOUNT === 'off' ||
|
|
44
|
+
...(client === 'cli' ? { 'X-Scout-Analytics': telemetryEnabled(true) ? 'on' : 'off' } : !telemetryEnabled(true) ? { 'X-Scout-Analytics': 'off' } : {}),
|
|
45
|
+
...(process.env.OPENMERIT_ANALYTICS_ACCOUNT === 'off' || !telemetryEnabled(true) ? { 'X-Scout-Analytics-Account': 'off' } : {}),
|
|
46
46
|
...(setupCorrelationEnabled() && setupUUID(ids.setup_id) ? { 'X-Scout-Setup-Id': ids.setup_id } : {}),
|
|
47
47
|
...(setupCorrelationEnabled() && setupUUID(ids.installation_id) ? { 'X-Scout-Installation-Id': ids.installation_id } : {}),
|
|
48
48
|
};
|
|
@@ -71,7 +71,7 @@ export function installationProperties(input) {
|
|
|
71
71
|
return out;
|
|
72
72
|
}
|
|
73
73
|
export async function sendInstallationEvent(input) {
|
|
74
|
-
if (!telemetryEnabled()) return;
|
|
74
|
+
if (!telemetryEnabled(true)) return;
|
|
75
75
|
const properties = installationProperties(input);
|
|
76
76
|
if (!properties) return;
|
|
77
77
|
if (!setupCorrelationEnabled()) { delete properties.setup_id; delete properties.installation_id; }
|
|
@@ -83,7 +83,7 @@ export async function sendInstallationEvent(input) {
|
|
|
83
83
|
try {
|
|
84
84
|
await Promise.race([
|
|
85
85
|
(async () => {
|
|
86
|
-
for (let attempt = 0; attempt < 2 && !controller.signal.aborted && telemetryEnabled(); attempt++) {
|
|
86
|
+
for (let attempt = 0; attempt < 2 && !controller.signal.aborted && telemetryEnabled(true); attempt++) {
|
|
87
87
|
try {
|
|
88
88
|
const response = await fetch('https://openmerit.ai/scout/api/analytics', {
|
|
89
89
|
method: 'POST', headers: { 'Content-Type': 'application/json', ...setupHeaders(properties) },
|
|
@@ -150,15 +150,16 @@ async function sendAnalytics(event, properties) {
|
|
|
150
150
|
// Consent is independent of terminal discovery. Command identity is ephemeral;
|
|
151
151
|
// the installer separately manages the owned installation's correlation IDs.
|
|
152
152
|
export function commandTelemetry(args) {
|
|
153
|
-
|
|
153
|
+
const setup = ['install', 'status', 'remove', 'login', 'logout', 'project', 'doctor'];
|
|
154
|
+
const enabled = () => telemetryEnabled(setup.includes(args[0]));
|
|
155
|
+
if (!enabled()) return async () => {};
|
|
154
156
|
const started = performance.now();
|
|
155
157
|
const command_id = randomUUID();
|
|
156
158
|
const actions = ['init', 'doctor', 'plan', 'run', 'watch', 'report'];
|
|
157
|
-
const setup = ['install', 'status', 'remove', 'login', 'logout', 'project', 'doctor'];
|
|
158
159
|
const command = !args.length || args.includes('--help') || args.includes('-h') || args[0] === 'eval' && args.length === 1
|
|
159
160
|
? 'help' : args[0] === 'dash' ? 'dash' : setup.includes(args[0]) ? args[0] : args[0] === 'eval' && actions.includes(args[1]) ? `eval_${args[1]}` : 'unknown';
|
|
160
161
|
async function send(event, properties) {
|
|
161
|
-
if (!
|
|
162
|
+
if (!enabled()) return;
|
|
162
163
|
const controller = new AbortController();
|
|
163
164
|
const body = JSON.stringify({ event, event_id: randomUUID(), anonymousId, properties: { ...properties, ...environment(), command_id } });
|
|
164
165
|
let timer;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
type ObjectValue = Record<string, unknown>;
|
|
2
|
+
interface UpdateRecord {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
version: string;
|
|
5
|
+
runnerHash: string;
|
|
6
|
+
hook: ObjectValue;
|
|
7
|
+
}
|
|
8
|
+
export declare const updateContract = "scout-recommend-v1";
|
|
9
|
+
export declare function updatePaths(base: string, scope: string): {
|
|
10
|
+
record: string;
|
|
11
|
+
runner: string;
|
|
12
|
+
settings: string;
|
|
13
|
+
state: string;
|
|
14
|
+
backup: string;
|
|
15
|
+
skill: string;
|
|
16
|
+
rule: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function withUpdateLock<T>(base: string, work: () => Promise<T>): Promise<T>;
|
|
19
|
+
export declare function inspectUpdates(base: string, scope: string, record: ObjectValue): Promise<{
|
|
20
|
+
paths: {
|
|
21
|
+
record: string;
|
|
22
|
+
runner: string;
|
|
23
|
+
settings: string;
|
|
24
|
+
state: string;
|
|
25
|
+
backup: string;
|
|
26
|
+
skill: string;
|
|
27
|
+
rule: string;
|
|
28
|
+
};
|
|
29
|
+
saved: UpdateRecord | null;
|
|
30
|
+
runner: string | null;
|
|
31
|
+
settings: ObjectValue;
|
|
32
|
+
hooks: ObjectValue;
|
|
33
|
+
conflicts: string[];
|
|
34
|
+
active: boolean;
|
|
35
|
+
}>;
|
|
36
|
+
export declare function configureUpdates(base: string, scope: string, record: ObjectValue, version: string, enabled: boolean): Promise<{
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
version: string;
|
|
39
|
+
runnerHash: string;
|
|
40
|
+
hook: {
|
|
41
|
+
hooks: {
|
|
42
|
+
type: string;
|
|
43
|
+
command: string;
|
|
44
|
+
async: boolean;
|
|
45
|
+
timeout: number;
|
|
46
|
+
}[];
|
|
47
|
+
};
|
|
48
|
+
}>;
|
|
49
|
+
export declare function removeUpdates(base: string, scope: string, record: ObjectValue): Promise<void>;
|
|
50
|
+
export declare function recoverUpdates(base: string, scope: string): Promise<void>;
|
|
51
|
+
export interface UpdateResult {
|
|
52
|
+
status: string;
|
|
53
|
+
message: string;
|
|
54
|
+
version?: string;
|
|
55
|
+
}
|
|
56
|
+
export declare function runUpdate(base: string, scope: string, options?: {
|
|
57
|
+
hook?: boolean;
|
|
58
|
+
enabled?: boolean;
|
|
59
|
+
fetcher?: typeof fetch;
|
|
60
|
+
now?: number;
|
|
61
|
+
}): Promise<UpdateResult>;
|
|
62
|
+
export {};
|