@mnemonik/shared 7.99.17 → 7.101.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/dist/hostAdapter.d.ts +3 -0
- package/dist/hostAdapter.d.ts.map +1 -1
- package/dist/hostAdapter.js +85 -60
- package/dist/hostAdapter.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/readiness.d.ts +1 -1
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +1 -5
- package/dist/readiness.js.map +1 -1
- package/dist/runtimeSigners.d.ts +3 -0
- package/dist/runtimeSigners.d.ts.map +1 -1
- package/dist/runtimeSigners.js +11 -6
- package/dist/runtimeSigners.js.map +1 -1
- package/dist/testing/hostAdapterConformance.d.ts.map +1 -1
- package/dist/testing/hostAdapterConformance.js +55 -48
- package/dist/testing/hostAdapterConformance.js.map +1 -1
- package/package.json +1 -1
- package/src/hostAdapter.ts +110 -73
- package/src/index.ts +1 -0
- package/src/readiness.ts +1 -8
- package/src/runtimeSigners.ts +18 -6
- package/src/testing/hostAdapterConformance.ts +62 -53
- package/tests/readiness.test.ts +1 -2
package/src/readiness.ts
CHANGED
|
@@ -12,8 +12,6 @@ export type ReadinessConditionKind =
|
|
|
12
12
|
| 'scanner_not_verified'
|
|
13
13
|
| 'hook_not_verified'
|
|
14
14
|
| 'hooks_missing'
|
|
15
|
-
| 'host_grant_unbound'
|
|
16
|
-
| 'host_not_connected'
|
|
17
15
|
| 'scanner_omitted'
|
|
18
16
|
| 'project_uncovered'
|
|
19
17
|
| 'host_skipped'
|
|
@@ -177,8 +175,6 @@ const stateFor: Record<ReadinessConditionKind, Exclude<ReadinessState, 'READY'>>
|
|
|
177
175
|
scanner_not_verified: 'LIMITED',
|
|
178
176
|
hook_not_verified: 'LIMITED',
|
|
179
177
|
hooks_missing: 'ACTION_REQUIRED',
|
|
180
|
-
host_grant_unbound: 'LIMITED',
|
|
181
|
-
host_not_connected: 'LIMITED',
|
|
182
178
|
scanner_omitted: 'LIMITED',
|
|
183
179
|
project_uncovered: 'LIMITED',
|
|
184
180
|
host_skipped: 'LIMITED',
|
|
@@ -195,8 +191,6 @@ const rank: Record<ReadinessState, number> = {
|
|
|
195
191
|
};
|
|
196
192
|
|
|
197
193
|
function defaultAction(condition: ReadinessCondition): string | undefined {
|
|
198
|
-
if (condition.kind === 'host_skipped' && condition.component)
|
|
199
|
-
return `Connect it later: mnemonik connect ${condition.component}`;
|
|
200
194
|
if (condition.kind === 'windows_task_creation_failed')
|
|
201
195
|
return 'Run mnemonik scanner enable to try again.';
|
|
202
196
|
return condition.action;
|
|
@@ -225,8 +219,7 @@ export function remainingReadinessCount(summary: ReadinessSummary): number {
|
|
|
225
219
|
}
|
|
226
220
|
|
|
227
221
|
export function describeReadiness(summary: ReadinessSummary): string {
|
|
228
|
-
if (summary.state === 'READY')
|
|
229
|
-
return 'Done. Your editors will use Mnemonik on their next session.';
|
|
222
|
+
if (summary.state === 'READY') return 'Done.';
|
|
230
223
|
const count = remainingReadinessCount(summary);
|
|
231
224
|
const opening =
|
|
232
225
|
summary.state === 'FAILED'
|
package/src/runtimeSigners.ts
CHANGED
|
@@ -33,11 +33,15 @@ function decodeBase64(text: string, length: number): Buffer {
|
|
|
33
33
|
return bytes;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
/** Pinned release identity; a downloaded manifest can never replace its own trust anchor. */
|
|
37
|
+
export const RELEASE_MINISIGN_PUBLIC_KEY =
|
|
38
|
+
'RWSBYwgbjz0qKB4/ToA3dywBiAlQeBjoQpib4OHdjP2nDbpQnOVqpPm0';
|
|
39
|
+
|
|
40
|
+
export function verifyMinisign(
|
|
41
|
+
message: Buffer,
|
|
42
|
+
signatureText: string,
|
|
43
|
+
identity = RELEASE_MINISIGN_PUBLIC_KEY
|
|
44
|
+
): void {
|
|
41
45
|
const publicPacket = decodeBase64(identity, 42);
|
|
42
46
|
const lines = signatureText.trimEnd().split(/\r?\n/);
|
|
43
47
|
const [untrustedComment, encodedSignature, trustedLine, encodedGlobalSignature] = lines;
|
|
@@ -79,6 +83,14 @@ async function verifyMinisign(path: string, signaturePath: string, identity: str
|
|
|
79
83
|
throw new Error('unsigned');
|
|
80
84
|
}
|
|
81
85
|
|
|
86
|
+
async function verifyMinisignFile(path: string, signaturePath: string, identity: string) {
|
|
87
|
+
const [message, signatureText] = await Promise.all([
|
|
88
|
+
readFile(path),
|
|
89
|
+
readFile(signaturePath, 'utf8'),
|
|
90
|
+
]);
|
|
91
|
+
verifyMinisign(message, signatureText, identity);
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
/** Release tooling supplies the real identity and artifacts. These checks do not sign anything. */
|
|
83
95
|
export async function verifySigner(path: string, signer: Signer, run = execute): Promise<void> {
|
|
84
96
|
if (signer.platform === 'darwin' && /^[A-Z0-9]{10}$/.test(signer.identity)) {
|
|
@@ -104,7 +116,7 @@ export async function verifySigner(path: string, signer: Signer, run = execute):
|
|
|
104
116
|
]);
|
|
105
117
|
} else if (signer.platform === 'linux') {
|
|
106
118
|
try {
|
|
107
|
-
await
|
|
119
|
+
await verifyMinisignFile(path, signer.signature, signer.identity);
|
|
108
120
|
} catch {
|
|
109
121
|
throw new Error('unsigned');
|
|
110
122
|
}
|
|
@@ -53,6 +53,7 @@ export function hostAdapterConformance(
|
|
|
53
53
|
runtimeRoot,
|
|
54
54
|
credentialFamily: 'hook-family',
|
|
55
55
|
runtimeEntry: join(runtimeRoot, '1.0.0', 'dist', 'hook.js'),
|
|
56
|
+
installationId: '11111111-1111-4111-8111-111111111111',
|
|
56
57
|
};
|
|
57
58
|
const deps: AdapterDependencies = {
|
|
58
59
|
target,
|
|
@@ -391,55 +392,10 @@ export function hostAdapterConformance(
|
|
|
391
392
|
assert.deepEqual(await readFile(configPath('user')), before);
|
|
392
393
|
}
|
|
393
394
|
);
|
|
394
|
-
check(
|
|
395
|
-
'parses connected listings without inferring account and uses execFile arguments',
|
|
396
|
-
async ({ deps, project, target }) => {
|
|
397
|
-
let stdout = 'mnemonik: https://api.mnemonik.dev/mcp - ✔ Connected\n';
|
|
398
|
-
const calls: unknown[][] = [];
|
|
399
|
-
const host = create({
|
|
400
|
-
...deps,
|
|
401
|
-
target: { ...target, component: 'mcp' },
|
|
402
|
-
execFile: async (...args) => {
|
|
403
|
-
if (args[1][0] === '--version') return { stdout: versionOutput, stderr: '' };
|
|
404
|
-
calls.push(args);
|
|
405
|
-
return { stdout, stderr: '' };
|
|
406
|
-
},
|
|
407
|
-
});
|
|
408
|
-
const inspection = await host.verify();
|
|
409
|
-
if (options.name === 'cursor') {
|
|
410
|
-
assert.equal(inspection.authenticatedTools, false);
|
|
411
|
-
assert.equal(inspection.grant, undefined);
|
|
412
|
-
assert.deepEqual(calls, []);
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
assert.equal(inspection.authenticatedTools, true);
|
|
416
|
-
assert.equal(inspection.grant, undefined);
|
|
417
|
-
assert.deepEqual(calls[0], [
|
|
418
|
-
options.name === 'claude-code'
|
|
419
|
-
? 'claude'
|
|
420
|
-
: options.name === 'cursor'
|
|
421
|
-
? 'agent'
|
|
422
|
-
: options.name,
|
|
423
|
-
['mcp', 'list'],
|
|
424
|
-
{ env: deps.env, cwd: project, timeout: 5000, maxBuffer: 64 * 1024, windowsHide: true },
|
|
425
|
-
]);
|
|
426
|
-
for (const listing of [
|
|
427
|
-
'mnemonik: not connected',
|
|
428
|
-
'foreign: ✔ Connected',
|
|
429
|
-
'mnemonik: enabled OAuth',
|
|
430
|
-
'mnemonik-foreign: Connected',
|
|
431
|
-
'mnemonik: warning: was previously connected',
|
|
432
|
-
'unrecognized format',
|
|
433
|
-
]) {
|
|
434
|
-
stdout = listing;
|
|
435
|
-
assert.equal((await host.verify()).authenticatedTools, false);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
);
|
|
439
395
|
if (options.mcpConfig)
|
|
440
396
|
for (const scope of options.scopes)
|
|
441
397
|
check(
|
|
442
|
-
`${scope}: MCP
|
|
398
|
+
`${scope}: MCP declaration has the correct machine header and preserves foreign fields`,
|
|
443
399
|
async ({ root, project, deps, target: base }) => {
|
|
444
400
|
const target = { ...base, component: 'mcp' as const, scope };
|
|
445
401
|
const adapter = create({ ...deps, target });
|
|
@@ -458,14 +414,14 @@ export function hostAdapterConformance(
|
|
|
458
414
|
const raw = plan.changes[0]?.content.toString();
|
|
459
415
|
assert.ok(raw);
|
|
460
416
|
assert.ok(raw.includes('https://api.mnemonik.dev/mcp'));
|
|
461
|
-
const sessionHeader =
|
|
417
|
+
const sessionHeader = /headers\s*=.*"x-mcp-session-id"\s*=\s*"\{\{session_id\}\}"/u;
|
|
462
418
|
const withoutSessionHeader =
|
|
463
|
-
options.name === 'grok' && config.toml
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
);
|
|
419
|
+
options.name === 'grok' && config.toml
|
|
420
|
+
? raw.replace(/,?\s*"x-mcp-session-id"\s*=\s*"\{\{session_id\}\}"/u, '')
|
|
421
|
+
: raw;
|
|
422
|
+
if (options.name === 'grok') assert.match(raw, sessionHeader);
|
|
423
|
+
if (scope === 'user') assert.match(withoutSessionHeader, /x-mnemonik-installation-id/);
|
|
424
|
+
else assert.doesNotMatch(withoutSessionHeader, /x-mnemonik-installation-id/);
|
|
469
425
|
if (config.toml) assert.ok(raw.startsWith(original));
|
|
470
426
|
else {
|
|
471
427
|
const parsed = JSON.parse(raw);
|
|
@@ -473,6 +429,13 @@ export function hostAdapterConformance(
|
|
|
473
429
|
assert.deepEqual(parsed.mcpServers.mnemonik, {
|
|
474
430
|
...(config.type ? { type: config.type } : {}),
|
|
475
431
|
url: 'https://api.mnemonik.dev/mcp',
|
|
432
|
+
...(scope === 'user'
|
|
433
|
+
? {
|
|
434
|
+
headers: {
|
|
435
|
+
'x-mnemonik-installation-id': '11111111-1111-4111-8111-111111111111',
|
|
436
|
+
},
|
|
437
|
+
}
|
|
438
|
+
: {}),
|
|
476
439
|
});
|
|
477
440
|
}
|
|
478
441
|
const staged: FileChange[] = [];
|
|
@@ -487,6 +450,52 @@ export function hostAdapterConformance(
|
|
|
487
450
|
await writeChanges(staged);
|
|
488
451
|
assert.equal((await adapter.inspect()).declarationPresent, true);
|
|
489
452
|
assert.deepEqual((await adapter.plan()).changes, plan.changes);
|
|
453
|
+
|
|
454
|
+
if (scope === 'user') {
|
|
455
|
+
const installed = await readFile(path, 'utf8');
|
|
456
|
+
if (config.toml)
|
|
457
|
+
await writeFile(
|
|
458
|
+
path,
|
|
459
|
+
installed
|
|
460
|
+
.replace(/,?\s*"x-mnemonik-installation-id"\s*=\s*"[^"]*"/u, '')
|
|
461
|
+
.replace(/\{\s*,/u, '{')
|
|
462
|
+
.replace(/,\s*\}/u, ' }')
|
|
463
|
+
.replace(
|
|
464
|
+
'url = "https://api.mnemonik.dev/mcp"\n',
|
|
465
|
+
'url = "https://api.mnemonik.dev/mcp"\ncustom = "keep exactly"\n'
|
|
466
|
+
)
|
|
467
|
+
);
|
|
468
|
+
else {
|
|
469
|
+
const missing = JSON.parse(installed);
|
|
470
|
+
missing.mcpServers.mnemonik.custom = 'keep exactly';
|
|
471
|
+
missing.mcpServers.mnemonik.headers['x-foreign'] = 'keep exactly';
|
|
472
|
+
delete missing.mcpServers.mnemonik.headers['x-mnemonik-installation-id'];
|
|
473
|
+
await writeFile(path, JSON.stringify(missing, null, 2) + '\n');
|
|
474
|
+
}
|
|
475
|
+
const missingHeader = await readFile(path, 'utf8');
|
|
476
|
+
staged.length = 0;
|
|
477
|
+
await adapter.repair(writer, target);
|
|
478
|
+
await writeChanges(staged);
|
|
479
|
+
const repaired = await readFile(path, 'utf8');
|
|
480
|
+
assert.match(repaired, /x-mnemonik-installation-id/);
|
|
481
|
+
assert.match(repaired, /keep exactly/);
|
|
482
|
+
if (options.name === 'grok') assert.match(repaired, /x-mcp-session-id/);
|
|
483
|
+
if (config.toml) {
|
|
484
|
+
const withoutManagedHeader = repaired
|
|
485
|
+
.replace(
|
|
486
|
+
/(?:,\s*)?(?:["']x-mnemonik-installation-id["']|x-mnemonik-installation-id)\s*=\s*["'][^"']*["']\s*,?/u,
|
|
487
|
+
''
|
|
488
|
+
)
|
|
489
|
+
.replace(/\{\s*,/u, '{')
|
|
490
|
+
.replace(/,\s*\}/u, ' }');
|
|
491
|
+
assert.equal(withoutManagedHeader, missingHeader);
|
|
492
|
+
} else {
|
|
493
|
+
const withoutManagedHeader = JSON.parse(repaired);
|
|
494
|
+
delete withoutManagedHeader.mcpServers.mnemonik.headers['x-mnemonik-installation-id'];
|
|
495
|
+
assert.equal(JSON.stringify(withoutManagedHeader, null, 2) + '\n', missingHeader);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
490
499
|
staged.length = 0;
|
|
491
500
|
await adapter.uninstall(writer, target);
|
|
492
501
|
await writeChanges(staged);
|
package/tests/readiness.test.ts
CHANGED
|
@@ -13,7 +13,6 @@ const cases: Array<[ReadinessCondition, ReadinessState]> = [
|
|
|
13
13
|
[{ kind: 'host_trust_pending', reason: 'Approve Cursor.' }, 'ACTION_REQUIRED'],
|
|
14
14
|
[{ kind: 'vendor_policy_pending', reason: 'Allow the extension.' }, 'ACTION_REQUIRED'],
|
|
15
15
|
[{ kind: 'hooks_missing', reason: 'Hook declaration is missing.' }, 'ACTION_REQUIRED'],
|
|
16
|
-
[{ kind: 'host_not_connected', reason: 'Codex has not connected.' }, 'LIMITED'],
|
|
17
16
|
[{ kind: 'scanner_omitted', reason: 'Scanner was omitted.' }, 'LIMITED'],
|
|
18
17
|
[{ kind: 'project_uncovered', reason: 'Project is outside approved roots.' }, 'LIMITED'],
|
|
19
18
|
[{ kind: 'post_commit_upload_failed', reason: 'Result upload failed.' }, 'FAILED'],
|
|
@@ -100,7 +99,7 @@ test('the CLI sentence names the concrete reason and action', () => {
|
|
|
100
99
|
},
|
|
101
100
|
])
|
|
102
101
|
),
|
|
103
|
-
'Done, with one thing left. Cursor was skipped.
|
|
102
|
+
'Done, with one thing left. Cursor was skipped.'
|
|
104
103
|
);
|
|
105
104
|
});
|
|
106
105
|
|