@peekdev/mcp 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/NOTICE +10 -0
  2. package/dist/db/index.d.ts +3 -0
  3. package/dist/db/index.d.ts.map +1 -0
  4. package/dist/db/index.js +7 -0
  5. package/dist/db/index.js.map +1 -0
  6. package/dist/db/migrate.d.ts +37 -0
  7. package/dist/db/migrate.d.ts.map +1 -0
  8. package/dist/db/migrate.js +86 -0
  9. package/dist/db/migrate.js.map +1 -0
  10. package/dist/db/migrations/0001_initial.sql +102 -0
  11. package/dist/db/migrations/0002_network_bodies.sql +15 -0
  12. package/dist/db/open.d.ts +57 -0
  13. package/dist/db/open.d.ts.map +1 -0
  14. package/dist/db/open.js +74 -0
  15. package/dist/db/open.js.map +1 -0
  16. package/dist/index.d.ts +10 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +58 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/mcp/action-schema.d.ts +223 -0
  21. package/dist/mcp/action-schema.d.ts.map +1 -0
  22. package/dist/mcp/action-schema.js +97 -0
  23. package/dist/mcp/action-schema.js.map +1 -0
  24. package/dist/mcp/event-blobs.d.ts +32 -0
  25. package/dist/mcp/event-blobs.d.ts.map +1 -0
  26. package/dist/mcp/event-blobs.js +65 -0
  27. package/dist/mcp/event-blobs.js.map +1 -0
  28. package/dist/mcp/event-walker.d.ts +86 -0
  29. package/dist/mcp/event-walker.d.ts.map +1 -0
  30. package/dist/mcp/event-walker.js +398 -0
  31. package/dist/mcp/event-walker.js.map +1 -0
  32. package/dist/mcp/host-bridge.d.ts +80 -0
  33. package/dist/mcp/host-bridge.d.ts.map +1 -0
  34. package/dist/mcp/host-bridge.js +88 -0
  35. package/dist/mcp/host-bridge.js.map +1 -0
  36. package/dist/mcp/index.d.ts +8 -0
  37. package/dist/mcp/index.d.ts.map +1 -0
  38. package/dist/mcp/index.js +32 -0
  39. package/dist/mcp/index.js.map +1 -0
  40. package/dist/mcp/playwright-repro.d.ts +19 -0
  41. package/dist/mcp/playwright-repro.d.ts.map +1 -0
  42. package/dist/mcp/playwright-repro.js +78 -0
  43. package/dist/mcp/playwright-repro.js.map +1 -0
  44. package/dist/mcp/queries.d.ts +73 -0
  45. package/dist/mcp/queries.d.ts.map +1 -0
  46. package/dist/mcp/queries.js +139 -0
  47. package/dist/mcp/queries.js.map +1 -0
  48. package/dist/mcp/roots.d.ts +50 -0
  49. package/dist/mcp/roots.d.ts.map +1 -0
  50. package/dist/mcp/roots.js +97 -0
  51. package/dist/mcp/roots.js.map +1 -0
  52. package/dist/mcp/rrweb-types.d.ts +3 -0
  53. package/dist/mcp/rrweb-types.d.ts.map +1 -0
  54. package/dist/mcp/rrweb-types.js +7 -0
  55. package/dist/mcp/rrweb-types.js.map +1 -0
  56. package/dist/mcp/selector.d.ts +54 -0
  57. package/dist/mcp/selector.d.ts.map +1 -0
  58. package/dist/mcp/selector.js +209 -0
  59. package/dist/mcp/selector.js.map +1 -0
  60. package/dist/mcp/server.d.ts +49 -0
  61. package/dist/mcp/server.d.ts.map +1 -0
  62. package/dist/mcp/server.js +469 -0
  63. package/dist/mcp/server.js.map +1 -0
  64. package/dist/mcp/summary.d.ts +26 -0
  65. package/dist/mcp/summary.d.ts.map +1 -0
  66. package/dist/mcp/summary.js +74 -0
  67. package/dist/mcp/summary.js.map +1 -0
  68. package/dist/native-host/action-protocol.d.ts +49 -0
  69. package/dist/native-host/action-protocol.d.ts.map +1 -0
  70. package/dist/native-host/action-protocol.js +36 -0
  71. package/dist/native-host/action-protocol.js.map +1 -0
  72. package/dist/native-host/audit.d.ts +69 -0
  73. package/dist/native-host/audit.d.ts.map +1 -0
  74. package/dist/native-host/audit.js +85 -0
  75. package/dist/native-host/audit.js.map +1 -0
  76. package/dist/native-host/config.d.ts +18 -0
  77. package/dist/native-host/config.d.ts.map +1 -0
  78. package/dist/native-host/config.js +56 -0
  79. package/dist/native-host/config.js.map +1 -0
  80. package/dist/native-host/extension-ids.json +6 -0
  81. package/dist/native-host/host.d.ts +30 -0
  82. package/dist/native-host/host.d.ts.map +1 -0
  83. package/dist/native-host/host.js +96 -0
  84. package/dist/native-host/host.js.map +1 -0
  85. package/dist/native-host/index.d.ts +4 -0
  86. package/dist/native-host/index.d.ts.map +1 -0
  87. package/dist/native-host/index.js +8 -0
  88. package/dist/native-host/index.js.map +1 -0
  89. package/dist/native-host/ingest.d.ts +83 -0
  90. package/dist/native-host/ingest.d.ts.map +1 -0
  91. package/dist/native-host/ingest.js +283 -0
  92. package/dist/native-host/ingest.js.map +1 -0
  93. package/dist/native-host/installer.d.ts +64 -0
  94. package/dist/native-host/installer.d.ts.map +1 -0
  95. package/dist/native-host/installer.js +110 -0
  96. package/dist/native-host/installer.js.map +1 -0
  97. package/dist/native-host/manifest.d.ts +64 -0
  98. package/dist/native-host/manifest.d.ts.map +1 -0
  99. package/dist/native-host/manifest.js +117 -0
  100. package/dist/native-host/manifest.js.map +1 -0
  101. package/dist/native-host/policy.d.ts +60 -0
  102. package/dist/native-host/policy.d.ts.map +1 -0
  103. package/dist/native-host/policy.js +116 -0
  104. package/dist/native-host/policy.js.map +1 -0
  105. package/dist/native-host/request-registry.d.ts +55 -0
  106. package/dist/native-host/request-registry.d.ts.map +1 -0
  107. package/dist/native-host/request-registry.js +111 -0
  108. package/dist/native-host/request-registry.js.map +1 -0
  109. package/dist/native-host/transport.d.ts +54 -0
  110. package/dist/native-host/transport.d.ts.map +1 -0
  111. package/dist/native-host/transport.js +113 -0
  112. package/dist/native-host/transport.js.map +1 -0
  113. package/dist/postinstall.d.ts +3 -0
  114. package/dist/postinstall.d.ts.map +1 -0
  115. package/dist/postinstall.js +72 -0
  116. package/dist/postinstall.js.map +1 -0
  117. package/package.json +59 -0
  118. package/src/db/migrations/0001_initial.sql +102 -0
  119. package/src/db/migrations/0002_network_bodies.sql +15 -0
  120. package/src/native-host/extension-ids.json +6 -0
@@ -0,0 +1,283 @@
1
+ // Native-host ingest pipeline (Task #44 — capture-loop closure).
2
+ //
3
+ // The SW already builds the four message types defined in
4
+ // `peek-extension/src/background/native-protocol.ts`. This module turns them
5
+ // into:
6
+ // - `~/.peek/rrweb-events/<sessionId>/<seq>.json.gz` chunk blobs
7
+ // - `events_chunks` index rows (path + byte range)
8
+ // - `console_events` / `network_events` rows
9
+ // - a `sessions` upsert keyed by sessionId
10
+ //
11
+ // Per ADR-0007 the SQLite DB stores ONLY pointers + extracted rows; the rrweb
12
+ // event bodies live on disk in gzipped chunks so the DB file stays small and
13
+ // queryable. The chunk file's path is recorded in `events_chunks.byte_offset
14
+ // = 0`, `byte_length = file_size`; we treat each chunk as its own gzip member,
15
+ // not a concatenated stream, which keeps appends trivially crash-safe.
16
+ //
17
+ // Hardening: every public entry point returns a structured reply (never
18
+ // throws). A FK error, a malformed shape, or a disk full doesn't tear down the
19
+ // host loop — the SW gets a `.err` reply and retries (per the SW's contract
20
+ // the host loop survives).
21
+ import { mkdirSync, statSync, writeFileSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+ import { gzipSync } from 'node:zlib';
24
+ /** Absolute path to the rrweb-events chunk store under PEEK_HOME. */
25
+ export function rrwebEventsDir(home) {
26
+ return join(home, 'rrweb-events');
27
+ }
28
+ /**
29
+ * Dispatch one ingest message to the appropriate handler. Pure with respect to
30
+ * the network: takes a context, returns a reply. Every error path returns a
31
+ * structured `.err` reply — never throws.
32
+ */
33
+ export function ingest(message, ctx) {
34
+ try {
35
+ switch (message.type) {
36
+ case 'session.append':
37
+ return ingestSessionAppend(message, ctx);
38
+ case 'console.append':
39
+ return ingestConsoleAppend(message, ctx);
40
+ case 'network.append':
41
+ return ingestNetworkAppend(message, ctx);
42
+ case 'shadow.report':
43
+ return ingestShadowReport(message, ctx);
44
+ default: {
45
+ const type = message.type ?? '(none)';
46
+ return {
47
+ type: 'ingest.err',
48
+ code: 'unknown_type',
49
+ detail: `no handler for message type '${type}'`,
50
+ };
51
+ }
52
+ }
53
+ }
54
+ catch (err) {
55
+ // Defense in depth: any uncaught throw from a handler becomes a structured
56
+ // error reply rather than tearing down the host loop.
57
+ return {
58
+ type: 'ingest.err',
59
+ code: 'handler_threw',
60
+ detail: err instanceof Error ? err.message : String(err),
61
+ };
62
+ }
63
+ }
64
+ // ---------------------------------------------------------------------------
65
+ // session.append
66
+ // ---------------------------------------------------------------------------
67
+ function ingestSessionAppend(msg, ctx) {
68
+ if (typeof msg.sessionId !== 'string' || msg.sessionId.length === 0) {
69
+ return {
70
+ type: 'session.append.err',
71
+ code: 'missing_session_id',
72
+ detail: 'session.append requires sessionId',
73
+ };
74
+ }
75
+ if (!Array.isArray(msg.events) || msg.events.length === 0) {
76
+ return {
77
+ type: 'session.append.err',
78
+ sessionId: msg.sessionId,
79
+ code: 'empty_events',
80
+ detail: 'session.append requires at least one event',
81
+ };
82
+ }
83
+ const { startMs, endMs } = eventTimeRange(msg.events);
84
+ const nowIso = new Date().toISOString();
85
+ // Choose a seq: client-stamped (idempotent retry) or auto-incremented.
86
+ const nextSeqRow = ctx.db
87
+ .prepare('SELECT IFNULL(MAX(seq), -1) + 1 AS next FROM events_chunks WHERE session_id = ?')
88
+ .get(msg.sessionId);
89
+ const seq = msg.seq ?? nextSeqRow.next;
90
+ // Encode the chunk body to disk first, then index it in the DB. Order
91
+ // matters: a crash AFTER the file write but BEFORE the row insert leaves an
92
+ // orphan file (recoverable: a recovery pass can re-walk the dir + re-index).
93
+ // A crash AFTER the row insert but BEFORE the file write would leave a
94
+ // dangling pointer the reader would 404 on — strictly worse.
95
+ const body = Buffer.from(JSON.stringify(msg.events), 'utf8');
96
+ const gz = gzipSync(body);
97
+ const dir = join(rrwebEventsDir(ctx.home), msg.sessionId);
98
+ mkdirSync(dir, { recursive: true });
99
+ const blobPath = join(dir, `${seq}.json.gz`);
100
+ writeFileSync(blobPath, gz);
101
+ const fileBytes = gz.length;
102
+ // Upsert the parent session, then INSERT OR IGNORE the chunk row, then
103
+ // accumulate event_count + bytes — all in one transaction so a retry of a
104
+ // dup seq doesn't double-count and a partial failure rolls back.
105
+ const origin = msg.url ? safeOrigin(msg.url) : undefined;
106
+ const upsertSession = ctx.db.prepare(`INSERT INTO sessions (id, created_at, updated_at, url, title, origin, event_count, bytes, events_blob_path)
107
+ VALUES (?, ?, ?, ?, ?, ?, 0, 0, ?)
108
+ ON CONFLICT(id) DO UPDATE SET
109
+ updated_at = excluded.updated_at,
110
+ url = COALESCE(excluded.url, sessions.url),
111
+ title = COALESCE(excluded.title, sessions.title),
112
+ origin = COALESCE(excluded.origin, sessions.origin)`);
113
+ const insertChunk = ctx.db.prepare(`INSERT OR IGNORE INTO events_chunks
114
+ (session_id, seq, start_ts_ms, end_ts_ms, event_count, byte_offset, byte_length, created_at)
115
+ VALUES (?, ?, ?, ?, ?, 0, ?, ?)`);
116
+ const accumulateSession = ctx.db.prepare(`UPDATE sessions
117
+ SET event_count = event_count + ?,
118
+ bytes = bytes + ?,
119
+ updated_at = ?
120
+ WHERE id = ?`);
121
+ const tx = ctx.db.transaction(() => {
122
+ upsertSession.run(msg.sessionId, nowIso, nowIso, msg.url ?? null, msg.title ?? null, origin ?? null,
123
+ // events_blob_path stays a per-session pointer label; chunk rows carry the per-seq path.
124
+ // We record the session-level directory (relative to PEEK_HOME) on first insert via the
125
+ // `?` placeholder above; the INSERT-OR-IGNORE on conflict won't update it (intentional).
126
+ join('rrweb-events', msg.sessionId));
127
+ const ins = insertChunk.run(msg.sessionId, seq, startMs, endMs, msg.events.length, fileBytes, nowIso);
128
+ if (ins.changes > 0) {
129
+ // Only accumulate when we actually inserted (a duplicate seq is a no-op).
130
+ accumulateSession.run(msg.events.length, fileBytes, nowIso, msg.sessionId);
131
+ }
132
+ });
133
+ tx();
134
+ return { type: 'session.append.ok', sessionId: msg.sessionId, seq };
135
+ }
136
+ // ---------------------------------------------------------------------------
137
+ // console.append
138
+ // ---------------------------------------------------------------------------
139
+ function ingestConsoleAppend(msg, ctx) {
140
+ if (typeof msg.sessionId !== 'string' || msg.sessionId.length === 0) {
141
+ return {
142
+ type: 'console.append.err',
143
+ code: 'missing_session_id',
144
+ detail: 'console.append requires sessionId',
145
+ };
146
+ }
147
+ const events = Array.isArray(msg.events) ? msg.events : [];
148
+ if (events.length === 0) {
149
+ return { type: 'console.append.ok', sessionId: msg.sessionId, count: 0 };
150
+ }
151
+ const insert = ctx.db.prepare('INSERT INTO console_events (session_id, ts_ms, level, message, url) VALUES (?, ?, ?, ?, ?)');
152
+ // ensureSessionRow runs INSIDE the per-batch transaction so a child-insert
153
+ // failure (disk full, NOT NULL violation, FK error) rolls back the parent
154
+ // upsert too — otherwise a failed batch leaves an empty `sessions` row.
155
+ const tx = ctx.db.transaction((rows) => {
156
+ ensureSessionRow(ctx.db, msg.sessionId, msg.url, msg.title);
157
+ for (const ev of rows) {
158
+ const ts = typeof ev.ts === 'number' ? ev.ts : Date.now();
159
+ const level = typeof ev.level === 'string' && ev.level.length > 0 ? ev.level : 'log';
160
+ const args = Array.isArray(ev.args) ? ev.args : [];
161
+ // The args[] array is the masked, already-stringified console arg list
162
+ // (see peek-extension/src/relay/mask.ts). Joining matches the rrweb
163
+ // console-plugin's natural format (one logical line per call).
164
+ const message = args.join(' ');
165
+ insert.run(msg.sessionId, ts, level, message, msg.url ?? null);
166
+ }
167
+ });
168
+ tx(events);
169
+ return { type: 'console.append.ok', sessionId: msg.sessionId, count: events.length };
170
+ }
171
+ // ---------------------------------------------------------------------------
172
+ // network.append
173
+ // ---------------------------------------------------------------------------
174
+ function ingestNetworkAppend(msg, ctx) {
175
+ if (typeof msg.sessionId !== 'string' || msg.sessionId.length === 0) {
176
+ return {
177
+ type: 'network.append.err',
178
+ code: 'missing_session_id',
179
+ detail: 'network.append requires sessionId',
180
+ };
181
+ }
182
+ const records = Array.isArray(msg.records) ? msg.records : [];
183
+ if (records.length === 0) {
184
+ return { type: 'network.append.ok', sessionId: msg.sessionId, count: 0 };
185
+ }
186
+ // Deep capture (ADR-0010): the relay-side mask runs `redactBody` BEFORE the
187
+ // body arrives here, so we just persist the already-masked string. Unset
188
+ // fields land as SQL NULL (not the literal "undefined" / empty string).
189
+ const insert = ctx.db.prepare(`INSERT INTO network_events
190
+ (session_id, ts_ms, method, url, status, status_text, request_id, resource_type, duration_ms, error_text, request_body_redacted, response_body_redacted)
191
+ VALUES (?, ?, ?, ?, ?, NULL, ?, ?, NULL, ?, ?, ?)`);
192
+ // ensureSessionRow runs INSIDE the per-batch transaction so a child-insert
193
+ // failure (disk full, NOT NULL violation, FK error) rolls back the parent
194
+ // upsert too — otherwise a failed batch leaves an empty `sessions` row.
195
+ const tx = ctx.db.transaction((rows) => {
196
+ ensureSessionRow(ctx.db, msg.sessionId, msg.url, msg.title);
197
+ for (const rec of rows) {
198
+ const ts = typeof rec.ts === 'number' ? rec.ts : Date.now();
199
+ const method = typeof rec.method === 'string' ? rec.method : rec.kind === 'request' ? 'GET' : 'GET';
200
+ const url = typeof rec.url === 'string' ? rec.url : '';
201
+ const status = typeof rec.status === 'number' ? rec.status : null;
202
+ const requestId = typeof rec.id === 'string' ? rec.id : null;
203
+ const resourceType = typeof rec.transport === 'string' ? rec.transport : null;
204
+ const errorText = typeof rec.error === 'string' ? rec.error : null;
205
+ const requestBody = typeof rec.requestBody === 'string' ? rec.requestBody : null;
206
+ const responseBody = typeof rec.responseBody === 'string' ? rec.responseBody : null;
207
+ insert.run(msg.sessionId, ts, method, url, status, requestId, resourceType, errorText, requestBody, responseBody);
208
+ }
209
+ });
210
+ tx(records);
211
+ return { type: 'network.append.ok', sessionId: msg.sessionId, count: records.length };
212
+ }
213
+ // ---------------------------------------------------------------------------
214
+ // shadow.report — deferred persistence (Phase 4)
215
+ // ---------------------------------------------------------------------------
216
+ function ingestShadowReport(msg, _ctx) {
217
+ if (typeof msg.sessionId !== 'string' || msg.sessionId.length === 0) {
218
+ return {
219
+ type: 'shadow.report.err',
220
+ code: 'missing_session_id',
221
+ detail: 'shadow.report requires sessionId',
222
+ };
223
+ }
224
+ const count = Array.isArray(msg.reports) ? msg.reports.length : 0;
225
+ // Reconnaissance shape — not a primary capture type. Persisted shape decision
226
+ // is deferred to Phase 4. For now we just log + acknowledge.
227
+ console.warn(`peek native host: shadow.report ack — ${count} report(s) for ${msg.sessionId}`);
228
+ return { type: 'shadow.report.ok', sessionId: msg.sessionId, count };
229
+ }
230
+ // ---------------------------------------------------------------------------
231
+ // helpers
232
+ // ---------------------------------------------------------------------------
233
+ /** Ensure the parent `sessions` row exists (FK target) before inserting children. */
234
+ function ensureSessionRow(db, sessionId, url, title) {
235
+ const origin = url ? safeOrigin(url) : undefined;
236
+ const nowIso = new Date().toISOString();
237
+ db.prepare(`INSERT INTO sessions (id, created_at, updated_at, url, title, origin)
238
+ VALUES (?, ?, ?, ?, ?, ?)
239
+ ON CONFLICT(id) DO UPDATE SET
240
+ updated_at = excluded.updated_at,
241
+ url = COALESCE(excluded.url, sessions.url),
242
+ title = COALESCE(excluded.title, sessions.title),
243
+ origin = COALESCE(excluded.origin, sessions.origin)`).run(sessionId, nowIso, nowIso, url ?? null, title ?? null, origin ?? null);
244
+ }
245
+ /** Min / max timestamp across an events batch. Defaults to now() for empty/missing. */
246
+ function eventTimeRange(events) {
247
+ let startMs = Number.POSITIVE_INFINITY;
248
+ let endMs = Number.NEGATIVE_INFINITY;
249
+ for (const ev of events) {
250
+ const ts = readTimestamp(ev);
251
+ if (ts === null)
252
+ continue;
253
+ if (ts < startMs)
254
+ startMs = ts;
255
+ if (ts > endMs)
256
+ endMs = ts;
257
+ }
258
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) {
259
+ const now = Date.now();
260
+ return { startMs: now, endMs: now };
261
+ }
262
+ return { startMs, endMs };
263
+ }
264
+ function readTimestamp(ev) {
265
+ if (typeof ev !== 'object' || ev === null)
266
+ return null;
267
+ const ts = ev.timestamp;
268
+ return typeof ts === 'number' && Number.isFinite(ts) ? ts : null;
269
+ }
270
+ function safeOrigin(url) {
271
+ try {
272
+ return new URL(url).origin;
273
+ }
274
+ catch {
275
+ return undefined;
276
+ }
277
+ }
278
+ // Internal helper exported for future test coverage of file size accounting.
279
+ export function _chunkFileSize(home, sessionId, seq) {
280
+ const path = join(rrwebEventsDir(home), sessionId, `${seq}.json.gz`);
281
+ return statSync(path).size;
282
+ }
283
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/native-host/ingest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import type { InstallTarget, NativeHostManifest } from './manifest.js';
2
+ /** Outcome of attempting to install one target. */
3
+ export interface InstallResult {
4
+ readonly browser: string;
5
+ /** Filesystem path written (darwin/linux + win32), if any. */
6
+ readonly manifestPath?: string;
7
+ /** Registry key written (win32), if any. */
8
+ readonly registryKey?: string;
9
+ /** True when a write actually happened (false in dry-run mode). */
10
+ readonly written: boolean;
11
+ /** Error message if the write failed; the run continues past failures. */
12
+ readonly error?: string;
13
+ }
14
+ /**
15
+ * Spawn shape `realSink.writeRegistryKey` uses to invoke `reg.exe`. Injectable
16
+ * so tests can capture argv without spawning a real Windows process.
17
+ */
18
+ export type RegExecFn = (file: string, args: readonly string[]) => void;
19
+ /** Injectable side-effect surface (defaults to real fs + registry). */
20
+ export interface InstallSink {
21
+ /** Write a manifest JSON file at an absolute path (creating parent dirs). */
22
+ writeManifestFile(path: string, contents: string): void;
23
+ /**
24
+ * Point an HKCU registry key's default value at the on-disk manifest path.
25
+ * The manifest file is written separately via `writeManifestFile`; this
26
+ * function does NOT write the file. Errors propagate; the installer catches
27
+ * per-target.
28
+ */
29
+ writeRegistryKey(key: string, manifestPath: string): void;
30
+ }
31
+ /**
32
+ * Build a real-OS sink. Exported so tests can inject a fake `execFn` while
33
+ * still exercising the platform guard. Production callers use {@link realSink}.
34
+ */
35
+ export declare function buildRealSink(deps?: {
36
+ execFn?: RegExecFn;
37
+ platform?: NodeJS.Platform;
38
+ }): InstallSink;
39
+ /**
40
+ * Default real-OS sink (writes to disk + the Windows registry). Constructed
41
+ * once at module-load via {@link buildRealSink}; tests requiring custom
42
+ * platform / execFn should call `buildRealSink` directly instead of mutating
43
+ * this object.
44
+ */
45
+ export declare const realSink: InstallSink;
46
+ export interface InstallOptions {
47
+ /** When true, report what would be written without writing (default false). */
48
+ readonly dryRun?: boolean;
49
+ /** Side-effect sink; defaults to the real fs/registry sink. */
50
+ readonly sink?: InstallSink;
51
+ }
52
+ /**
53
+ * Install `manifest` to every `target`. Failures are captured per-target (so
54
+ * one unwritable browser dir doesn't abort the rest) and returned in the
55
+ * result list. In `dryRun` mode nothing is written and every result has
56
+ * `written: false`.
57
+ *
58
+ * For a target that carries BOTH `manifestPath` AND `registryKey` (Windows),
59
+ * the JSON file is written first and the registry value is then pointed at
60
+ * the just-written path. If the file write throws, the registry write is
61
+ * skipped (don't leave a dangling registry entry pointing nowhere).
62
+ */
63
+ export declare function installManifests(targets: InstallTarget[], manifest: NativeHostManifest, options?: InstallOptions): InstallResult[];
64
+ //# sourceMappingURL=installer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../src/native-host/installer.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEvE,mDAAmD;AACnD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,4CAA4C;IAC5C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;AAExE,uEAAuE;AACvE,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxD;;;;;OAKG;IACH,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,GAAE;IACJ,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CACvB,GACL,WAAW,CAmCb;AAED;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,WAA6B,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,aAAa,EAAE,EACxB,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,GAAE,cAAmB,GAC3B,aAAa,EAAE,CAwCjB"}
@@ -0,0 +1,110 @@
1
+ // Native-host manifest installer (P2 PRD §A7). The actual filesystem / registry
2
+ // writes are factored behind injectable writer functions so the postinstall
3
+ // flow can be unit-tested with fakes — and so a dry run can report exactly what
4
+ // *would* be written without touching the real OS.
5
+ //
6
+ // Windows install path (3d-4):
7
+ // Chrome/Edge on Windows read the native-host manifest path from a registry
8
+ // key under HKCU. The default value of that key is the absolute path to the
9
+ // manifest JSON on disk (conventionally
10
+ // `%LOCALAPPDATA%\<vendor>\<browser>\NativeMessagingHosts\<host>.json`).
11
+ // So the install does TWO things in order:
12
+ // 1. write the manifest JSON to disk (target.manifestPath)
13
+ // 2. set the registry default value to that path via reg.exe
14
+ // `realSink.writeRegistryKey` shells out to `reg.exe add` (built into every
15
+ // Windows install since XP) and is gated on `process.platform === 'win32'`
16
+ // so a misconfigured target never accidentally fires on darwin/linux.
17
+ import { execFileSync } from 'node:child_process';
18
+ import { mkdirSync, writeFileSync } from 'node:fs';
19
+ import { dirname } from 'node:path';
20
+ /**
21
+ * Build a real-OS sink. Exported so tests can inject a fake `execFn` while
22
+ * still exercising the platform guard. Production callers use {@link realSink}.
23
+ */
24
+ export function buildRealSink(deps = {}) {
25
+ const execFn = deps.execFn ??
26
+ ((file, args) => {
27
+ execFileSync(file, [...args], { stdio: 'ignore' });
28
+ });
29
+ const platform = deps.platform ?? process.platform;
30
+ return {
31
+ writeManifestFile(path, contents) {
32
+ mkdirSync(dirname(path), { recursive: true });
33
+ writeFileSync(path, contents, 'utf8');
34
+ },
35
+ writeRegistryKey(key, manifestPath) {
36
+ // Defense in depth: a misconfigured target (win32 entry on darwin/linux)
37
+ // must never silently shell out to a non-existent binary. Throw with a
38
+ // clear, structured message so installManifests records it as a
39
+ // per-target error and the run continues.
40
+ if (platform !== 'win32') {
41
+ throw new Error(`realSink.writeRegistryKey called on non-Windows platform (${platform}); refused`);
42
+ }
43
+ if (typeof manifestPath !== 'string' || manifestPath.length === 0) {
44
+ throw new Error(`realSink.writeRegistryKey: manifestPath must be a non-empty absolute path (got '${manifestPath}')`);
45
+ }
46
+ // `reg.exe add <KEY> /ve /d <DATA> /f`
47
+ // /ve → set the (Default) value (the one Chrome/Edge read)
48
+ // /d → data to write
49
+ // /f → force, no Y/N prompt
50
+ execFn('reg.exe', ['add', key, '/ve', '/d', manifestPath, '/f']);
51
+ },
52
+ };
53
+ }
54
+ /**
55
+ * Default real-OS sink (writes to disk + the Windows registry). Constructed
56
+ * once at module-load via {@link buildRealSink}; tests requiring custom
57
+ * platform / execFn should call `buildRealSink` directly instead of mutating
58
+ * this object.
59
+ */
60
+ export const realSink = buildRealSink();
61
+ /**
62
+ * Install `manifest` to every `target`. Failures are captured per-target (so
63
+ * one unwritable browser dir doesn't abort the rest) and returned in the
64
+ * result list. In `dryRun` mode nothing is written and every result has
65
+ * `written: false`.
66
+ *
67
+ * For a target that carries BOTH `manifestPath` AND `registryKey` (Windows),
68
+ * the JSON file is written first and the registry value is then pointed at
69
+ * the just-written path. If the file write throws, the registry write is
70
+ * skipped (don't leave a dangling registry entry pointing nowhere).
71
+ */
72
+ export function installManifests(targets, manifest, options = {}) {
73
+ const dryRun = options.dryRun ?? false;
74
+ const sink = options.sink ?? realSink;
75
+ const contents = `${JSON.stringify(manifest, null, 2)}\n`;
76
+ const results = [];
77
+ for (const target of targets) {
78
+ const base = {
79
+ browser: target.browser,
80
+ ...(target.manifestPath !== undefined ? { manifestPath: target.manifestPath } : {}),
81
+ ...(target.registryKey !== undefined ? { registryKey: target.registryKey } : {}),
82
+ };
83
+ if (dryRun) {
84
+ results.push({ ...base, written: false });
85
+ continue;
86
+ }
87
+ try {
88
+ if (target.manifestPath !== undefined) {
89
+ sink.writeManifestFile(target.manifestPath, contents);
90
+ }
91
+ if (target.registryKey !== undefined) {
92
+ // Windows targets carry both: the registry value must point at the
93
+ // file we just wrote (or, if the file write was skipped above, the
94
+ // declared target path so a separate prior install still resolves).
95
+ const pathForRegistry = target.manifestPath ?? '';
96
+ sink.writeRegistryKey(target.registryKey, pathForRegistry);
97
+ }
98
+ results.push({ ...base, written: true });
99
+ }
100
+ catch (err) {
101
+ results.push({
102
+ ...base,
103
+ written: false,
104
+ error: err instanceof Error ? err.message : String(err),
105
+ });
106
+ }
107
+ }
108
+ return results;
109
+ }
110
+ //# sourceMappingURL=installer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.js","sourceRoot":"","sources":["../../src/native-host/installer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /** Reverse-DNS native-host id (ADR-0009 / NAMING.md). */
2
+ export declare const NATIVE_HOST_NAME = "com.cubenest.peek";
3
+ /** The manifest filename written into each NativeMessagingHosts directory. */
4
+ export declare const MANIFEST_FILENAME = "com.cubenest.peek.json";
5
+ /** Shape of the published `src/native-host/extension-ids.json`. */
6
+ export interface ExtensionIds {
7
+ readonly chromeWebStore: string;
8
+ readonly edgeAddons: string;
9
+ readonly dev: string;
10
+ }
11
+ /** A Chrome/Edge native-messaging host manifest. */
12
+ export interface NativeHostManifest {
13
+ readonly name: string;
14
+ readonly description: string;
15
+ readonly path: string;
16
+ readonly type: 'stdio';
17
+ readonly allowed_origins: string[];
18
+ }
19
+ /**
20
+ * Turn the three configured extension IDs into the `allowed_origins` array.
21
+ * Chrome forbids wildcards, so each id becomes an explicit
22
+ * `chrome-extension://<id>/` origin. Unconfigured placeholder ids are dropped
23
+ * (so a pre-publish install doesn't ship dead origins), de-duplicating the
24
+ * result.
25
+ */
26
+ export declare function allowedOrigins(ids: ExtensionIds): string[];
27
+ /**
28
+ * Build the native-host manifest. `hostBinaryPath` is the absolute path the
29
+ * browser will spawn over stdio (the installed `peek-mcp` bin, invoked with
30
+ * `--native-host`). Both Chrome and Edge ids live in the SAME manifest's
31
+ * `allowed_origins` per the Edge "first registry location wins" gotcha
32
+ * (P2 PRD §A7).
33
+ */
34
+ export declare function buildManifest(hostBinaryPath: string, ids: ExtensionIds): NativeHostManifest;
35
+ /** Supported desktop platforms (Node `process.platform` values). */
36
+ export type SupportedPlatform = 'darwin' | 'linux' | 'win32';
37
+ /** A single place the manifest must be registered. */
38
+ export interface InstallTarget {
39
+ /** Human label, e.g. "macOS Chrome" — used in postinstall logging. */
40
+ readonly browser: string;
41
+ /**
42
+ * For darwin/linux: an absolute filesystem path to write the manifest JSON.
43
+ * For win32: omitted (registry targets carry `registryKey` instead).
44
+ */
45
+ readonly manifestPath?: string;
46
+ /**
47
+ * For win32: the HKCU registry key whose default value points at the
48
+ * manifest JSON on disk (Chrome/Edge read the path from the registry).
49
+ */
50
+ readonly registryKey?: string;
51
+ }
52
+ /**
53
+ * Resolve the per-OS set of native-messaging install targets (P2 PRD §A7).
54
+ *
55
+ * - macOS: Chrome, Chromium, Edge `NativeMessagingHosts/` directories under
56
+ * `~/Library/Application Support/`.
57
+ * - Linux: Chrome + Chromium `NativeMessagingHosts/` under `~/.config/`.
58
+ * - Windows: HKCU registry keys for Chrome + Edge (the default value of each
59
+ * key is the on-disk manifest path).
60
+ *
61
+ * `homeDir` is injected for testability.
62
+ */
63
+ export declare function resolveInstallTargets(platform: SupportedPlatform, homeDir: string): InstallTarget[];
64
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/native-host/manifest.ts"],"names":[],"mappings":"AAQA,yDAAyD;AACzD,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAEpD,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,2BAA6B,CAAC;AAE5D,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,oDAAoD;AACpD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;CACpC;AAID;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,EAAE,CAY1D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,kBAAkB,CAQ3F;AAED,oEAAoE;AACpE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAE7D,sDAAsD;AACtD,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,MAAM,GACd,aAAa,EAAE,CA8EjB"}