@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
package/NOTICE ADDED
@@ -0,0 +1,10 @@
1
+ @peekdev/mcp
2
+ Copyright 2026 Cubenest
3
+
4
+ This product includes software developed by:
5
+ - WiseLibs — better-sqlite3 (MIT License)
6
+ - Anthropic — @modelcontextprotocol/sdk (MIT License)
7
+ - Colin McDonnell — zod (MIT License)
8
+
9
+ This product is licensed under the Apache License, Version 2.0.
10
+ See the LICENSE file for details.
@@ -0,0 +1,3 @@
1
+ export { defaultDbPath, openDb, type OpenDbOptions, openReadonlyDb, peekHomeDir, type ReadonlyDbResult, schemaVersion, } from './open.js';
2
+ export { defaultMigrationsDir, loadMigrations, type Migration, runMigrations, } from './migrate.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,aAAa,EACb,MAAM,EACN,KAAK,aAAa,EAClB,cAAc,EACd,WAAW,EACX,KAAK,gBAAgB,EACrB,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,KAAK,SAAS,EACd,aAAa,GACd,MAAM,cAAc,CAAC"}
@@ -0,0 +1,7 @@
1
+ // Public DB surface re-exported for the thin clients (@peekdev/cli, and the MCP
2
+ // server itself) so they open ~/.peek/sessions.db through the same open/migrate
3
+ // path the native host owns — no duplicated DB code (ADR-0007). Consumed as the
4
+ // `@peekdev/mcp/db` subpath export.
5
+ export { defaultDbPath, openDb, openReadonlyDb, peekHomeDir, schemaVersion, } from './open.js';
6
+ export { defaultMigrationsDir, loadMigrations, runMigrations, } from './migrate.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAChF,oCAAoC;AAEpC,OAAO,EACL,aAAa,EACb,MAAM,EAEN,cAAc,EACd,WAAW,EAEX,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,oBAAoB,EACpB,cAAc,EAEd,aAAa,GACd,MAAM,cAAc,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ /** A single migration discovered on disk. */
3
+ export interface Migration {
4
+ /** Numeric prefix, e.g. 1 for `0001_initial.sql`. Determines apply order. */
5
+ readonly version: number;
6
+ /** Full filename, e.g. `0001_initial.sql`. Recorded in `_migrations`. */
7
+ readonly name: string;
8
+ /** Raw SQL contents. */
9
+ readonly sql: string;
10
+ }
11
+ /**
12
+ * Default directory holding the `.sql` migration files, resolved relative to
13
+ * this module. Works from both `src/` (vitest, ts) and `dist/` (built, js)
14
+ * because the build step copies the `migrations/` folder next to the output.
15
+ */
16
+ export declare function defaultMigrationsDir(): string;
17
+ /**
18
+ * Read and parse every `NNNN-*.sql` (or `NNNN_*.sql`) file in `dir`, sorted by
19
+ * numeric version ascending. Non-matching files are ignored.
20
+ */
21
+ export declare function loadMigrations(dir?: string): Migration[];
22
+ /**
23
+ * Apply every pending migration to `db` in order. Each migration runs inside a
24
+ * transaction together with the `_migrations` bookkeeping insert, so a failure
25
+ * mid-migration rolls back cleanly and leaves the version unrecorded.
26
+ *
27
+ * Returns the list of migrations applied during this call (empty if the DB was
28
+ * already up to date).
29
+ */
30
+ export declare function runMigrations(db: Database, migrations?: Migration[]): Migration[];
31
+ /**
32
+ * Current schema version (highest applied migration), or 0 if migrations have
33
+ * never run. Read-only: unlike {@link runMigrations} it does not create the
34
+ * `_migrations` table — querying the version must not mutate an uninitialized DB.
35
+ */
36
+ export declare function schemaVersion(db: Database): number;
37
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,6CAA6C;AAC7C,MAAM,WAAW,SAAS;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wBAAwB;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAID;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAA+B,GAAG,SAAS,EAAE,CAchF;AAmBD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,QAAQ,EACZ,UAAU,GAAE,SAAS,EAAqB,GACzC,SAAS,EAAE,CAiBb;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CASlD"}
@@ -0,0 +1,86 @@
1
+ // Migrations runner (ADR-0007 action item 5): a directory of `NNNN-description.sql`
2
+ // files applied in lexical order on host startup, each inside a transaction,
3
+ // tracked in a `_migrations` bookkeeping table so re-running is idempotent.
4
+ import { readFileSync, readdirSync } from 'node:fs';
5
+ import { dirname, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ const MIGRATION_FILE_RE = /^(\d+)[_-].+\.sql$/;
8
+ /**
9
+ * Default directory holding the `.sql` migration files, resolved relative to
10
+ * this module. Works from both `src/` (vitest, ts) and `dist/` (built, js)
11
+ * because the build step copies the `migrations/` folder next to the output.
12
+ */
13
+ export function defaultMigrationsDir() {
14
+ return join(dirname(fileURLToPath(import.meta.url)), 'migrations');
15
+ }
16
+ /**
17
+ * Read and parse every `NNNN-*.sql` (or `NNNN_*.sql`) file in `dir`, sorted by
18
+ * numeric version ascending. Non-matching files are ignored.
19
+ */
20
+ export function loadMigrations(dir = defaultMigrationsDir()) {
21
+ const entries = readdirSync(dir);
22
+ const migrations = [];
23
+ for (const name of entries) {
24
+ const match = MIGRATION_FILE_RE.exec(name);
25
+ if (!match)
26
+ continue;
27
+ migrations.push({
28
+ version: Number(match[1]),
29
+ name,
30
+ sql: readFileSync(join(dir, name), 'utf8'),
31
+ });
32
+ }
33
+ migrations.sort((a, b) => a.version - b.version);
34
+ return migrations;
35
+ }
36
+ function ensureMigrationsTable(db) {
37
+ db.exec(`CREATE TABLE IF NOT EXISTS _migrations (
38
+ version INTEGER PRIMARY KEY,
39
+ name TEXT NOT NULL,
40
+ applied_at TEXT NOT NULL
41
+ )`);
42
+ }
43
+ function appliedVersions(db) {
44
+ const rows = db.prepare('SELECT version FROM _migrations').all();
45
+ return new Set(rows.map((r) => r.version));
46
+ }
47
+ /**
48
+ * Apply every pending migration to `db` in order. Each migration runs inside a
49
+ * transaction together with the `_migrations` bookkeeping insert, so a failure
50
+ * mid-migration rolls back cleanly and leaves the version unrecorded.
51
+ *
52
+ * Returns the list of migrations applied during this call (empty if the DB was
53
+ * already up to date).
54
+ */
55
+ export function runMigrations(db, migrations = loadMigrations()) {
56
+ ensureMigrationsTable(db);
57
+ const already = appliedVersions(db);
58
+ const record = db.prepare('INSERT INTO _migrations (version, name, applied_at) VALUES (?, ?, ?)');
59
+ const applied = [];
60
+ for (const migration of migrations) {
61
+ if (already.has(migration.version))
62
+ continue;
63
+ const apply = db.transaction(() => {
64
+ db.exec(migration.sql);
65
+ record.run(migration.version, migration.name, new Date().toISOString());
66
+ });
67
+ apply();
68
+ applied.push(migration);
69
+ }
70
+ return applied;
71
+ }
72
+ /**
73
+ * Current schema version (highest applied migration), or 0 if migrations have
74
+ * never run. Read-only: unlike {@link runMigrations} it does not create the
75
+ * `_migrations` table — querying the version must not mutate an uninitialized DB.
76
+ */
77
+ export function schemaVersion(db) {
78
+ const exists = db
79
+ .prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '_migrations'")
80
+ .get();
81
+ if (!exists)
82
+ return 0;
83
+ const row = db.prepare('SELECT MAX(version) AS v FROM _migrations').get();
84
+ return row.v ?? 0;
85
+ }
86
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,6EAA6E;AAC7E,4EAA4E;AAE5E,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAazC,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AAE/C;;;;GAIG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;AACrE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc,oBAAoB,EAAE;IACjE,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,UAAU,GAAgB,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,UAAU,CAAC,IAAI,CAAC;YACd,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI;YACJ,GAAG,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC;SAC3C,CAAC,CAAC;IACL,CAAC;IACD,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;IACjD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,qBAAqB,CAAC,EAAY;IACzC,EAAE,CAAC,IAAI,CACL;;;;OAIG,CACJ,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,EAAY;IACnC,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC,GAAG,EAE5D,CAAC;IACH,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAC3B,EAAY,EACZ,aAA0B,cAAc,EAAE;IAE1C,qBAAqB,CAAC,EAAE,CAAC,CAAC;IAC1B,MAAM,OAAO,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,sEAAsE,CAAC,CAAC;IAClG,MAAM,OAAO,GAAgB,EAAE,CAAC;IAEhC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC;YAAE,SAAS;QAC7C,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;YAChC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACvB,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QACH,KAAK,EAAE,CAAC;QACR,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,EAAY;IACxC,MAAM,MAAM,GAAG,EAAE;SACd,OAAO,CAAC,2EAA2E,CAAC;SACpF,GAAG,EAAE,CAAC;IACT,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAC,GAAG,EAEtE,CAAC;IACF,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC"}
@@ -0,0 +1,102 @@
1
+ -- 0001_initial — peek native-host SQLite schema (ADR-0007).
2
+ --
3
+ -- The native messaging host owns ~/.peek/sessions.db; the extension writes via
4
+ -- native messaging and the MCP server + CLI read the same file directly. rrweb
5
+ -- event blobs live on disk under ~/.peek/rrweb-events/ (gzipped, one per
6
+ -- session); this DB stores session metadata, the chunk index into those blobs,
7
+ -- and the extracted console / network / audit rows that the MCP tools query.
8
+ --
9
+ -- Conventions: UTC ISO-8601 strings for timestamps (text); epoch-millis
10
+ -- integers where a monotonic ordering key is wanted (`ts_ms`). All foreign keys
11
+ -- reference sessions(id) and cascade on delete so `peek sessions delete` is a
12
+ -- single statement.
13
+
14
+ -- One row per recorded browser session.
15
+ CREATE TABLE sessions (
16
+ id TEXT PRIMARY KEY, -- e.g. "s_8nQ..."
17
+ created_at TEXT NOT NULL, -- UTC ISO-8601 of first event
18
+ updated_at TEXT NOT NULL, -- UTC ISO-8601 of last append
19
+ url TEXT, -- initial top-frame URL
20
+ title TEXT, -- initial document title
21
+ origin TEXT, -- scheme://host[:port] of `url`
22
+ user_agent TEXT,
23
+ -- Path to the gzipped rrweb event blob for this session, relative to
24
+ -- ~/.peek/rrweb-events/ (the native host owns the absolute base).
25
+ events_blob_path TEXT,
26
+ event_count INTEGER NOT NULL DEFAULT 0, -- total rrweb events recorded
27
+ bytes INTEGER NOT NULL DEFAULT 0, -- on-disk size of the blob
28
+ status TEXT NOT NULL DEFAULT 'active' -- 'active' | 'finalized'
29
+ );
30
+
31
+ CREATE INDEX idx_sessions_created_at ON sessions (created_at);
32
+ CREATE INDEX idx_sessions_origin ON sessions (origin);
33
+
34
+ -- Index of rrweb event chunks appended over native messaging. The events
35
+ -- themselves are persisted to the gzipped blob; a chunk row records the byte
36
+ -- range + event range so a reader can seek without parsing the whole blob.
37
+ CREATE TABLE events_chunks (
38
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
39
+ session_id TEXT NOT NULL REFERENCES sessions (id) ON DELETE CASCADE,
40
+ seq INTEGER NOT NULL, -- 0-based chunk order within session
41
+ start_ts_ms INTEGER NOT NULL, -- epoch-millis of first event in chunk
42
+ end_ts_ms INTEGER NOT NULL, -- epoch-millis of last event in chunk
43
+ event_count INTEGER NOT NULL,
44
+ byte_offset INTEGER NOT NULL, -- offset into the gzipped blob
45
+ byte_length INTEGER NOT NULL,
46
+ created_at TEXT NOT NULL,
47
+ UNIQUE (session_id, seq)
48
+ );
49
+
50
+ CREATE INDEX idx_events_chunks_session ON events_chunks (session_id, seq);
51
+
52
+ -- Console messages extracted from the rrweb console plugin
53
+ -- (EventType=6, plugin "rrweb/console@1") for fast MCP queries.
54
+ CREATE TABLE console_events (
55
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
56
+ session_id TEXT NOT NULL REFERENCES sessions (id) ON DELETE CASCADE,
57
+ ts_ms INTEGER NOT NULL, -- epoch-millis of the log
58
+ level TEXT NOT NULL, -- 'log'|'info'|'warn'|'error'|'debug'|...
59
+ message TEXT NOT NULL, -- joined payload text
60
+ stack TEXT, -- trace, when present
61
+ url TEXT -- page URL at time of log
62
+ );
63
+
64
+ CREATE INDEX idx_console_events_session ON console_events (session_id, ts_ms);
65
+ CREATE INDEX idx_console_events_level ON console_events (session_id, level);
66
+
67
+ -- Network activity captured via CDP (chrome.debugger) / console-error fallback.
68
+ CREATE TABLE network_events (
69
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
70
+ session_id TEXT NOT NULL REFERENCES sessions (id) ON DELETE CASCADE,
71
+ ts_ms INTEGER NOT NULL, -- epoch-millis of request start
72
+ method TEXT NOT NULL, -- GET/POST/...
73
+ url TEXT NOT NULL,
74
+ status INTEGER, -- HTTP status; NULL if failed/pending
75
+ status_text TEXT,
76
+ request_id TEXT, -- CDP requestId, for correlation
77
+ resource_type TEXT, -- 'xhr'|'fetch'|'document'|...
78
+ duration_ms INTEGER, -- response time, when known
79
+ error_text TEXT -- net error string, when failed
80
+ );
81
+
82
+ CREATE INDEX idx_network_events_session ON network_events (session_id, ts_ms);
83
+ CREATE INDEX idx_network_events_status ON network_events (session_id, status);
84
+
85
+ -- Append-only audit log of every AI-driven act-tool call (ADR-0010 / PRD §H3).
86
+ -- Mirrors the JSONL shape written to ~/.peek/audit.log; kept in SQLite too so
87
+ -- `peek audit log --since 1h --tool ... --client ...` is an indexed query.
88
+ CREATE TABLE audit_log (
89
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
90
+ ts TEXT NOT NULL, -- UTC ISO-8601 of the action
91
+ tool TEXT NOT NULL, -- e.g. 'execute_action'
92
+ args_json TEXT, -- JSON of the tool args
93
+ approval_ts TEXT, -- when the user approved
94
+ approver TEXT, -- 'user' | ...
95
+ client TEXT, -- 'claude-code' | 'cursor' | ...
96
+ session_id TEXT REFERENCES sessions (id) ON DELETE SET NULL,
97
+ result TEXT -- 'ok' | 'error' | ...
98
+ );
99
+
100
+ CREATE INDEX idx_audit_log_ts ON audit_log (ts);
101
+ CREATE INDEX idx_audit_log_tool ON audit_log (tool);
102
+ CREATE INDEX idx_audit_log_client ON audit_log (client);
@@ -0,0 +1,15 @@
1
+ -- 0002_network_bodies — persist Deep capture masked bodies (ADR-0010, PRD §A.8).
2
+ --
3
+ -- The extension already masks request/response bodies via `maskNetMessage` in
4
+ -- the ISOLATED-world relay (packages/peek-extension/src/relay/mask.ts) BEFORE
5
+ -- forwarding them on `network.append`. Without these columns the native host
6
+ -- silently drops the masked bodies on the floor — the user's yellow-banner
7
+ -- consent for Deep capture yields no persisted body data, defeating the
8
+ -- whole reason `webRequest` is insufficient under MV3 (PRD §A.9).
9
+ --
10
+ -- Columns are TEXT (the relay produces strings via `redactBody`) and nullable
11
+ -- — Basic capture, request-only records, and error records leave them NULL.
12
+ -- SQLite's ALTER TABLE ADD COLUMN is non-destructive: existing rows get NULL
13
+ -- for the new columns, fresh DBs land with them from the start.
14
+ ALTER TABLE network_events ADD COLUMN request_body_redacted TEXT;
15
+ ALTER TABLE network_events ADD COLUMN response_body_redacted TEXT;
@@ -0,0 +1,57 @@
1
+ import Database from 'better-sqlite3';
2
+ import { schemaVersion } from './migrate.js';
3
+ /**
4
+ * The native host's data directory (ADR-0007). Defaults to `~/.peek`; the
5
+ * `PEEK_HOME` environment variable overrides it (used by tests and by users who
6
+ * relocate the store).
7
+ */
8
+ export declare function peekHomeDir(): string;
9
+ /** Absolute path to the SQLite database file. */
10
+ export declare function defaultDbPath(): string;
11
+ export interface OpenDbOptions {
12
+ /**
13
+ * Path to the database file. Defaults to `~/.peek/sessions.db`. Pass
14
+ * `':memory:'` for tests.
15
+ */
16
+ readonly path?: string;
17
+ /** Skip applying migrations on open (default: false). */
18
+ readonly skipMigrations?: boolean;
19
+ /**
20
+ * Open the file read-only (better-sqlite3 `{ readonly: true }`). Forces
21
+ * `skipMigrations` — a read-only handle cannot run DDL, and a read-only
22
+ * client (the MCP server, ADR-0011) must never mutate the host-owned schema.
23
+ * Opening a non-existent file read-only would throw, so the caller is
24
+ * expected to have ensured the file exists (see {@link openReadonlyDb}).
25
+ */
26
+ readonly readonly?: boolean;
27
+ }
28
+ /**
29
+ * Open (creating if needed) the peek SQLite database with WAL mode + foreign
30
+ * keys enabled, run any pending migrations, and return the connection. The
31
+ * caller owns closing it.
32
+ *
33
+ * When `readonly` is set the handle is opened read-only and migrations are
34
+ * skipped regardless of `skipMigrations`.
35
+ */
36
+ export declare function openDb(options?: OpenDbOptions): Database.Database;
37
+ /**
38
+ * The result of {@link openReadonlyDb}: either a live read-only connection, or
39
+ * a sentinel that no store exists yet (no native host has ever run). MCP tools
40
+ * branch on `exists` to return "no sessions recorded yet" rather than throwing.
41
+ */
42
+ export type ReadonlyDbResult = {
43
+ readonly exists: true;
44
+ readonly db: Database.Database;
45
+ } | {
46
+ readonly exists: false;
47
+ readonly db: undefined;
48
+ };
49
+ /**
50
+ * Open `~/.peek/sessions.db` read-only for a thin client (the MCP server). If
51
+ * the file does not exist — the user installed the MCP server but never ran the
52
+ * extension / native host — return `{ exists: false }` so callers degrade
53
+ * gracefully instead of better-sqlite3 throwing `SQLITE_CANTOPEN`.
54
+ */
55
+ export declare function openReadonlyDb(path?: string): ReadonlyDbResult;
56
+ export { schemaVersion };
57
+ //# sourceMappingURL=open.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../src/db/open.ts"],"names":[],"mappings":"AAOA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAiB,aAAa,EAAE,MAAM,cAAc,CAAC;AAE5D;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAED,iDAAiD;AACjD,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,QAAQ,CAAC,QAAQ,CA0BrE;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAA;CAAE,GACzD;IAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAA;CAAE,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAwB,GAAG,gBAAgB,CAQ/E;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,74 @@
1
+ // Database open + bootstrap. The native host, MCP server, and CLI all open the
2
+ // same ~/.peek/sessions.db through here so the pragmas (WAL, foreign keys) and
3
+ // the migration state stay consistent across the three thin clients (ADR-0007).
4
+ import { existsSync, mkdirSync } from 'node:fs';
5
+ import { homedir } from 'node:os';
6
+ import { dirname, join } from 'node:path';
7
+ import Database from 'better-sqlite3';
8
+ import { runMigrations, schemaVersion } from './migrate.js';
9
+ /**
10
+ * The native host's data directory (ADR-0007). Defaults to `~/.peek`; the
11
+ * `PEEK_HOME` environment variable overrides it (used by tests and by users who
12
+ * relocate the store).
13
+ */
14
+ export function peekHomeDir() {
15
+ const override = process.env.PEEK_HOME;
16
+ if (override && override.length > 0)
17
+ return override;
18
+ return join(homedir(), '.peek');
19
+ }
20
+ /** Absolute path to the SQLite database file. */
21
+ export function defaultDbPath() {
22
+ return join(peekHomeDir(), 'sessions.db');
23
+ }
24
+ /**
25
+ * Open (creating if needed) the peek SQLite database with WAL mode + foreign
26
+ * keys enabled, run any pending migrations, and return the connection. The
27
+ * caller owns closing it.
28
+ *
29
+ * When `readonly` is set the handle is opened read-only and migrations are
30
+ * skipped regardless of `skipMigrations`.
31
+ */
32
+ export function openDb(options = {}) {
33
+ const path = options.path ?? defaultDbPath();
34
+ const readonly = options.readonly ?? false;
35
+ // A read-only open must not create the directory or the file — that would
36
+ // resurrect a deleted store or mask a "never recorded" state as an empty DB.
37
+ if (path !== ':memory:' && !readonly) {
38
+ mkdirSync(dirname(path), { recursive: true });
39
+ }
40
+ const db = new Database(path, readonly ? { readonly: true } : {});
41
+ if (readonly) {
42
+ // Read-only handles can't enable WAL (a DDL/pragma write); the writer (the
43
+ // native host) already put the file in WAL mode. FK enforcement is moot
44
+ // for a reader. Leave pragmas to the writer.
45
+ }
46
+ else {
47
+ // WAL gives concurrent readers (CLI/MCP) while the host writes (ADR-0007).
48
+ db.pragma('journal_mode = WAL');
49
+ // Enforce the ON DELETE CASCADE / SET NULL foreign keys in the schema.
50
+ db.pragma('foreign_keys = ON');
51
+ }
52
+ // Read-only handles can't run DDL, so migrations are always skipped there.
53
+ if (!options.skipMigrations && !readonly) {
54
+ runMigrations(db);
55
+ }
56
+ return db;
57
+ }
58
+ /**
59
+ * Open `~/.peek/sessions.db` read-only for a thin client (the MCP server). If
60
+ * the file does not exist — the user installed the MCP server but never ran the
61
+ * extension / native host — return `{ exists: false }` so callers degrade
62
+ * gracefully instead of better-sqlite3 throwing `SQLITE_CANTOPEN`.
63
+ */
64
+ export function openReadonlyDb(path = defaultDbPath()) {
65
+ // `:memory:` is only meaningful for tests that seed a writable handle; a
66
+ // read-only in-memory DB is empty and useless, so treat it as "exists" and
67
+ // let the caller manage it.
68
+ if (path !== ':memory:' && !existsSync(path)) {
69
+ return { exists: false, db: undefined };
70
+ }
71
+ return { exists: true, db: openDb({ path, readonly: true }) };
72
+ }
73
+ export { schemaVersion };
74
+ //# sourceMappingURL=open.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.js","sourceRoot":"","sources":["../../src/db/open.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAEhF,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE5D;;;;GAIG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;IACvC,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AAClC,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,aAAa;IAC3B,OAAO,IAAI,CAAC,WAAW,EAAE,EAAE,aAAa,CAAC,CAAC;AAC5C,CAAC;AAoBD;;;;;;;GAOG;AACH,MAAM,UAAU,MAAM,CAAC,UAAyB,EAAE;IAChD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,aAAa,EAAE,CAAC;IAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC;IAC3C,0EAA0E;IAC1E,6EAA6E;IAC7E,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC,QAAQ,EAAE,CAAC;QACrC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAClE,IAAI,QAAQ,EAAE,CAAC;QACb,2EAA2E;QAC3E,wEAAwE;QACxE,6CAA6C;IAC/C,CAAC;SAAM,CAAC;QACN,2EAA2E;QAC3E,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAChC,uEAAuE;QACvE,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACjC,CAAC;IAED,2EAA2E;IAC3E,IAAI,CAAC,OAAO,CAAC,cAAc,IAAI,CAAC,QAAQ,EAAE,CAAC;QACzC,aAAa,CAAC,EAAE,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAWD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe,aAAa,EAAE;IAC3D,yEAAyE;IACzE,2EAA2E;IAC3E,4BAA4B;IAC5B,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;IAC1C,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAChE,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Decide the run mode from argv. Native-host mode when invoked with the
4
+ * explicit `--native-host` flag, or when a browser spawns us with the calling
5
+ * extension's `chrome-extension://...` origin as an argument. Otherwise MCP.
6
+ */
7
+ export declare function resolveMode(argv: readonly string[]): 'native-host' | 'mcp';
8
+ /** The `chrome-extension://<id>/` origin Chrome/Edge passed, if any. */
9
+ export declare function callingExtensionOrigin(argv: readonly string[]): string | undefined;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAsBA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,GAAG,KAAK,CAI1E;AAED,wEAAwE;AACxE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAElF"}
package/dist/index.js ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ // `peek-mcp` entry. The same binary serves two roles depending on argv
3
+ // (ADR-0007):
4
+ // - native messaging host (owns ~/.peek/sessions.db), entered either via the
5
+ // explicit `--native-host` flag (manual/testing) OR — the path that fires
6
+ // in production — when a browser spawns the manifest's `path`. Chrome/Edge
7
+ // pass the calling extension's origin (`chrome-extension://<id>/`) as the
8
+ // first argument (and on Windows a second arg, the Chrome native window
9
+ // handle), with NO `--native-host` flag. We detect that origin argument so
10
+ // the installed manifest works with zero platform-specific shell wrappers
11
+ // (this supersedes the PRD §A7 `native-host.sh` example).
12
+ // - `peek-mcp` (default): run the stdio MCP server AI tools spawn (Phase 3c).
13
+ //
14
+ // This dispatcher keeps the surface stable so the manifest path and
15
+ // `npx -y @peekdev/mcp` invocations are already correct.
16
+ import { runMcpServer } from './mcp/index.js';
17
+ import { startNativeHost } from './native-host/host.js';
18
+ /** Matches the `chrome-extension://<id>/` origin Chrome/Edge pass when spawning a host. */
19
+ const EXTENSION_ORIGIN_RE = /^chrome-extension:\/\//;
20
+ /**
21
+ * Decide the run mode from argv. Native-host mode when invoked with the
22
+ * explicit `--native-host` flag, or when a browser spawns us with the calling
23
+ * extension's `chrome-extension://...` origin as an argument. Otherwise MCP.
24
+ */
25
+ export function resolveMode(argv) {
26
+ if (argv.includes('--native-host'))
27
+ return 'native-host';
28
+ if (argv.some((arg) => EXTENSION_ORIGIN_RE.test(arg)))
29
+ return 'native-host';
30
+ return 'mcp';
31
+ }
32
+ /** The `chrome-extension://<id>/` origin Chrome/Edge passed, if any. */
33
+ export function callingExtensionOrigin(argv) {
34
+ return argv.find((arg) => EXTENSION_ORIGIN_RE.test(arg));
35
+ }
36
+ async function main() {
37
+ const argv = process.argv.slice(2);
38
+ const mode = resolveMode(argv);
39
+ if (mode === 'native-host') {
40
+ const callerOrigin = callingExtensionOrigin(argv);
41
+ const host = startNativeHost(callerOrigin !== undefined ? { callerOrigin } : {});
42
+ try {
43
+ await host.done;
44
+ }
45
+ finally {
46
+ // Always release the SQLite handle, even if the stream errored.
47
+ host.close();
48
+ }
49
+ return;
50
+ }
51
+ // MCP stdio server (ADR-0011): runs until the client closes stdin.
52
+ await runMcpServer();
53
+ }
54
+ main().catch((err) => {
55
+ console.error(`peek-mcp: fatal — ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
56
+ process.exitCode = 1;
57
+ });
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,uEAAuE;AACvE,cAAc;AACd,+EAA+E;AAC/E,8EAA8E;AAC9E,+EAA+E;AAC/E,8EAA8E;AAC9E,4EAA4E;AAC5E,+EAA+E;AAC/E,8EAA8E;AAC9E,8DAA8D;AAC9D,gFAAgF;AAChF,EAAE;AACF,oEAAoE;AACpE,yDAAyD;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,2FAA2F;AAC3F,MAAM,mBAAmB,GAAG,wBAAwB,CAAC;AAErD;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAuB;IACjD,IAAI,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC;QAAE,OAAO,aAAa,CAAC;IACzD,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAAE,OAAO,aAAa,CAAC;IAC5E,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,sBAAsB,CAAC,IAAuB;IAC5D,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAE/B,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,eAAe,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC;QAClB,CAAC;gBAAS,CAAC;YACT,gEAAgE;YAChE,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;QACD,OAAO;IACT,CAAC;IAED,mEAAmE;IACnE,MAAM,YAAY,EAAE,CAAC;AACvB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CACX,qBAAqB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACvF,CAAC;IACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}