openmerit 0.1.7 → 0.1.8
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 +6 -0
- package/dist/terminal/src/install.mjs +57 -20
- package/docs/scout.md +17 -5
- package/docs/security.md +2 -0
- package/docs/testing.md +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.8 — 2026-10-24
|
|
4
|
+
|
|
5
|
+
- Install an owned startup rule requiring Scout before explicit or implicit application-model choices, including pretrained/local/browser models, weight downloads and SDK defaults. Keep detailed access-policy and search instructions in the skill. These instructions are guidance, not hard enforcement.
|
|
6
|
+
- Upgrade unchanged skill-only installations without another device approval; include rule ownership, conflict detection, interrupted-update recovery, status, diagnostics and safe removal.
|
|
7
|
+
- Require a new Claude Code session after setup or upgrade; reconnecting `/mcp` alone does not reload startup instructions. Pause model selection when Scout is unavailable or lacks suitable candidates rather than falling back to a remembered model.
|
|
8
|
+
|
|
3
9
|
## 0.1.7 — 2026-10-24
|
|
4
10
|
|
|
5
11
|
- Release coding-agent device sign-in, Claude Code MCP/Scout skill installation, status, diagnostics, removal and project endpoint policy on the stable `latest` channel. Setup requires browser approval; it never requests a pasted key.
|
|
@@ -7,7 +7,7 @@ import { promisify } from 'node:util';
|
|
|
7
7
|
import { openBrowser } from './browser.mjs';
|
|
8
8
|
|
|
9
9
|
// One-command coding-agent setup: device sign-in in the browser, then the MCP
|
|
10
|
-
// server and Scout skill. Built to be run by a person or by a coding agent:
|
|
10
|
+
// server, startup rule and Scout skill. Built to be run by a person or by a coding agent:
|
|
11
11
|
// no interactive prompts, bounded waits, resumable logins and explicit exit codes.
|
|
12
12
|
export const exitCodes = { ok: 0, error: 1, pending: 10, denied: 11, conflict: 20, unreachable: 30 };
|
|
13
13
|
const exec = promisify(execFile);
|
|
@@ -15,18 +15,45 @@ const hash = value => createHash('sha256').update(value).digest('hex');
|
|
|
15
15
|
const policyPath = '.openmerit/scout.json';
|
|
16
16
|
const clients = { 'claude-code': 'Claude Code' };
|
|
17
17
|
|
|
18
|
+
// No paths frontmatter: Claude loads this before the first model decision,
|
|
19
|
+
// including user-scope sessions in Desktop's automatically created folders.
|
|
20
|
+
export const rule = `# Application model decisions · OpenMerit Scout
|
|
21
|
+
|
|
22
|
+
Before choosing, recommending, adding, downloading, replacing or configuring
|
|
23
|
+
ANY application inference model, load the \`openmerit-scout\` skill and consult
|
|
24
|
+
\`scout_models\` on the \`openmerit\` MCP server before committing to a model.
|
|
25
|
+
This applies even when the user only asks you to build a feature, never mentions
|
|
26
|
+
models or Scout, or names a model themselves (assess it; do not silently replace it).
|
|
27
|
+
|
|
28
|
+
A pretrained model, local weights, browser inference, an SDK's default model,
|
|
29
|
+
embeddings, OCR, speech, image classification, detection and segmentation all
|
|
30
|
+
count as model choices, not implementation details. Identify this decision before
|
|
31
|
+
writing model-specific code, installing a model package or downloading weights.
|
|
32
|
+
|
|
33
|
+
Follow the skill's project access policy. If it is missing, ask and save it first.
|
|
34
|
+
If Scout is unavailable, fails or has no suitable evidence/candidate, disclose the
|
|
35
|
+
gap and pause the model decision; do not fall back to a remembered or SDK-default
|
|
36
|
+
model. You may continue work that does not commit to a model.
|
|
37
|
+
|
|
38
|
+
Do not consult Scout for unrelated edits, normal inference calls using an unchanged
|
|
39
|
+
approved model, or this coding agent's own model. Do not reselect approved models
|
|
40
|
+
unless the task introduces or changes a model decision.
|
|
41
|
+
`;
|
|
42
|
+
|
|
18
43
|
export const skill = `---
|
|
19
44
|
name: openmerit-scout
|
|
20
|
-
description:
|
|
45
|
+
description: 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.
|
|
21
46
|
---
|
|
22
47
|
|
|
23
48
|
# OpenMerit · Scout
|
|
24
49
|
|
|
25
50
|
Before introducing, recommending, replacing or configuring an application
|
|
26
51
|
inference model, call the \`scout_models\` tool on the \`openmerit\` MCP server.
|
|
27
|
-
This applies even when the user never mentions Scout.
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
This applies even when the user never mentions models or Scout. Downloading local
|
|
53
|
+
weights, using a pretrained browser model or accepting an SDK default is a model
|
|
54
|
+
decision: consult Scout before model-specific code, installs or downloads.
|
|
55
|
+
Preserve the user's explicit model choice; assess it rather than silently replacing
|
|
56
|
+
it. Leave unchanged, approved application selections in place.
|
|
30
57
|
|
|
31
58
|
## Project policy
|
|
32
59
|
|
|
@@ -64,8 +91,10 @@ writes. Report recommendation, endpoint connectivity and application checks as
|
|
|
64
91
|
separate results, each only after it succeeds. Do not switch this coding agent's
|
|
65
92
|
own model.
|
|
66
93
|
|
|
67
|
-
If Scout fails
|
|
68
|
-
|
|
94
|
+
If Scout fails or has no suitable candidate/evidence for the required deployment,
|
|
95
|
+
say so and pause the model decision; do not substitute a remembered recommendation
|
|
96
|
+
or SDK default. Continue only model-independent work. Local/browser models are not
|
|
97
|
+
exempt when the catalogue lacks coverage. If it reports that the device key is missing,
|
|
69
98
|
expired or revoked, run \`npx -y openmerit@latest login\` and then
|
|
70
99
|
\`npx -y openmerit@latest install\` again. Never ask the user to paste a key.
|
|
71
100
|
`;
|
|
@@ -81,7 +110,7 @@ export const help = `OpenMerit · coding-agent setup
|
|
|
81
110
|
openmerit doctor [--json]
|
|
82
111
|
|
|
83
112
|
install signs this device in through the browser (one Approve click), adds the
|
|
84
|
-
OpenMerit MCP server and
|
|
113
|
+
OpenMerit MCP server, startup rule and Scout skill, then verifies the connection.
|
|
85
114
|
It opens the approval page and waits up to 9 minutes for the Approve click
|
|
86
115
|
(30 seconds when no browser could be opened). If it exits with approval
|
|
87
116
|
pending, rerun the same command: it resumes the same request.
|
|
@@ -134,12 +163,13 @@ function detectClient() {
|
|
|
134
163
|
}
|
|
135
164
|
|
|
136
165
|
// Claude Code locations. Project scope keeps the key out of the repository:
|
|
137
|
-
// the MCP entry uses Claude's private "local" scope
|
|
166
|
+
// the MCP entry uses Claude's private "local" scope; rule and skill are in the project.
|
|
138
167
|
function claudePaths(root, scope) {
|
|
139
168
|
const config = resolve(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'));
|
|
140
169
|
const base = scope === 'user' ? config : join(root, '.claude');
|
|
141
170
|
return {
|
|
142
171
|
skill: join(base, 'skills/openmerit-scout/SKILL.md'),
|
|
172
|
+
rule: join(base, 'rules/openmerit-scout.md'),
|
|
143
173
|
legacyRule: join(base, 'rules/openmerit.md'),
|
|
144
174
|
record: join(base, 'openmerit-install.json'),
|
|
145
175
|
mcpFile: process.env.CLAUDE_CONFIG_DIR ? join(config, '.claude.json') : join(homedir(), '.claude.json'),
|
|
@@ -164,16 +194,17 @@ async function claude(args, root, key) {
|
|
|
164
194
|
// Inspect the current Claude Code installation without changing anything.
|
|
165
195
|
async function inspect(root, scope) {
|
|
166
196
|
const paths = claudePaths(root, scope);
|
|
167
|
-
const [record, config, installedSkill, legacyRule] = await Promise.all([object(paths.record), object(paths.mcpFile), optional(paths.skill), optional(paths.legacyRule)]);
|
|
168
|
-
const owned = record.version
|
|
197
|
+
const [record, config, installedSkill, installedRule, legacyRule] = await Promise.all([object(paths.record), object(paths.mcpFile), optional(paths.skill), optional(paths.rule), optional(paths.legacyRule)]);
|
|
198
|
+
const owned = [2, 3].includes(record.version) && record.scope === scope;
|
|
169
199
|
const current = configuredServer(config, root, scope);
|
|
170
200
|
const otherScope = configuredServer(config, root, scope === 'user' ? 'project' : 'user');
|
|
171
201
|
const projectFile = await object(join(root, '.mcp.json'));
|
|
172
202
|
return {
|
|
173
|
-
paths, record, owned, current, installedSkill,
|
|
203
|
+
paths, record, owned, current, installedSkill, installedRule,
|
|
174
204
|
// A pending header hash covers an install interrupted between add and record.
|
|
175
205
|
serverOwned: !current || owned && (serverHash(current) === record.serverHash || !!record.pendingAuth && hash(current.headers?.Authorization ?? '') === record.pendingAuth),
|
|
176
206
|
skillOwned: installedSkill === null || owned && [record.skillHash, record.pendingSkill].includes(hash(installedSkill)),
|
|
207
|
+
ruleOwned: installedRule === null || owned && record.version === 3 && [record.ruleHash, record.pendingRule].includes(hash(installedRule)),
|
|
177
208
|
// The pre-release installer recorded its rule hash; only an unchanged rule is removed.
|
|
178
209
|
legacyRule: legacyRule !== null && record.version === 1 && hash(legacyRule) === record.ruleHash ? 'remove' : legacyRule !== null ? 'keep' : 'none',
|
|
179
210
|
shadowed: !!(otherScope || projectFile.mcpServers?.openmerit),
|
|
@@ -184,6 +215,7 @@ function conflicts(state) {
|
|
|
184
215
|
const found = [];
|
|
185
216
|
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.');
|
|
186
217
|
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.`);
|
|
218
|
+
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.`);
|
|
187
219
|
if (state.shadowed) found.push('An "openmerit" MCP server exists in another Claude Code scope (or in .mcp.json). Remove it, then retry.');
|
|
188
220
|
return found;
|
|
189
221
|
}
|
|
@@ -251,6 +283,7 @@ async function install(root, { client, scope, plan, open, log, wait }) {
|
|
|
251
283
|
const changes = [
|
|
252
284
|
`${state.current ? 'Update' : 'Add'} MCP server "openmerit" in Claude Code (${state.paths.mcpScope} scope, ${state.paths.mcpFile})`,
|
|
253
285
|
`${state.installedSkill === null ? 'Create' : 'Update'} ${state.paths.skill}`,
|
|
286
|
+
`${state.installedRule === null ? 'Create' : 'Update'} ${state.paths.rule} (loaded at session startup)`,
|
|
254
287
|
...(state.legacyRule === 'remove' ? [`Remove the unchanged earlier instructions at ${state.paths.legacyRule}`] : []),
|
|
255
288
|
...(await credentials() ? [] : [`Sign this device in through the browser and store its key in ${credentialsPath()} (mode 600)`]),
|
|
256
289
|
];
|
|
@@ -263,31 +296,33 @@ async function install(root, { client, scope, plan, open, log, wait }) {
|
|
|
263
296
|
key ??= await login({ client, scope, open, log, wait });
|
|
264
297
|
const next = serverConfig(key);
|
|
265
298
|
// Record intent first so an interrupted install is repaired by rerunning.
|
|
266
|
-
await save(state.paths.record, { version:
|
|
299
|
+
await save(state.paths.record, { version: 3, scope, client, serverHash: serverHash(state.current), skillHash: state.installedSkill === null ? null : hash(state.installedSkill), ruleHash: state.installedRule === null ? null : hash(state.installedRule), pendingAuth: hash(next.headers.Authorization), pendingSkill: hash(skill), pendingRule: hash(rule) });
|
|
267
300
|
if (state.current?.url !== next.url || state.current?.headers?.Authorization !== next.headers.Authorization) {
|
|
268
301
|
if (state.current) await claude(['mcp', 'remove', '--scope', state.paths.mcpScope, 'openmerit'], root, key);
|
|
269
302
|
await claude(['mcp', 'add-json', '--scope', state.paths.mcpScope, 'openmerit', JSON.stringify(next)], root, key);
|
|
270
303
|
}
|
|
271
304
|
await save(state.paths.skill, skill);
|
|
305
|
+
await save(state.paths.rule, rule);
|
|
272
306
|
if (state.legacyRule === 'remove') await rm(state.paths.legacyRule, { force: true });
|
|
273
307
|
// Record what Claude actually wrote, so later edits are detected precisely.
|
|
274
308
|
const written = configuredServer(await object(state.paths.mcpFile), root, scope);
|
|
275
309
|
if (!written) throw new Stop('Claude Code did not save the "openmerit" MCP server.', exitCodes.error, 'Run "openmerit doctor".');
|
|
276
|
-
await save(state.paths.record, { version:
|
|
310
|
+
await save(state.paths.record, { version: 3, scope, client, serverHash: serverHash(written), skillHash: hash(skill), ruleHash: hash(rule), installedAt: new Date().toISOString() });
|
|
277
311
|
const connection = await verify(key);
|
|
278
312
|
if (connection === 'key_rejected') throw new Stop('OpenMerit rejected this device key (expired or revoked).', exitCodes.denied, 'Run "openmerit login", then rerun install.');
|
|
279
313
|
return { action: 'install', applied: true, client, scope, changes, connection,
|
|
280
314
|
next: connection === 'connected'
|
|
281
|
-
? `Scout is ready in new ${clients[client]} sessions.
|
|
315
|
+
? `Scout is ready in new ${clients[client]} sessions. Start a new session to load the startup rule and updated skill; reconnecting /mcp alone does not reload instructions.`
|
|
282
316
|
: 'Installed, but the connection check failed. Run "openmerit doctor".' };
|
|
283
317
|
}
|
|
284
318
|
|
|
285
319
|
async function status(root, { client, scope, check }) {
|
|
286
320
|
const state = await inspect(root, scope), saved = await credentials();
|
|
287
|
-
const installed = state.owned && !!state.current && state.serverOwned && state.installedSkill !== null && state.skillOwned && !state.shadowed;
|
|
321
|
+
const installed = state.owned && !!state.current && state.serverOwned && state.installedSkill !== null && state.skillOwned && state.installedRule !== null && state.ruleOwned && !state.shadowed;
|
|
288
322
|
return { action: 'status', client, scope, installed, signedIn: !!saved,
|
|
289
323
|
server: state.current ? state.serverOwned ? 'matches' : 'modified_or_unowned' : 'missing',
|
|
290
324
|
skill: state.installedSkill === null ? 'missing' : state.skillOwned ? 'matches' : 'modified_or_unowned',
|
|
325
|
+
rule: state.installedRule === null ? 'missing' : state.ruleOwned ? 'matches' : 'modified_or_unowned',
|
|
291
326
|
conflicts: conflicts(state), connection: check && saved ? await verify(saved.key) : 'not_checked' };
|
|
292
327
|
}
|
|
293
328
|
|
|
@@ -303,11 +338,13 @@ async function remove(root, { client, scope, plan }) {
|
|
|
303
338
|
const state = await inspect(root, scope);
|
|
304
339
|
if (!state.owned) return { action: 'remove', applied: false, client, scope, changes: [], note: 'No OpenMerit installation by this installer in this scope.' };
|
|
305
340
|
const changes = [...(state.current && state.serverOwned ? [`Remove MCP server "openmerit" (${state.paths.mcpScope} scope)`] : []),
|
|
306
|
-
...(state.installedSkill !== null && state.skillOwned ? [`Delete ${state.paths.skill}`] : []),
|
|
307
|
-
|
|
341
|
+
...(state.installedSkill !== null && state.skillOwned ? [`Delete ${state.paths.skill}`] : []),
|
|
342
|
+
...(state.installedRule !== null && state.ruleOwned ? [`Delete ${state.paths.rule}`] : []), 'Revoke and delete this device key'];
|
|
343
|
+
const kept = [...(state.serverOwned ? [] : ['the edited "openmerit" MCP server']), ...(state.skillOwned ? [] : [state.paths.skill]), ...(state.ruleOwned ? [] : [state.paths.rule]), policyPath];
|
|
308
344
|
if (plan) return { action: 'remove', applied: false, client, scope, changes, keeps: kept };
|
|
309
345
|
if (state.current && state.serverOwned) await claude(['mcp', 'remove', '--scope', state.paths.mcpScope, 'openmerit'], root);
|
|
310
346
|
if (state.installedSkill !== null && state.skillOwned) await rm(dirname(state.paths.skill), { recursive: true, force: true });
|
|
347
|
+
if (state.installedRule !== null && state.ruleOwned) await rm(state.paths.rule, { force: true });
|
|
311
348
|
await rm(state.paths.record, { force: true });
|
|
312
349
|
const revoked = await revokeKey();
|
|
313
350
|
return { action: 'remove', applied: true, client, scope, changes, keeps: kept, keyRevoked: revoked };
|
|
@@ -335,13 +372,13 @@ async function doctor(root, { scope }) {
|
|
|
335
372
|
add('key', !!saved, saved ? `Device key ${saved.keyId ?? ''} stored in ${credentialsPath()}` : 'Not signed in. Run "openmerit install" or "openmerit login".');
|
|
336
373
|
if (saved && reach.status === 200) { const connection = await verify(saved.key); add('mcp', connection === 'connected', `MCP check: ${connection}`); }
|
|
337
374
|
const state = await inspect(root, scope).catch(() => null);
|
|
338
|
-
if (state) add('install', state.owned && !conflicts(state).length, state.owned ? conflicts(state).join(' ') || `Installed (${scope} scope)` : `Not installed in ${scope} scope`);
|
|
375
|
+
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`);
|
|
339
376
|
return { action: 'doctor', ok: checks.every(check => check.ok), checks };
|
|
340
377
|
}
|
|
341
378
|
|
|
342
379
|
function render(result) {
|
|
343
380
|
if (result.action === 'doctor') return result.checks.map(c => `${c.ok ? '✓' : '✗'} ${c.name}: ${c.detail}`).join('\n');
|
|
344
|
-
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} · connection: ${result.connection}`, ...result.conflicts].join('\n');
|
|
381
|
+
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');
|
|
345
382
|
if (result.action === 'project') return `${result.applied ? 'Saved' : 'Would save'} ${result.path}: ${result.policy.policy}, ${result.policy.endpointIds.length} endpoint ID(s). ${result.note}`;
|
|
346
383
|
if (result.action === 'login') return result.note;
|
|
347
384
|
if (result.action === 'logout') return result.note;
|
package/docs/scout.md
CHANGED
|
@@ -396,7 +396,7 @@ The capability index lists each area with its benchmark-series and model counts.
|
|
|
396
396
|
|
|
397
397
|
## Connect a coding agent
|
|
398
398
|
|
|
399
|
-
**Device sign-in setup — npm 0.1.
|
|
399
|
+
**Device sign-in setup — npm 0.1.8:** use the stable `latest` tag for the commands below. Version 0.1.8 adds the startup model-decision rule to the device setup introduced in 0.1.7. Hosted Scout serves `/connect` and `/scout/mcp` independently of npm. Claude Code is the only supported client; other agents are not yet supported. The older `0.1.7-preview.5` package does not contain these commands.
|
|
400
400
|
|
|
401
401
|
Setup takes four steps, and you act in each one only once:
|
|
402
402
|
|
|
@@ -408,13 +408,13 @@ Setup takes four steps, and you act in each one only once:
|
|
|
408
408
|
```
|
|
409
409
|
|
|
410
410
|
3. **Click Approve** on `https://openmerit.ai/connect`, which the command opens for you. Sign in first if needed. The page shows the client, the device name, the scope, an eight-character code that matches the command's output, and the only permission granted: Scout recommendations through MCP. Only approve a request you started yourself.
|
|
411
|
-
4. **Done.** The command has kept waiting, so nothing needs rerunning. It stores the device key, adds the `openmerit` MCP server with `claude mcp add-json`, writes the Scout skill and checks the connection with a real MCP `initialize` and tool discovery (not a search). The approval page switches to *Claude Code is connected* once the command has collected its key, and Claude Code reports the result.
|
|
411
|
+
4. **Done.** The command has kept waiting, so nothing needs rerunning. It stores the device key, adds the `openmerit` MCP server with `claude mcp add-json`, writes the Scout skill and startup rule, and checks the connection with a real MCP `initialize` and tool discovery (not a search). The approval page switches to *Claude Code is connected* once the command has collected its key, and Claude Code reports the result. **Start a new Claude Code session** to load the startup instructions; reconnecting `openmerit` in `/mcp` alone is not enough.
|
|
412
412
|
|
|
413
413
|
The prompt contains no key, account data or task draft. If the Claude desktop app isn't installed or your browser blocks the link, paste the copied prompt into Claude Code instead. The command needs `npx` (Node.js 22.19 or newer) and the `claude` CLI on the PATH of that session; the installer runs `claude mcp add-json`. Having the desktop app alone does not establish that those commands are available. See [Claude Desktop](https://code.claude.com/docs/en/desktop).
|
|
414
414
|
|
|
415
415
|
When the command can open a browser, it waits up to nine minutes for the Approve click. When it can't, for example over SSH, it prints the link and code and returns after 30 seconds with exit code 10 so the agent can show them. Rerunning the same command resumes the same request; it does not issue a new code. Codes expire after ten minutes and can be used once. `OPENMERIT_LOGIN_WAIT` sets the wait in seconds.
|
|
416
416
|
|
|
417
|
-
`--client` defaults to the agent running the command (Claude Code sets `CLAUDECODE=1`). `--scope` defaults to `user`, which makes Scout available in every project. `--scope project` writes the skill to `.claude/skills/` in the current project and stores the MCP server in Claude Code's private per-project (`local`) configuration, so the key never enters the repository. `--plan` lists the changes without making them. `--json` prints one result object for agents.
|
|
417
|
+
`--client` defaults to the agent running the command (Claude Code sets `CLAUDECODE=1`). `--scope` defaults to `user`, which makes Scout available in every project. `--scope project` writes the skill to `.claude/skills/` and startup rule to `.claude/rules/` in the current project and stores the MCP server in Claude Code's private per-project (`local`) configuration, so the key never enters the repository. `--plan` lists the changes without making them. `--json` prints one result object for agents.
|
|
418
418
|
|
|
419
419
|
| Exit code | Meaning |
|
|
420
420
|
|---|---|
|
|
@@ -431,6 +431,8 @@ When the command can open a browser, it waits up to nine minutes for the Approve
|
|
|
431
431
|
|
|
432
432
|
Each approval creates one key for one device and client, limited to `scout:recommend`. It is accepted only on `/scout/mcp`; the JSON API and browser still require a Clerk session. The server stores only SHA-256 hashes of keys and device codes. A key expires after 90 days without use; each use extends it. The usual per-user rate limits and analytics preferences apply.
|
|
433
433
|
|
|
434
|
+
Missing or invalid MCP credentials return HTTP 401 with `device_key_required`. The recovery command is `npx -y openmerit@latest install`; the older preview package does not include the installer.
|
|
435
|
+
|
|
434
436
|
On the device, the key is stored in `~/.config/openmerit/credentials.json` (mode 600; `OPENMERIT_CONFIG_DIR` or `XDG_CONFIG_HOME` override the location) and in Claude Code's MCP header configuration, which Claude Code stores in plain text. The CLI never prints the key and scrubs it from any failing `claude` output. Do not paste keys into a chat.
|
|
435
437
|
|
|
436
438
|
Revoke devices under **Connected devices** on `/connect`. `openmerit remove` and `openmerit logout` also revoke this device's key.
|
|
@@ -447,14 +449,24 @@ openmerit project --policy existing-only|existing-first [--endpoint-id <id> ...]
|
|
|
447
449
|
openmerit doctor # Node.js, claude, reachability, key, MCP check and install state
|
|
448
450
|
```
|
|
449
451
|
|
|
450
|
-
Removal deletes the MCP server, the unchanged skill and the install record, and revokes the key. It keeps edited files and `.openmerit/scout.json`.
|
|
452
|
+
Removal deletes the MCP server, the unchanged skill and startup rule, and the install record, and revokes the key. It keeps edited files and `.openmerit/scout.json`.
|
|
451
453
|
|
|
452
454
|
### Using Scout from the agent
|
|
453
455
|
|
|
454
|
-
|
|
456
|
+
Claude can load the `openmerit-scout` skill when choosing an application inference model, even if you do not mention Scout. The skill-only setup in npm 0.1.7 can miss implicit choices while building an app, including selecting pretrained browser models. Availability in the skill list does not prove invocation. Unrelated edits, ordinary inference calls and the coding agent's own model are outside its scope. On first use in a project, the skill instructs Claude to ask once whether the project is **existing-only** or **existing-first** and which public catalogue endpoint IDs it can reach, then save them with `openmerit project`. Every search passes the saved `endpointIds`, including `[]`. Existing-only never broadens; existing-first asks before an unscoped search. Task descriptions go to Scout and OpenAI; do not send source files, secrets or customer records.
|
|
455
457
|
|
|
456
458
|
The agent compares model and endpoint, evidence, reference cost/time and unknowns, and helps configure the chosen route. New providers, credentials, spending and deployments need separate approval. A skill is a default, not enforcement. The MCP tool, `scout_models`, is read-only and does not execute models.
|
|
457
459
|
|
|
460
|
+
### Startup model-decision rule — npm 0.1.8
|
|
461
|
+
|
|
462
|
+
The 0.1.8 installer adds `rules/openmerit-scout.md` alongside the skill. Unlike an on-demand skill, an unscoped [Claude Code rule](https://code.claude.com/docs/en/memory#organize-rules-with-claude/rules/) loads at session startup. The rule requires loading the skill and consulting Scout before any application-model choice, even when the user only asks for a feature. It explicitly includes pretrained/local/browser models, weight downloads, SDK defaults, embeddings, OCR, speech, classification, detection and segmentation. An explicit user model choice must be assessed, not silently replaced. Unchanged approved models and ordinary inference calls do not require another search.
|
|
463
|
+
|
|
464
|
+
Rerunning `npx -y openmerit@latest install --client claude-code` with version 0.1.8 or later upgrades an unchanged npm 0.1.7 installation without another device approval. User scope writes the rule under `~/.claude/rules/` (or `CLAUDE_CONFIG_DIR`) and covers new projects and Desktop's no-folder sessions. Project scope writes `.claude/rules/openmerit-scout.md` and applies only in that project. The existing skill, private MCP entry and key locations are unchanged. Start a **new Claude Code session** after installation or upgrade; reconnecting `/mcp` alone does not reload startup instructions.
|
|
465
|
+
|
|
466
|
+
The installer records rule ownership, repairs missing rules, resumes interrupted updates and refuses to overwrite edited or unowned rules. `--plan` includes the rule without writing it; `status` reports its state and `doctor` treats a missing rule as incomplete. `remove` deletes only the unchanged owned rule and retains edited rules and other user instructions.
|
|
467
|
+
|
|
468
|
+
If policy is missing, Claude must ask before searching. If Scout is unavailable or has no suitable evidence/candidate for the required deployment, the instructions require disclosing the gap and pausing model selection, not choosing from memory or an SDK default. Model-independent work may continue. Catalogue coverage is not guaranteed for local/browser models. These are behavioral instructions, **not hard enforcement**; Claude can still fail to follow them. Older 0.1.7 installations remain skill-only until upgraded; website deployment alone does not update an installed client.
|
|
469
|
+
|
|
458
470
|
## JSON API
|
|
459
471
|
|
|
460
472
|
```sh
|
package/docs/security.md
CHANGED
|
@@ -57,6 +57,8 @@ The [coding-agent setup](scout.md#connect-a-coding-agent) in npm 0.1.7 uses host
|
|
|
57
57
|
|
|
58
58
|
**Historical reset, superseded by npm 0.1.7 and hosted device setup.** The reset temporarily removed the installer and MCP transport. The current [Claude Code setup](scout.md#connect-a-coding-agent) uses browser-approved, device-specific keys scoped to MCP recommendations, not pasted session tokens or agent OAuth. The key is stored locally in mode-600 credentials and Claude Code's plain-text MCP configuration. Setup installs an owned Scout skill; conflicts are not overwritten, and removal revokes the device key. Browser Google/GitHub sign-in remains separate.
|
|
59
59
|
|
|
60
|
+
**npm 0.1.8:** setup also installs an owned, always-loaded `rules/openmerit-scout.md` instruction requiring Scout before implicit or explicit application-model choices, including pretrained/local/browser models and SDK defaults. Edited or unowned rules are not overwritten or removed. Missing policy requires asking; unavailable Scout or insufficient coverage requires pausing model selection. This is model guidance, not an enforced tool gate or authorization to download weights, add providers, spend or deploy. Scope, device-key permissions and task-data handling are unchanged. New sessions are required after upgrading; older npm 0.1.7 installations remain skill-only until upgraded.
|
|
61
|
+
|
|
60
62
|
Scout serving, main-site documentation and npm publication are separate releases. This reset does not revoke remote grants or clean up files previously installed on other machines. Earlier hosted MCP/authentication descriptions in this guide refer to past deployments, not this build. Browser Google/GitHub sign-in and its Clerk account lifecycle are separate and unchanged.
|
|
61
63
|
|
|
62
64
|
## Hosted Scout analytics
|
package/docs/testing.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Testing and validation
|
|
2
2
|
|
|
3
|
+
## Stable 0.1.7 acceptance — 24 October 2026
|
|
4
|
+
|
|
5
|
+
Published `openmerit@0.1.7` to npm's `latest` channel after full repository verification: 757 passing tests, one existing skip, syntax/type checks, builds and documentation checks. The `preview` tag remains `0.1.7-preview.5`. A fresh registry installation on macOS ARM64 with Node.js 22.23.1 matched the tested 104-file tarball byte-for-byte. Packed core/protocol/Pi exports and the installed terminal runtime smoke checks passed, including passive collection, source removal, runtime outage/recovery and shutdown.
|
|
6
|
+
|
|
7
|
+
The live Scout button opened a prefilled Claude Desktop Code session through Chrome. That session ran `npx -y openmerit@latest install --client claude-code`, opened the real device-approval page, and completed after approval of the matching code. This was a clean setup after removing and revoking this test's earlier source installation, not reuse of an existing key. Status then reported the MCP server and skill installed and connected; credentials had mode 600 and other MCP servers were preserved.
|
|
8
|
+
|
|
9
|
+
A fresh Desktop session received an application-model request that never mentioned Scout. It loaded `openmerit-scout` and called the real `scout_models` tool with the saved empty inventory, returning zero candidates. It asked before broadening; after separate approval, its second MCP call returned five candidates and it produced a provisional recommendation. No application code, providers, candidate inference or deployments were performed by that session. Anonymous MCP access and use of the device key on the JSON API both returned 401, while authenticated tool discovery succeeded.
|
|
10
|
+
|
|
11
|
+
These are integration observations, not a general guarantee that every agent follows the skill. Both search responses reported stale/degraded source data, including incomplete Parallel research. Their approximately 228,000- and 538,000-character payloads exceeded Desktop's inline tool limit; Claude recovered through saved tool-result files. The flow works, but these checks do not establish catalogue freshness, task accuracy or small response size. A separate recovery-path check exposed an obsolete preview install command in the MCP 401 message; its stable-command regression test failed before the source correction and passed afterward. Hosted correction is a separate deployment from the npm release.
|
|
12
|
+
|
|
3
13
|
## Published preview.5 acceptance — 17 October 2026
|
|
4
14
|
|
|
5
15
|
Published `0.1.7-preview.5` to npm's `preview` channel after all **679 repository tests**, syntax/type checks, builds and documentation checks passed, including 336 Scout tests. Registry integrity matches the reviewed tarball and stable `latest` remains `0.1.6`. All 101 packaged files are accounted for; runtime bytes, public exports and legal metadata are unchanged from preview.4. Version metadata and public guides carry this release. The frozen package contains the preparation record; this post-publication result is maintained here after the registry and VM checks.
|