@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,19 @@
1
+ import type { eventWithTime } from './rrweb-types.js';
2
+ export interface GenerateReproOptions {
3
+ /** Only include actions at/after this epoch-millis. */
4
+ readonly startTs?: number;
5
+ /** Only include actions at/before this epoch-millis. */
6
+ readonly endTs?: number;
7
+ /** Test title (defaults to a session-derived label). */
8
+ readonly title?: string;
9
+ /** Max actions to emit (default 200); keeps the latest N, the rest noted. */
10
+ readonly maxActions?: number;
11
+ }
12
+ /**
13
+ * Build a Playwright `test(...)` script from the user actions within
14
+ * `[startTs, endTs]`. Actions whose target selector couldn't be resolved are
15
+ * emitted as a `// TODO` comment (so the script stays runnable and the gap is
16
+ * visible) rather than silently dropped.
17
+ */
18
+ export declare function generatePlaywrightRepro(events: eventWithTime[], options?: GenerateReproOptions): string;
19
+ //# sourceMappingURL=playwright-repro.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-repro.d.ts","sourceRoot":"","sources":["../../src/mcp/playwright-repro.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAgCD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,aAAa,EAAE,EACvB,OAAO,GAAE,oBAAyB,GACjC,MAAM,CAqCR"}
@@ -0,0 +1,78 @@
1
+ // generate_playwright_repro (Task 3.13): turn a window of extracted user
2
+ // actions into a runnable Playwright test string. Each action maps to the
3
+ // idiomatic Playwright call:
4
+ // navigate -> await page.goto('url')
5
+ // click -> await page.click('selector')
6
+ // input -> await page.fill('selector', 'value')
7
+ // The first navigation seeds the opening goto; subsequent navigations are
8
+ // emitted inline (e.g. an in-app route change that triggered a full load).
9
+ import { extractUserActions } from './event-walker.js';
10
+ /** Default ceiling on emitted actions — caps the output size (PRD §B token budget). */
11
+ const DEFAULT_MAX_ACTIONS = 200;
12
+ /** Single-quote-escape a value for embedding in a generated JS string literal. */
13
+ function jsString(value) {
14
+ // Order matters: backslash first, then quote, then the line terminators —
15
+ // a bare \r or \n in a single-quoted literal is a strict-mode syntax error.
16
+ return `'${value
17
+ .replace(/\\/g, '\\\\')
18
+ .replace(/'/g, "\\'")
19
+ .replace(/\n/g, '\\n')
20
+ .replace(/\r/g, '\\r')}'`;
21
+ }
22
+ /** Map one user action to a Playwright statement, or undefined to skip it. */
23
+ function actionToStatement(action) {
24
+ switch (action.type) {
25
+ case 'navigate':
26
+ return action.url ? ` await page.goto(${jsString(action.url)});` : undefined;
27
+ case 'click':
28
+ return action.selector ? ` await page.click(${jsString(action.selector)});` : undefined;
29
+ case 'input':
30
+ return action.selector
31
+ ? ` await page.fill(${jsString(action.selector)}, ${jsString(action.value ?? '')});`
32
+ : undefined;
33
+ default:
34
+ return undefined;
35
+ }
36
+ }
37
+ /**
38
+ * Build a Playwright `test(...)` script from the user actions within
39
+ * `[startTs, endTs]`. Actions whose target selector couldn't be resolved are
40
+ * emitted as a `// TODO` comment (so the script stays runnable and the gap is
41
+ * visible) rather than silently dropped.
42
+ */
43
+ export function generatePlaywrightRepro(events, options = {}) {
44
+ const startTs = options.startTs ?? Number.NEGATIVE_INFINITY;
45
+ const endTs = options.endTs ?? Number.POSITIVE_INFINITY;
46
+ const title = options.title ?? 'peek recorded session';
47
+ const maxActions = options.maxActions ?? DEFAULT_MAX_ACTIONS;
48
+ const allInWindow = extractUserActions(events).filter((a) => a.ts >= startTs && a.ts <= endTs);
49
+ // Cap output: keep the LATEST N actions (most relevant to reproduce recent
50
+ // behavior), noting the truncation so the agent knows the repro is partial.
51
+ const truncated = allInWindow.length > maxActions;
52
+ const actions = truncated ? allInWindow.slice(allInWindow.length - maxActions) : allInWindow;
53
+ const lines = [];
54
+ lines.push(`import { test, expect } from '@playwright/test';`);
55
+ lines.push('');
56
+ lines.push(`test(${jsString(title)}, async ({ page }) => {`);
57
+ if (actions.length === 0) {
58
+ lines.push(' // No user actions were recorded in this window.');
59
+ }
60
+ else if (truncated) {
61
+ lines.push(` // truncated: showing last ${maxActions} of ${allInWindow.length} actions (narrow startTs/endTs for the rest)`);
62
+ }
63
+ for (const action of actions) {
64
+ const stmt = actionToStatement(action);
65
+ if (stmt !== undefined) {
66
+ lines.push(stmt);
67
+ }
68
+ else {
69
+ lines.push(` // TODO: ${action.summary} (target selector unresolved)`);
70
+ }
71
+ }
72
+ lines.push('});');
73
+ lines.push('');
74
+ return lines.join('\n');
75
+ }
76
+ // `expect` is imported in the generated script for the author to add assertions;
77
+ // we don't synthesize assertions in v1 (we have no oracle for "correct" state).
78
+ //# sourceMappingURL=playwright-repro.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-repro.js","sourceRoot":"","sources":["../../src/mcp/playwright-repro.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ /** A row of `sessions` as the MCP tools present it (camelCase, PRD §B3 ids). */
3
+ export interface SessionSummaryRow {
4
+ readonly id: string;
5
+ readonly origin: string | null;
6
+ readonly url: string | null;
7
+ readonly title: string | null;
8
+ readonly startedAt: number;
9
+ readonly durationMs: number;
10
+ readonly errorCount: number;
11
+ readonly eventCount: number;
12
+ }
13
+ /** Accurate `COUNT(*)` of error-level console rows for a session. */
14
+ export declare function countConsoleErrors(db: Database, sessionId: string): number;
15
+ /** Accurate `COUNT(*)` of failed/notable network rows (status >= 400 or net error). */
16
+ export declare function countNetworkErrors(db: Database, sessionId: string): number;
17
+ export interface ListSessionsOptions {
18
+ readonly limit?: number;
19
+ readonly origin?: string;
20
+ }
21
+ /**
22
+ * List the most-recently-updated sessions, newest first (PRD §B3
23
+ * `list_recent_sessions`). Default limit 10; capped to the schema's
24
+ * `origin`-filtered subset when `origin` is given.
25
+ */
26
+ export declare function listRecentSessions(db: Database, options?: ListSessionsOptions): SessionSummaryRow[];
27
+ /** One session's summary row, or undefined if no such id. */
28
+ export declare function getSessionSummaryRow(db: Database, id: string): SessionSummaryRow | undefined;
29
+ export interface ConsoleErrorRow {
30
+ readonly id: number;
31
+ readonly ts: number;
32
+ readonly level: string;
33
+ readonly message: string;
34
+ readonly stack: string | null;
35
+ }
36
+ /**
37
+ * Console error rows for a session, oldest first (PRD §B3
38
+ * `get_session_console_errors`). `since` filters to ts >= it; default level
39
+ * filter is `error` only (the tool name says "errors").
40
+ */
41
+ export declare function getConsoleErrors(db: Database, id: string, options?: {
42
+ since?: number;
43
+ limit?: number;
44
+ level?: string;
45
+ }): ConsoleErrorRow[];
46
+ export interface NetworkErrorRow {
47
+ readonly id: number;
48
+ readonly ts: number;
49
+ readonly method: string;
50
+ readonly url: string;
51
+ readonly status: number | null;
52
+ readonly statusText: string | null;
53
+ readonly resourceType: string | null;
54
+ readonly durationMs: number | null;
55
+ readonly errorText: string | null;
56
+ }
57
+ /**
58
+ * Failed/notable network rows for a session, oldest first (PRD §B3
59
+ * `get_session_network_errors`). `statusGte` defaults to 400; rows with a
60
+ * net-error string are always included.
61
+ */
62
+ export declare function getNetworkErrors(db: Database, id: string, options?: {
63
+ statusGte?: number;
64
+ limit?: number;
65
+ }): NetworkErrorRow[];
66
+ /** Look up the events blob path + first-event ts for a session (for the walker tools). */
67
+ export declare function getSessionBlobRef(db: Database, id: string): {
68
+ blobPath: string | null;
69
+ startedAt: number;
70
+ } | undefined;
71
+ /** The ts of a single console event id (used by get_user_action_before_error). */
72
+ export declare function getConsoleEventTs(db: Database, id: string, errorId: number): number | undefined;
73
+ //# sourceMappingURL=queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/mcp/queries.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAoBD,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAM1E;AAED,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAQ1E;AAqBD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,EACZ,OAAO,GAAE,mBAAwB,GAChC,iBAAiB,EAAE,CAYrB;AAED,6DAA6D;AAC7D,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAG5F;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/D,eAAe,EAAE,CA8BnB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACnD,eAAe,EAAE,CAgCnB;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,EACZ,EAAE,EAAE,MAAM,GACT;IAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAM5D;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK/F"}
@@ -0,0 +1,139 @@
1
+ // SQL read helpers for the MCP tools that hit structured tables directly
2
+ // (sessions, console_events, network_events). These mirror @peekdev/cli's
3
+ // db.ts shapes so a `peek sessions export --format json` and the MCP
4
+ // `get_session_*` tools return interchangeable data (P2 PRD §B3). The console /
5
+ // network rows are pre-extracted by the native host, so these are fast indexed
6
+ // reads — only the event-level tools fall back to the gzipped blob walker.
7
+ /** Epoch-millis of an ISO-8601 timestamp; 0 if unparseable. */
8
+ function isoToMs(iso) {
9
+ if (!iso)
10
+ return 0;
11
+ const ms = Date.parse(iso);
12
+ return Number.isNaN(ms) ? 0 : ms;
13
+ }
14
+ /** Accurate `COUNT(*)` of error-level console rows for a session. */
15
+ export function countConsoleErrors(db, sessionId) {
16
+ return db
17
+ .prepare("SELECT COUNT(*) AS c FROM console_events WHERE session_id = ? AND level = 'error'")
18
+ .get(sessionId).c;
19
+ }
20
+ /** Accurate `COUNT(*)` of failed/notable network rows (status >= 400 or net error). */
21
+ export function countNetworkErrors(db, sessionId) {
22
+ return db
23
+ .prepare('SELECT COUNT(*) AS c FROM network_events WHERE session_id = ? AND (status >= 400 OR error_text IS NOT NULL)')
24
+ .get(sessionId).c;
25
+ }
26
+ function errorCountFor(db, sessionId) {
27
+ return countConsoleErrors(db, sessionId) + countNetworkErrors(db, sessionId);
28
+ }
29
+ function toSummaryRow(db, r) {
30
+ const startedAt = isoToMs(r.created_at);
31
+ const endedAt = isoToMs(r.updated_at);
32
+ return {
33
+ id: r.id,
34
+ origin: r.origin,
35
+ url: r.url,
36
+ title: r.title,
37
+ startedAt,
38
+ durationMs: Math.max(0, endedAt - startedAt),
39
+ errorCount: errorCountFor(db, r.id),
40
+ eventCount: r.event_count,
41
+ };
42
+ }
43
+ /**
44
+ * List the most-recently-updated sessions, newest first (PRD §B3
45
+ * `list_recent_sessions`). Default limit 10; capped to the schema's
46
+ * `origin`-filtered subset when `origin` is given.
47
+ */
48
+ export function listRecentSessions(db, options = {}) {
49
+ const limit = options.limit ?? 10;
50
+ const params = [];
51
+ let sql = 'SELECT * FROM sessions';
52
+ if (options.origin !== undefined) {
53
+ sql += ' WHERE origin = ?';
54
+ params.push(options.origin);
55
+ }
56
+ sql += ' ORDER BY updated_at DESC, created_at DESC LIMIT ?';
57
+ params.push(limit);
58
+ const rows = db.prepare(sql).all(...params);
59
+ return rows.map((r) => toSummaryRow(db, r));
60
+ }
61
+ /** One session's summary row, or undefined if no such id. */
62
+ export function getSessionSummaryRow(db, id) {
63
+ const row = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id);
64
+ return row ? toSummaryRow(db, row) : undefined;
65
+ }
66
+ /**
67
+ * Console error rows for a session, oldest first (PRD §B3
68
+ * `get_session_console_errors`). `since` filters to ts >= it; default level
69
+ * filter is `error` only (the tool name says "errors").
70
+ */
71
+ export function getConsoleErrors(db, id, options = {}) {
72
+ const limit = options.limit ?? 50;
73
+ const params = [id];
74
+ let sql = 'SELECT id, ts_ms, level, message, stack FROM console_events WHERE session_id = ?';
75
+ if (options.level !== undefined) {
76
+ sql += ' AND level = ?';
77
+ params.push(options.level);
78
+ }
79
+ else {
80
+ sql += " AND level = 'error'";
81
+ }
82
+ if (options.since !== undefined) {
83
+ sql += ' AND ts_ms >= ?';
84
+ params.push(options.since);
85
+ }
86
+ sql += ' ORDER BY ts_ms ASC LIMIT ?';
87
+ params.push(limit);
88
+ const rows = db.prepare(sql).all(...params);
89
+ return rows.map((r) => ({
90
+ id: r.id,
91
+ ts: r.ts_ms,
92
+ level: r.level,
93
+ message: r.message,
94
+ stack: r.stack,
95
+ }));
96
+ }
97
+ /**
98
+ * Failed/notable network rows for a session, oldest first (PRD §B3
99
+ * `get_session_network_errors`). `statusGte` defaults to 400; rows with a
100
+ * net-error string are always included.
101
+ */
102
+ export function getNetworkErrors(db, id, options = {}) {
103
+ const statusGte = options.statusGte ?? 400;
104
+ const limit = options.limit ?? 50;
105
+ const rows = db
106
+ .prepare(`SELECT id, ts_ms, method, url, status, status_text, resource_type, duration_ms, error_text
107
+ FROM network_events
108
+ WHERE session_id = ? AND (status >= ? OR error_text IS NOT NULL)
109
+ ORDER BY ts_ms ASC LIMIT ?`)
110
+ .all(id, statusGte, limit);
111
+ return rows.map((r) => ({
112
+ id: r.id,
113
+ ts: r.ts_ms,
114
+ method: r.method,
115
+ url: r.url,
116
+ status: r.status,
117
+ statusText: r.status_text,
118
+ resourceType: r.resource_type,
119
+ durationMs: r.duration_ms,
120
+ errorText: r.error_text,
121
+ }));
122
+ }
123
+ /** Look up the events blob path + first-event ts for a session (for the walker tools). */
124
+ export function getSessionBlobRef(db, id) {
125
+ const row = db
126
+ .prepare('SELECT events_blob_path, created_at FROM sessions WHERE id = ?')
127
+ .get(id);
128
+ if (!row)
129
+ return undefined;
130
+ return { blobPath: row.events_blob_path, startedAt: isoToMs(row.created_at) };
131
+ }
132
+ /** The ts of a single console event id (used by get_user_action_before_error). */
133
+ export function getConsoleEventTs(db, id, errorId) {
134
+ const row = db
135
+ .prepare('SELECT ts_ms FROM console_events WHERE id = ? AND session_id = ?')
136
+ .get(errorId, id);
137
+ return row?.ts_ms;
138
+ }
139
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/mcp/queries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The subset of `McpServer.server` this module depends on. Kept structurally
3
+ * loose (only the fields we read) so the SDK's richer `listRoots` return type
4
+ * (extra `_meta`, index signature) assigns cleanly under exactOptionalPropertyTypes.
5
+ */
6
+ export interface RootsCapableServer {
7
+ getClientCapabilities(): {
8
+ roots?: unknown;
9
+ } | undefined;
10
+ listRoots(): Promise<{
11
+ roots: ReadonlyArray<{
12
+ uri: string;
13
+ }>;
14
+ }>;
15
+ }
16
+ /** Resolved scope after consulting (or failing to consult) the client's roots. */
17
+ export interface RootsScope {
18
+ /**
19
+ * Origins (`scheme://host[:port]`) the session queries should be limited to,
20
+ * or `undefined` to mean "no scoping — all sessions" (the safe fallback when
21
+ * the client doesn't support roots, doesn't answer, or supplies roots from
22
+ * which no origin can be derived).
23
+ */
24
+ readonly allowedOrigins: string[] | undefined;
25
+ /** Why we ended up with this scope — surfaced in logs / tests. */
26
+ readonly reason: 'no-roots-capability' | 'roots-timeout' | 'roots-error' | 'no-origins-derived' | 'scoped';
27
+ }
28
+ /**
29
+ * Derive the origins peek should scope to from a set of client roots. Roots are
30
+ * usually `file://` project directories (e.g. `file:///Users/x/repo`), not web
31
+ * origins — so v1 derives origins only from roots that ARE http(s) URIs (a
32
+ * client that surfaces a dev-server URL as a root). `file://` roots yield no
33
+ * origin here (deriving localhost ports from package.json/vite config is a
34
+ * documented future enhancement, PRD §B5), so a file-only root set falls back
35
+ * to unscoped rather than scoping to nothing.
36
+ */
37
+ export declare function deriveAllowedOrigins(roots: ReadonlyArray<{
38
+ uri: string;
39
+ }>): string[];
40
+ export interface ResolveRootsScopeOptions {
41
+ /** Timeout for `roots/list` before falling back (default 1000ms, Issue #3315). */
42
+ readonly timeoutMs?: number;
43
+ }
44
+ /**
45
+ * Consult the client's roots (if advertised) and resolve the session scope,
46
+ * defensively timing out per claude-code Issue #3315. Never throws — every
47
+ * failure mode degrades to {@link UNSCOPED}.
48
+ */
49
+ export declare function resolveRootsScope(server: RootsCapableServer, options?: ResolveRootsScopeOptions): Promise<RootsScope>;
50
+ //# sourceMappingURL=roots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roots.d.ts","sourceRoot":"","sources":["../../src/mcp/roots.ts"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,qBAAqB,IAAI;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;IACzD,SAAS,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,aAAa,CAAC;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CACjE;AAED,kFAAkF;AAClF,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC9C,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EACX,qBAAqB,GACrB,eAAe,GACf,aAAa,GACb,oBAAoB,GACpB,QAAQ,CAAC;CACd;AAkCD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,EAAE,CAapF;AAED,MAAM,WAAW,wBAAwB;IACvC,kFAAkF;IAClF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,UAAU,CAAC,CAyBrB"}
@@ -0,0 +1,97 @@
1
+ // MCP Roots scoping (Task 3.14 / P2 PRD §B5). On `oninitialized` the server
2
+ // asks whether the client advertised the `roots` capability; if so it fetches
3
+ // the client's roots (project directories) and derives the origins peek should
4
+ // scope session queries to. Two defensive realities drive the design:
5
+ //
6
+ // 1. Anthropic claude-code Issue #3315 (Apr 2026): Claude Code advertises the
7
+ // `roots` capability but historically did NOT implement `roots/list` — a
8
+ // naive `await listRoots()` hangs forever. So we race it against a 1s
9
+ // timeout and fall back to "all sessions" (no scoping).
10
+ // 2. modelcontextprotocol/servers Issue #3602: the filesystem server
11
+ // *replaces* its allowed dirs with the client roots. peek instead treats
12
+ // roots as a SOFT filter over origins (v1) — see deriveAllowedOrigins.
13
+ //
14
+ // This module is transport-agnostic and side-effect-free: it takes the two
15
+ // Server methods it needs as inputs so it's trivially unit-testable.
16
+ /** The unscoped fallback. */
17
+ const UNSCOPED = (reason) => ({
18
+ allowedOrigins: undefined,
19
+ reason,
20
+ });
21
+ /**
22
+ * Race a promise against a timeout. Resolves to the promise's value, or
23
+ * `{ timedOut: true }` after `ms`. The losing promise is left to settle
24
+ * (its result is ignored) — we never reject from the timeout path.
25
+ */
26
+ async function withTimeout(promise, ms) {
27
+ let timer;
28
+ const timeout = new Promise((resolve) => {
29
+ timer = setTimeout(() => resolve({ timedOut: true }), ms);
30
+ // Don't keep the event loop alive solely for this timer.
31
+ timer.unref?.();
32
+ });
33
+ try {
34
+ const value = await Promise.race([
35
+ promise.then((v) => ({ timedOut: false, value: v })),
36
+ timeout,
37
+ ]);
38
+ return value;
39
+ }
40
+ finally {
41
+ if (timer)
42
+ clearTimeout(timer);
43
+ }
44
+ }
45
+ /**
46
+ * Derive the origins peek should scope to from a set of client roots. Roots are
47
+ * usually `file://` project directories (e.g. `file:///Users/x/repo`), not web
48
+ * origins — so v1 derives origins only from roots that ARE http(s) URIs (a
49
+ * client that surfaces a dev-server URL as a root). `file://` roots yield no
50
+ * origin here (deriving localhost ports from package.json/vite config is a
51
+ * documented future enhancement, PRD §B5), so a file-only root set falls back
52
+ * to unscoped rather than scoping to nothing.
53
+ */
54
+ export function deriveAllowedOrigins(roots) {
55
+ const origins = new Set();
56
+ for (const root of roots) {
57
+ try {
58
+ const url = new URL(root.uri);
59
+ if (url.protocol === 'http:' || url.protocol === 'https:') {
60
+ origins.add(url.origin);
61
+ }
62
+ }
63
+ catch {
64
+ // Non-URL root — skip.
65
+ }
66
+ }
67
+ return [...origins];
68
+ }
69
+ /**
70
+ * Consult the client's roots (if advertised) and resolve the session scope,
71
+ * defensively timing out per claude-code Issue #3315. Never throws — every
72
+ * failure mode degrades to {@link UNSCOPED}.
73
+ */
74
+ export async function resolveRootsScope(server, options = {}) {
75
+ const timeoutMs = options.timeoutMs ?? 1000;
76
+ const caps = server.getClientCapabilities();
77
+ if (!caps?.roots) {
78
+ return UNSCOPED('no-roots-capability');
79
+ }
80
+ const raced = await withTimeout(server.listRoots(), timeoutMs).catch(() => ({
81
+ timedOut: false,
82
+ value: undefined,
83
+ }));
84
+ if (raced.timedOut) {
85
+ return UNSCOPED('roots-timeout');
86
+ }
87
+ const result = raced.value;
88
+ if (!result || !Array.isArray(result.roots)) {
89
+ return UNSCOPED('roots-error');
90
+ }
91
+ const allowedOrigins = deriveAllowedOrigins(result.roots);
92
+ if (allowedOrigins.length === 0) {
93
+ return UNSCOPED('no-origins-derived');
94
+ }
95
+ return { allowedOrigins, reason: 'scoped' };
96
+ }
97
+ //# sourceMappingURL=roots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roots.js","sourceRoot":"","sources":["../../src/mcp/roots.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,8EAA8E;AAC9E,+EAA+E;AAC/E,sEAAsE;AACtE,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,2EAA2E;AAC3E,6DAA6D;AAC7D,uEAAuE;AACvE,8EAA8E;AAC9E,4EAA4E;AAC5E,EAAE;AACF,2EAA2E;AAC3E,qEAAqE;AA8BrE,6BAA6B;AAC7B,MAAM,QAAQ,GAAG,CAAC,MAA4B,EAAc,EAAE,CAAC,CAAC;IAC9D,cAAc,EAAE,SAAS;IACzB,MAAM;CACP,CAAC,CAAC;AAEH;;;;GAIG;AACH,KAAK,UAAU,WAAW,CACxB,OAAmB,EACnB,EAAU;IAEV,IAAI,KAAgD,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAqB,CAAC,OAAO,EAAE,EAAE;QAC1D,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1D,yDAAyD;QACzD,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAc,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YAC7D,OAAO;SACR,CAAC,CAAC;QACH,OAAO,KAAK,CAAC;IACf,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAqC;IACxE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBAC1D,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AACtB,CAAC;AAOD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAA0B,EAC1B,UAAoC,EAAE;IAEtC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC;IAC5C,MAAM,IAAI,GAAG,MAAM,CAAC,qBAAqB,EAAE,CAAC;IAC5C,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;QACjB,OAAO,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAC1E,QAAQ,EAAE,KAAc;QACxB,KAAK,EAAE,SAAS;KACjB,CAAC,CAAC,CAAC;IAEJ,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,OAAO,QAAQ,CAAC,eAAe,CAAC,CAAC;IACnC,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC;IAC3B,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5C,OAAO,QAAQ,CAAC,aAAa,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,cAAc,GAAG,oBAAoB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1D,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { EventType, IncrementalSource, MouseInteractions, NodeType, } from '@cubenest/rrweb-core';
2
+ export type { eventWithTime, serializedNodeWithId, incrementalData, incrementalSnapshotEvent, fullSnapshotEvent, metaEvent, mouseInteractionData, inputData, mutationData, addedNodeMutation, attributeMutation, textMutation, } from '@cubenest/rrweb-core';
3
+ //# sourceMappingURL=rrweb-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rrweb-types.d.ts","sourceRoot":"","sources":["../../src/mcp/rrweb-types.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,GACT,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,eAAe,EACf,wBAAwB,EACxB,iBAAiB,EACjB,SAAS,EACT,oBAAoB,EACpB,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,GACb,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,7 @@
1
+ // One local re-export point for the rrweb wire-format types + enums the event
2
+ // walker needs. Everything funnels through @cubenest/rrweb-core (the package
3
+ // that owns the vendored rrweb surface) so peek-mcp never depends on the
4
+ // PostHog rrweb-types package directly — if upstream shifts, rrweb-core is the
5
+ // single place that breaks (ADR-0002).
6
+ export { EventType, IncrementalSource, MouseInteractions, NodeType, } from '@cubenest/rrweb-core';
7
+ //# sourceMappingURL=rrweb-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rrweb-types.js","sourceRoot":"","sources":["../../src/mcp/rrweb-types.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6EAA6E;AAC7E,yEAAyE;AACzE,+EAA+E;AAC/E,uCAAuC;AAEvC,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,GACT,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,54 @@
1
+ import { type serializedNodeWithId } from './rrweb-types.js';
2
+ /** A serialized node plus its resolved parent, for ancestor walks. */
3
+ interface IndexedNode {
4
+ readonly node: serializedNodeWithId;
5
+ readonly parentId: number | null;
6
+ }
7
+ /**
8
+ * A flat index of a serialized snapshot tree: node id -> { node, parentId }.
9
+ * Built once per snapshot so selector derivation and DOM lookups are O(1) per
10
+ * id instead of re-walking the tree.
11
+ */
12
+ export type NodeIndex = ReadonlyMap<number, IndexedNode>;
13
+ /**
14
+ * Max DOM nesting depth the offline walkers descend before stopping. Real
15
+ * browser DOMs are nowhere near this deep; the bound exists because this code
16
+ * ingests UNTRUSTED recordings — a crafted ~10k-deep tree would otherwise blow
17
+ * the call stack. Shared so the index builders and the serializer agree.
18
+ */
19
+ export declare const MAX_DOM_DEPTH = 1000;
20
+ /**
21
+ * Build a {@link NodeIndex} from a FullSnapshot's root serialized node. Nodes
22
+ * deeper than {@link MAX_DOM_DEPTH} are skipped (not indexed) rather than
23
+ * overflowing the stack on an adversarial blob.
24
+ */
25
+ export declare function indexNodes(root: serializedNodeWithId): NodeIndex;
26
+ /** The serialized child nodes of a node, or `[]` for leaf/text/comment nodes. */
27
+ export declare function nodeChildren(node: serializedNodeWithId): serializedNodeWithId[];
28
+ /**
29
+ * Derive the most stable selector for a single node, ignoring ancestors:
30
+ * 1. `#id` (when the id looks static, not an obvious nonce)
31
+ * 2. `[data-testid="…"]` (or data-test / data-cy — test hooks)
32
+ * 3. `tag[name="…"]` (form controls)
33
+ * 4. `tag.class.class` (first two classes)
34
+ * 5. `tag`
35
+ * Returns `undefined` for non-element nodes.
36
+ */
37
+ export declare function localSelector(node: serializedNodeWithId): string | undefined;
38
+ /**
39
+ * Heuristic: does a token look like a stable hook rather than a generated
40
+ * nonce? Rejects hashed/utility tokens (long hex-ish runs, CSS-module suffixes
41
+ * like `Button_x8Hk2`, emotion `css-1q2w3e`). Keeps short, word-y identifiers.
42
+ */
43
+ export declare function isStableToken(token: string): boolean;
44
+ /**
45
+ * Derive a full selector path for `id` within `index`, climbing ancestors until
46
+ * an id/test-hook anchor is hit (or the document root). Joins with `>` to keep
47
+ * the path specific and short. Returns `undefined` if `id` is missing or not an
48
+ * element. The result is a best-effort selector for human/agent consumption and
49
+ * Playwright scripts — not guaranteed unique on a live page, but stable enough
50
+ * for the recorded snapshot.
51
+ */
52
+ export declare function selectorFor(index: NodeIndex, id: number): string | undefined;
53
+ export {};
54
+ //# sourceMappingURL=selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../../src/mcp/selector.ts"],"names":[],"mappings":"AAUA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAEvE,sEAAsE;AACtE,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAEzD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,OAAO,CAAC;AAElC;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,oBAAoB,GAAG,SAAS,CAWhE;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,IAAI,EAAE,oBAAoB,GAAG,oBAAoB,EAAE,CAM/E;AA8BD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,GAAG,SAAS,CAiC5E;AAOD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAWpD;AAwBD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAyC5E"}