spect8-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +69 -0
  2. package/dist/auth.d.ts +7 -0
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/auth.js +60 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +103 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/collectors/claude_jsonl.d.ts +28 -0
  11. package/dist/collectors/claude_jsonl.d.ts.map +1 -0
  12. package/dist/collectors/claude_jsonl.js +130 -0
  13. package/dist/collectors/claude_jsonl.js.map +1 -0
  14. package/dist/collectors/cursor_sqlite.d.ts +37 -0
  15. package/dist/collectors/cursor_sqlite.d.ts.map +1 -0
  16. package/dist/collectors/cursor_sqlite.js +164 -0
  17. package/dist/collectors/cursor_sqlite.js.map +1 -0
  18. package/dist/collectors/fs_metrics.d.ts +7 -0
  19. package/dist/collectors/fs_metrics.d.ts.map +1 -0
  20. package/dist/collectors/fs_metrics.js +36 -0
  21. package/dist/collectors/fs_metrics.js.map +1 -0
  22. package/dist/config.d.ts +63 -0
  23. package/dist/config.d.ts.map +1 -0
  24. package/dist/config.js +108 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/hooks/claude_post_tool.d.ts +11 -0
  27. package/dist/hooks/claude_post_tool.d.ts.map +1 -0
  28. package/dist/hooks/claude_post_tool.js +77 -0
  29. package/dist/hooks/claude_post_tool.js.map +1 -0
  30. package/dist/hooks/claude_session_start.d.ts +9 -0
  31. package/dist/hooks/claude_session_start.d.ts.map +1 -0
  32. package/dist/hooks/claude_session_start.js +18 -0
  33. package/dist/hooks/claude_session_start.js.map +1 -0
  34. package/dist/hooks/claude_stop.d.ts +10 -0
  35. package/dist/hooks/claude_stop.d.ts.map +1 -0
  36. package/dist/hooks/claude_stop.js +51 -0
  37. package/dist/hooks/claude_stop.js.map +1 -0
  38. package/dist/hooks/cli.d.ts +8 -0
  39. package/dist/hooks/cli.d.ts.map +1 -0
  40. package/dist/hooks/cli.js +26 -0
  41. package/dist/hooks/cli.js.map +1 -0
  42. package/dist/hooks/shared.d.ts +27 -0
  43. package/dist/hooks/shared.d.ts.map +1 -0
  44. package/dist/hooks/shared.js +132 -0
  45. package/dist/hooks/shared.js.map +1 -0
  46. package/dist/interceptors/context.d.ts +11 -0
  47. package/dist/interceptors/context.d.ts.map +1 -0
  48. package/dist/interceptors/context.js +13 -0
  49. package/dist/interceptors/context.js.map +1 -0
  50. package/dist/interceptors/file_ops.d.ts +5 -0
  51. package/dist/interceptors/file_ops.d.ts.map +1 -0
  52. package/dist/interceptors/file_ops.js +21 -0
  53. package/dist/interceptors/file_ops.js.map +1 -0
  54. package/dist/interceptors/git_diff.d.ts +4 -0
  55. package/dist/interceptors/git_diff.d.ts.map +1 -0
  56. package/dist/interceptors/git_diff.js +37 -0
  57. package/dist/interceptors/git_diff.js.map +1 -0
  58. package/dist/interceptors/run_command.d.ts +9 -0
  59. package/dist/interceptors/run_command.d.ts.map +1 -0
  60. package/dist/interceptors/run_command.js +10 -0
  61. package/dist/interceptors/run_command.js.map +1 -0
  62. package/dist/interceptors/search_files.d.ts +4 -0
  63. package/dist/interceptors/search_files.d.ts.map +1 -0
  64. package/dist/interceptors/search_files.js +9 -0
  65. package/dist/interceptors/search_files.js.map +1 -0
  66. package/dist/logger.d.ts +11 -0
  67. package/dist/logger.d.ts.map +1 -0
  68. package/dist/logger.js +40 -0
  69. package/dist/logger.js.map +1 -0
  70. package/dist/server.d.ts +12 -0
  71. package/dist/server.d.ts.map +1 -0
  72. package/dist/server.js +218 -0
  73. package/dist/server.js.map +1 -0
  74. package/dist/session/developer_id.d.ts +6 -0
  75. package/dist/session/developer_id.d.ts.map +1 -0
  76. package/dist/session/developer_id.js +25 -0
  77. package/dist/session/developer_id.js.map +1 -0
  78. package/dist/session/session_id.d.ts +8 -0
  79. package/dist/session/session_id.d.ts.map +1 -0
  80. package/dist/session/session_id.js +32 -0
  81. package/dist/session/session_id.js.map +1 -0
  82. package/dist/tools/end_task.d.ts +14 -0
  83. package/dist/tools/end_task.d.ts.map +1 -0
  84. package/dist/tools/end_task.js +25 -0
  85. package/dist/tools/end_task.js.map +1 -0
  86. package/dist/tools/get_score.d.ts +29 -0
  87. package/dist/tools/get_score.d.ts.map +1 -0
  88. package/dist/tools/get_score.js +54 -0
  89. package/dist/tools/get_score.js.map +1 -0
  90. package/dist/tools/report_activity.d.ts +59 -0
  91. package/dist/tools/report_activity.d.ts.map +1 -0
  92. package/dist/tools/report_activity.js +44 -0
  93. package/dist/tools/report_activity.js.map +1 -0
  94. package/dist/tools/start_task.d.ts +28 -0
  95. package/dist/tools/start_task.d.ts.map +1 -0
  96. package/dist/tools/start_task.js +34 -0
  97. package/dist/tools/start_task.js.map +1 -0
  98. package/dist/transport/batcher.d.ts +40 -0
  99. package/dist/transport/batcher.d.ts.map +1 -0
  100. package/dist/transport/batcher.js +115 -0
  101. package/dist/transport/batcher.js.map +1 -0
  102. package/dist/transport/ingest_client.d.ts +20 -0
  103. package/dist/transport/ingest_client.d.ts.map +1 -0
  104. package/dist/transport/ingest_client.js +78 -0
  105. package/dist/transport/ingest_client.js.map +1 -0
  106. package/dist/transport/offline_queue.d.ts +30 -0
  107. package/dist/transport/offline_queue.d.ts.map +1 -0
  108. package/dist/transport/offline_queue.js +83 -0
  109. package/dist/transport/offline_queue.js.map +1 -0
  110. package/dist/transport/sign.d.ts +7 -0
  111. package/dist/transport/sign.d.ts.map +1 -0
  112. package/dist/transport/sign.js +14 -0
  113. package/dist/transport/sign.js.map +1 -0
  114. package/dist/types.d.ts +123 -0
  115. package/dist/types.d.ts.map +1 -0
  116. package/dist/types.js +53 -0
  117. package/dist/types.js.map +1 -0
  118. package/examples/claude/hooks.json +37 -0
  119. package/examples/cursor/mcp.json +14 -0
  120. package/package.json +53 -0
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # @spect8/mcp
2
+
3
+ Local MCP server + Claude Code hooks that capture Cursor / Claude Code tool
4
+ calls and token usage, and forward them to a Spect8 ingest API.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npm install -g @spect8/mcp
10
+ # or invoke on demand:
11
+ npx -y @spect8/mcp
12
+ ```
13
+
14
+ ### One-time config
15
+
16
+ Create `~/.spect8/config.json`:
17
+
18
+ ```json
19
+ {
20
+ "ingest_url": "https://spect8-api.herokuapp.com",
21
+ "ingest_hmac_key": "<32-byte hex shared secret>",
22
+ "developer_id": "dev-your-id",
23
+ "ide_name": "cursor"
24
+ }
25
+ ```
26
+
27
+ All fields can also be supplied via env vars (same uppercase names prefixed
28
+ with `SPECT8_`, e.g. `SPECT8_INGEST_URL`).
29
+
30
+ ## Cursor wiring
31
+
32
+ Copy `examples/cursor/mcp.json` to `~/.cursor/mcp.json` (or your project's
33
+ `.cursor/mcp.json`) and fill in the placeholders.
34
+
35
+ ## Claude Code wiring
36
+
37
+ Copy `examples/claude/hooks.json` to `~/.claude/hooks.json` (or your
38
+ project-local `.claude/hooks.json`) and fill in the placeholders. The hook
39
+ binary runs via npx so global install is not required.
40
+
41
+ ## Provided tools
42
+
43
+ - `start_task(task_label)` - emits `prompt_start` + `start_task` events.
44
+ - `end_task()` - emits an `end_task` event for the active task.
45
+ - `get_score([session_id])` - fetches the caller's live posterior score and,
46
+ when a session id is supplied, the latest session score.
47
+
48
+ ## Transport
49
+
50
+ - HMAC-SHA256 signed POSTs (`X-Spect8-Signature` + `X-Spect8-Timestamp`)
51
+ reaching `/api/v1/events` and `/api/v1/token_events`.
52
+ - 500 ms / 50 event batcher with an offline SQLite queue (`~/.spect8/
53
+ offline_queue.db`) so no event is lost across network blips.
54
+
55
+ ## Local data we read
56
+
57
+ - Cursor: `~/Library/Application Support/Cursor/User/globalStorage/state.vscdb`
58
+ (read-only) - tails `cursorDiskKV.bubbleId:*`.
59
+ - Claude Code: `~/.claude/projects/**/*.jsonl` - extracts
60
+ `message.usage.input_tokens` / `output_tokens`.
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ cd spect8-mcp
66
+ npm install
67
+ npm run build
68
+ npm test
69
+ ```
package/dist/auth.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { Logger } from "./logger.js";
2
+ /**
3
+ * Starts a temporary local server to receive the MCP key from the browser.
4
+ * Returns a promise that resolves when the key is received and saved.
5
+ */
6
+ export declare function runInteractiveAuth(logger: Logger, frontendUrl: string): Promise<void>;
7
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyD3F"}
package/dist/auth.js ADDED
@@ -0,0 +1,60 @@
1
+ import http from "node:http";
2
+ import { exec } from "node:child_process";
3
+ import { saveConfig } from "./config.js";
4
+ /**
5
+ * Starts a temporary local server to receive the MCP key from the browser.
6
+ * Returns a promise that resolves when the key is received and saved.
7
+ */
8
+ export async function runInteractiveAuth(logger, frontendUrl) {
9
+ return new Promise((resolve) => {
10
+ const port = 9999;
11
+ const callbackUrl = `http://localhost:${port}/callback`;
12
+ const connectUrl = `${frontendUrl}/connect?callback=${encodeURIComponent(callbackUrl)}`;
13
+ const server = http.createServer((req, res) => {
14
+ const url = new URL(req.url || "", `http://localhost:${port}`);
15
+ if (url.pathname === "/callback") {
16
+ const key = url.searchParams.get("key");
17
+ const developerId = url.searchParams.get("developerId");
18
+ if (key && developerId) {
19
+ saveConfig({
20
+ ingest_hmac_key: key,
21
+ developer_id: developerId,
22
+ });
23
+ res.writeHead(200, { "Content-Type": "text/html" });
24
+ res.end(`
25
+ <html>
26
+ <body style="background: #0a0a0a; color: white; font-family: sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0;">
27
+ <div style="border: 1px solid #EE4540; padding: 40px; text-align: center; background: #111;">
28
+ <h1 style="color: #EE4540; text-transform: uppercase; letter-spacing: 2px;">Success</h1>
29
+ <p>MCP Connection established. You can close this window.</p>
30
+ </div>
31
+ </body>
32
+ </html>
33
+ `);
34
+ logger.info("Authentication successful! Key saved to config.");
35
+ // Small delay before closing to ensure response is sent
36
+ setTimeout(() => {
37
+ server.close();
38
+ resolve();
39
+ }, 1000);
40
+ }
41
+ else {
42
+ res.writeHead(400);
43
+ res.end("Missing key or developerId");
44
+ }
45
+ }
46
+ else {
47
+ res.writeHead(404);
48
+ res.end();
49
+ }
50
+ });
51
+ server.listen(port, () => {
52
+ logger.info(`Starting interactive auth...`);
53
+ logger.info(`Please visit: ${connectUrl}`);
54
+ // Attempt to open the browser automatically
55
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
56
+ exec(`${openCmd} "${connectUrl}"`);
57
+ });
58
+ });
59
+ }
60
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAc,EAAE,WAAmB;IAC1E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC;QAClB,MAAM,WAAW,GAAG,oBAAoB,IAAI,WAAW,CAAC;QACxD,MAAM,UAAU,GAAG,GAAG,WAAW,qBAAqB,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;QAExF,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YAC5C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,oBAAoB,IAAI,EAAE,CAAC,CAAC;YAE/D,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;gBACjC,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACxC,MAAM,WAAW,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;gBAExD,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;oBACvB,UAAU,CAAC;wBACT,eAAe,EAAE,GAAG;wBACpB,YAAY,EAAE,WAAW;qBAC1B,CAAC,CAAC;oBAEH,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;oBACpD,GAAG,CAAC,GAAG,CAAC;;;;;;;;;WASP,CAAC,CAAC;oBAEH,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;oBAE/D,wDAAwD;oBACxD,UAAU,CAAC,GAAG,EAAE;wBACd,MAAM,CAAC,KAAK,EAAE,CAAC;wBACf,OAAO,EAAE,CAAC;oBACZ,CAAC,EAAE,IAAI,CAAC,CAAC;gBACX,CAAC;qBAAM,CAAC;oBACN,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;oBACnB,GAAG,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;gBACxC,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;YACvB,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;YAC5C,MAAM,CAAC,IAAI,CAAC,iBAAiB,UAAU,EAAE,CAAC,CAAC;YAE3C,4CAA4C;YAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;YAC7G,IAAI,CAAC,GAAG,OAAO,KAAK,UAAU,GAAG,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ const RULES_CONTENT = `# Spect8 AI Proficiency Rules
6
+ You are integrated with the Spect8 Telemetry system. To maintain logical density tracking, you MUST:
7
+ 1. Call \`mcp_spect8_start_task\` at the beginning of every user request.
8
+ 2. Call \`mcp_spect8_end_task\` once the objective is met.
9
+ 3. Identify yourself in the \`ide_name\` parameter (e.g., 'cursor', 'kiro', 'antigravity').
10
+ 4. After EVERY file read, file write, command execution, search, or git diff, call \`mcp_spect8_report_activity\` with:
11
+ - activity_type: the type of action ('read_file', 'write_file', 'run_command', 'git_diff', 'search_files')
12
+ - file_path: the path of the file (for reads/writes)
13
+ - file_size_bytes: size of content written (for writes)
14
+ - command: the command string (for run_command)
15
+ - exit_code: the result code (for run_command)
16
+ `;
17
+ function log(msg) {
18
+ console.log(`[Spect8] ${msg}`);
19
+ }
20
+ function enforceCursor(targetDir) {
21
+ const cursorRulesPath = join(targetDir, ".cursorrules");
22
+ if (!existsSync(cursorRulesPath)) {
23
+ writeFileSync(cursorRulesPath, RULES_CONTENT);
24
+ log("✅ Created .cursorrules");
25
+ }
26
+ else {
27
+ const content = readFileSync(cursorRulesPath, "utf-8");
28
+ if (!content.includes("Spect8")) {
29
+ writeFileSync(cursorRulesPath, content + "\n\n" + RULES_CONTENT);
30
+ log("✅ Appended Spect8 rules to .cursorrules");
31
+ }
32
+ else {
33
+ log("⚡ .cursorrules already contains Spect8 rules");
34
+ }
35
+ }
36
+ }
37
+ function enforceKiro(targetDir) {
38
+ const kiroDir = join(targetDir, ".kiro", "steering");
39
+ mkdirSync(kiroDir, { recursive: true });
40
+ const kiroPath = join(kiroDir, "spect8.md");
41
+ writeFileSync(kiroPath, RULES_CONTENT);
42
+ log("✅ Created .kiro/steering/spect8.md");
43
+ }
44
+ function enforceCopilot(targetDir) {
45
+ const copilotDir = join(targetDir, ".github");
46
+ mkdirSync(copilotDir, { recursive: true });
47
+ const copilotPath = join(copilotDir, "copilot-instructions.md");
48
+ if (!existsSync(copilotPath)) {
49
+ writeFileSync(copilotPath, RULES_CONTENT);
50
+ log("✅ Created .github/copilot-instructions.md");
51
+ }
52
+ else {
53
+ const content = readFileSync(copilotPath, "utf-8");
54
+ if (!content.includes("Spect8")) {
55
+ writeFileSync(copilotPath, content + "\n\n" + RULES_CONTENT);
56
+ log("✅ Appended Spect8 rules to copilot-instructions.md");
57
+ }
58
+ else {
59
+ log("⚡ copilot-instructions.md already contains Spect8 rules");
60
+ }
61
+ }
62
+ }
63
+ function setupGlobalMcpConfig() {
64
+ const mcpConfigDir = join(homedir(), ".spect8");
65
+ mkdirSync(mcpConfigDir, { recursive: true });
66
+ const configPath = join(mcpConfigDir, "config.json");
67
+ if (!existsSync(configPath)) {
68
+ const baseConfig = {
69
+ ingest_url: "http://localhost:8000",
70
+ ingest_hmac_key: "CHANGE_ME",
71
+ developer_id: "default-user",
72
+ ide_name: "auto"
73
+ };
74
+ writeFileSync(configPath, JSON.stringify(baseConfig, null, 2));
75
+ log("✅ Created base ~/.spect8/config.json");
76
+ }
77
+ else {
78
+ log("⚡ ~/.spect8/config.json already exists");
79
+ }
80
+ }
81
+ function main() {
82
+ const args = process.argv.slice(2);
83
+ const command = args[0];
84
+ if (command === "init") {
85
+ const targetDir = process.cwd();
86
+ log(`Initializing Spect8 rules in ${targetDir}...`);
87
+ enforceCursor(targetDir);
88
+ enforceKiro(targetDir);
89
+ enforceCopilot(targetDir);
90
+ // Also try to ensure global config exists
91
+ setupGlobalMcpConfig();
92
+ log("✨ Project is now Spect8-compliant.");
93
+ }
94
+ else {
95
+ console.log(`
96
+ Spect8 CLI
97
+ Usage:
98
+ spect8 init - Inject AI telemetry rules into the current project's IDE configs.
99
+ `);
100
+ }
101
+ }
102
+ main();
103
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { Logger } from "../logger.js";
2
+ import type { Batcher } from "../transport/batcher.js";
3
+ export interface ClaudeTailerOptions {
4
+ projectsRoot: string;
5
+ developerId: string;
6
+ logger: Logger;
7
+ batcher: Batcher;
8
+ getSessionId: () => string;
9
+ }
10
+ /**
11
+ * Watches `~/.claude/projects/*\/*.jsonl` and emits a token event for every
12
+ * assistant message that carries a `message.usage` block. Resilient to
13
+ * partially written lines - we only advance the byte watermark to the last
14
+ * newline we saw, so mid-line appends get replayed cleanly.
15
+ */
16
+ export declare class ClaudeJsonlTailer {
17
+ private readonly opts;
18
+ private watcher;
19
+ private readonly state;
20
+ constructor(opts: ClaudeTailerOptions);
21
+ start(): void;
22
+ stop(): Promise<void>;
23
+ private onChange;
24
+ private scanTrailingNewline;
25
+ private readRange;
26
+ private processLine;
27
+ }
28
+ //# sourceMappingURL=claude_jsonl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude_jsonl.d.ts","sourceRoot":"","sources":["../../src/collectors/claude_jsonl.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAGvD,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,MAAM,CAAC;CAC5B;AAuBD;;;;;GAKG;AACH,qBAAa,iBAAiB;IAIhB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;gBAEzB,IAAI,EAAE,mBAAmB;IAEtD,KAAK,IAAI,IAAI;IAkBP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAKb,QAAQ;YAiCR,mBAAmB;YAuBnB,SAAS;IAiBvB,OAAO,CAAC,WAAW;CAmCpB"}
@@ -0,0 +1,130 @@
1
+ import { createReadStream, existsSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { createInterface } from "node:readline";
4
+ import chokidar from "chokidar";
5
+ /**
6
+ * Watches `~/.claude/projects/*\/*.jsonl` and emits a token event for every
7
+ * assistant message that carries a `message.usage` block. Resilient to
8
+ * partially written lines - we only advance the byte watermark to the last
9
+ * newline we saw, so mid-line appends get replayed cleanly.
10
+ */
11
+ export class ClaudeJsonlTailer {
12
+ opts;
13
+ watcher = null;
14
+ state = new Map();
15
+ constructor(opts) {
16
+ this.opts = opts;
17
+ }
18
+ start() {
19
+ if (this.watcher)
20
+ return;
21
+ const pattern = join(this.opts.projectsRoot, "**", "*.jsonl");
22
+ this.watcher = chokidar.watch(pattern, {
23
+ ignoreInitial: false,
24
+ awaitWriteFinish: {
25
+ stabilityThreshold: 250,
26
+ pollInterval: 100,
27
+ },
28
+ usePolling: false,
29
+ });
30
+ this.watcher.on("add", (p) => void this.onChange(p));
31
+ this.watcher.on("change", (p) => void this.onChange(p));
32
+ this.watcher.on("error", (err) => this.opts.logger.warn("claude_jsonl: watcher error", err.message));
33
+ }
34
+ async stop() {
35
+ await this.watcher?.close();
36
+ this.watcher = null;
37
+ }
38
+ async onChange(path) {
39
+ if (!existsSync(path))
40
+ return;
41
+ try {
42
+ const stat = statSync(path);
43
+ const state = this.state.get(path) ?? { bytesRead: 0, seenIds: new Set() };
44
+ if (stat.size < state.bytesRead) {
45
+ state.bytesRead = 0;
46
+ state.seenIds.clear();
47
+ }
48
+ if (stat.size === state.bytesRead)
49
+ return;
50
+ const lastNewline = await this.scanTrailingNewline(path, state.bytesRead, stat.size);
51
+ const endByte = lastNewline ?? state.bytesRead;
52
+ if (endByte <= state.bytesRead) {
53
+ return;
54
+ }
55
+ await this.readRange(path, state.bytesRead, endByte, state.seenIds);
56
+ state.bytesRead = endByte;
57
+ this.state.set(path, state);
58
+ }
59
+ catch (err) {
60
+ this.opts.logger.debug(`claude_jsonl: ${path} scan failed`, err.message);
61
+ }
62
+ }
63
+ async scanTrailingNewline(path, start, end) {
64
+ return new Promise((resolve) => {
65
+ const stream = createReadStream(path, { start, end: end - 1 });
66
+ let bytes = start;
67
+ let lastNewline = null;
68
+ stream.on("data", (chunk) => {
69
+ const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
70
+ for (let i = 0; i < buf.length; i++) {
71
+ if (buf[i] === 0x0a) {
72
+ lastNewline = bytes + i + 1;
73
+ }
74
+ }
75
+ bytes += buf.length;
76
+ });
77
+ stream.on("end", () => resolve(lastNewline));
78
+ stream.on("error", () => resolve(lastNewline));
79
+ });
80
+ }
81
+ async readRange(path, start, end, seenIds) {
82
+ await new Promise((resolve, reject) => {
83
+ const stream = createReadStream(path, { start, end: end - 1 });
84
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
85
+ rl.on("line", (line) => {
86
+ this.processLine(line.trim(), seenIds);
87
+ });
88
+ rl.on("close", () => resolve());
89
+ stream.on("error", (err) => reject(err));
90
+ });
91
+ }
92
+ processLine(line, seenIds) {
93
+ if (!line)
94
+ return;
95
+ let entry;
96
+ try {
97
+ entry = JSON.parse(line);
98
+ }
99
+ catch {
100
+ return;
101
+ }
102
+ const msg = entry.message;
103
+ if (!msg?.usage)
104
+ return;
105
+ const requestId = msg.id;
106
+ if (!requestId)
107
+ return;
108
+ if (seenIds.has(requestId))
109
+ return;
110
+ seenIds.add(requestId);
111
+ const input = msg.usage.input_tokens ?? 0;
112
+ const output = msg.usage.output_tokens ?? 0;
113
+ if (input + output === 0)
114
+ return;
115
+ const event = {
116
+ request_id: requestId,
117
+ session_id: entry.sessionId ?? this.opts.getSessionId(),
118
+ developer_id: this.opts.developerId,
119
+ timestamp_ms: entry.timestamp ? Date.parse(entry.timestamp) : Date.now(),
120
+ ide_name: "claude_code",
121
+ model_name: msg.model ?? null,
122
+ input_tokens: input,
123
+ output_tokens: output,
124
+ cache_read_input_tokens: msg.usage.cache_read_input_tokens ?? null,
125
+ cache_creation_input_tokens: msg.usage.cache_creation_input_tokens ?? null,
126
+ };
127
+ this.opts.batcher.enqueueTokenEvent(event);
128
+ }
129
+ }
130
+ //# sourceMappingURL=claude_jsonl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude_jsonl.js","sourceRoot":"","sources":["../../src/collectors/claude_jsonl.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { Logger } from "../logger.js";
2
+ import type { Batcher } from "../transport/batcher.js";
3
+ export interface CursorTailerOptions {
4
+ sqlitePath: string;
5
+ developerId: string;
6
+ logger: Logger;
7
+ batcher: Batcher;
8
+ pollIntervalMs?: number;
9
+ getSessionId: () => string;
10
+ }
11
+ /**
12
+ * Polls Cursor's SQLite state file for new `bubbleId:*` rows and posts any
13
+ * token usage they carry to `/api/v1/token_events`. Safe across Cursor
14
+ * upgrades because:
15
+ *
16
+ * - The file is opened read-only (`mode=ro`). If Cursor rewrites it we
17
+ * simply re-open on the next tick.
18
+ * - Rows without a `tokenCount` block are skipped silently (matches the
19
+ * ~99.8% zero-token reality observed after Cursor switched metering
20
+ * providers in Jan 2026).
21
+ * - A rowid watermark (not createdAt) is used so clock-skewed machines
22
+ * still advance and entries with missing timestamps aren't replayed.
23
+ */
24
+ export declare class CursorSqliteTailer {
25
+ private readonly opts;
26
+ private db;
27
+ private timer;
28
+ private watermarkRowId;
29
+ private readonly pollIntervalMs;
30
+ constructor(opts: CursorTailerOptions);
31
+ start(): void;
32
+ stop(): void;
33
+ private tryOpen;
34
+ private tick;
35
+ private toTokenEvent;
36
+ }
37
+ //# sourceMappingURL=cursor_sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor_sqlite.d.ts","sourceRoot":"","sources":["../../src/collectors/cursor_sqlite.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAGvD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,MAAM,CAAC;CAC5B;AAoBD;;;;;;;;;;;;GAYG;AACH,qBAAa,kBAAkB;IAMjB,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,OAAO,CAAC,EAAE,CAAkC;IAC5C,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAEX,IAAI,EAAE,mBAAmB;IAItD,KAAK,IAAI,IAAI;IAqBb,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,OAAO;YAyBD,IAAI;IA4DlB,OAAO,CAAC,YAAY;CAsBrB"}
@@ -0,0 +1,164 @@
1
+ import Database from "better-sqlite3";
2
+ import { existsSync } from "node:fs";
3
+ /**
4
+ * Polls Cursor's SQLite state file for new `bubbleId:*` rows and posts any
5
+ * token usage they carry to `/api/v1/token_events`. Safe across Cursor
6
+ * upgrades because:
7
+ *
8
+ * - The file is opened read-only (`mode=ro`). If Cursor rewrites it we
9
+ * simply re-open on the next tick.
10
+ * - Rows without a `tokenCount` block are skipped silently (matches the
11
+ * ~99.8% zero-token reality observed after Cursor switched metering
12
+ * providers in Jan 2026).
13
+ * - A rowid watermark (not createdAt) is used so clock-skewed machines
14
+ * still advance and entries with missing timestamps aren't replayed.
15
+ */
16
+ export class CursorSqliteTailer {
17
+ opts;
18
+ db = null;
19
+ timer = null;
20
+ watermarkRowId = 0;
21
+ pollIntervalMs;
22
+ constructor(opts) {
23
+ this.opts = opts;
24
+ this.pollIntervalMs = opts.pollIntervalMs ?? 5_000;
25
+ }
26
+ start() {
27
+ if (this.timer)
28
+ return;
29
+ if (this.tryOpen() && this.db) {
30
+ try {
31
+ const max = this.db
32
+ .prepare("SELECT MAX(rowid) AS maxId FROM cursorDiskKV WHERE key LIKE 'bubbleId:%'")
33
+ .get();
34
+ if (max?.maxId) {
35
+ this.watermarkRowId = max.maxId;
36
+ this.opts.logger.debug(`cursor_sqlite: initialized watermark to ${this.watermarkRowId}`);
37
+ }
38
+ }
39
+ catch (err) {
40
+ this.opts.logger.debug("cursor_sqlite: initial watermark query failed", err.message);
41
+ }
42
+ }
43
+ this.timer = setInterval(() => void this.tick(), this.pollIntervalMs);
44
+ this.timer.unref?.();
45
+ }
46
+ stop() {
47
+ if (this.timer) {
48
+ clearInterval(this.timer);
49
+ this.timer = null;
50
+ }
51
+ try {
52
+ this.db?.close();
53
+ }
54
+ catch {
55
+ // close on a readonly file is best-effort
56
+ }
57
+ this.db = null;
58
+ }
59
+ tryOpen() {
60
+ if (this.db)
61
+ return true;
62
+ if (!existsSync(this.opts.sqlitePath)) {
63
+ this.opts.logger.debug(`cursor_sqlite: state file not found at ${this.opts.sqlitePath}`);
64
+ return false;
65
+ }
66
+ try {
67
+ this.db = new Database(this.opts.sqlitePath, {
68
+ fileMustExist: true,
69
+ readonly: true,
70
+ });
71
+ this.db.pragma("journal_mode = WAL");
72
+ return true;
73
+ }
74
+ catch (err) {
75
+ this.opts.logger.warn("cursor_sqlite: failed to open db", err.message);
76
+ this.db = null;
77
+ return false;
78
+ }
79
+ }
80
+ async tick() {
81
+ if (!this.tryOpen() || !this.db)
82
+ return;
83
+ let rows = [];
84
+ try {
85
+ rows = this.db
86
+ .prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'bubbleId:%' AND rowid > ? ORDER BY rowid")
87
+ .all(this.watermarkRowId);
88
+ }
89
+ catch (err) {
90
+ this.opts.logger.debug("cursor_sqlite: tick query failed (will retry)", err.message);
91
+ // schema drift or concurrent vacuum — reopen next tick
92
+ try {
93
+ this.db.close();
94
+ }
95
+ catch {
96
+ /* noop */
97
+ }
98
+ this.db = null;
99
+ return;
100
+ }
101
+ if (rows.length === 0)
102
+ return;
103
+ try {
104
+ const max = this.db
105
+ .prepare("SELECT MAX(rowid) AS maxId FROM cursorDiskKV WHERE key LIKE 'bubbleId:%'")
106
+ .get();
107
+ if (max?.maxId)
108
+ this.watermarkRowId = max.maxId;
109
+ }
110
+ catch {
111
+ /* best-effort watermark */
112
+ }
113
+ if (rows.length > 0) {
114
+ this.opts.logger.debug(`cursor_sqlite: found ${rows.length} new rows`);
115
+ }
116
+ let posted = 0;
117
+ for (const row of rows) {
118
+ try {
119
+ const parsed = JSON.parse(row.value);
120
+ const event = this.toTokenEvent(parsed);
121
+ if (event) {
122
+ this.opts.batcher.enqueueTokenEvent(event);
123
+ posted++;
124
+ }
125
+ }
126
+ catch {
127
+ // unparsable row — ignore silently
128
+ }
129
+ }
130
+ if (posted > 0) {
131
+ this.opts.logger.debug(`cursor_sqlite: queued ${posted} events`);
132
+ }
133
+ }
134
+ toTokenEvent(bubble) {
135
+ const tc = bubble.tokenCount;
136
+ const input = tc?.inputTokens ?? null;
137
+ const output = tc?.outputTokens ?? null;
138
+ const requestId = bubble.requestId ?? bubble.bubbleId;
139
+ if (!requestId)
140
+ return null;
141
+ const ts = toMs(bubble.createdAt);
142
+ return {
143
+ request_id: requestId,
144
+ session_id: this.opts.getSessionId(),
145
+ developer_id: this.opts.developerId,
146
+ timestamp_ms: ts ?? Date.now(),
147
+ ide_name: "cursor",
148
+ model_name: bubble.modelInfo?.modelName ?? bubble.modelInfo?.model ?? null,
149
+ input_tokens: input,
150
+ output_tokens: output,
151
+ cache_read_input_tokens: tc?.cacheReadInputTokens ?? null,
152
+ cache_creation_input_tokens: tc?.cacheCreationInputTokens ?? null,
153
+ };
154
+ }
155
+ }
156
+ function toMs(val) {
157
+ if (val == null)
158
+ return null;
159
+ if (typeof val === "number")
160
+ return val > 1e12 ? val : val * 1000;
161
+ const parsed = Date.parse(val);
162
+ return Number.isFinite(parsed) ? parsed : null;
163
+ }
164
+ //# sourceMappingURL=cursor_sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor_sqlite.js","sourceRoot":"","sources":["../../src/collectors/cursor_sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAiCrC;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,kBAAkB;IAMA;IALrB,EAAE,GAA6B,IAAI,CAAC;IACpC,KAAK,GAA0B,IAAI,CAAC;IACpC,cAAc,GAAG,CAAC,CAAC;IACV,cAAc,CAAS;IAExC,YAA6B,IAAyB;QAAzB,SAAI,GAAJ,IAAI,CAAqB;QACpD,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC;IACrD,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,IAAI,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;qBAChB,OAAO,CACN,0EAA0E,CAC3E;qBACA,GAAG,EAA8B,CAAC;gBACrC,IAAI,GAAG,EAAE,KAAK,EAAE,CAAC;oBACf,IAAI,CAAC,cAAc,GAAG,GAAG,CAAC,KAAK,CAAC;oBAChC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2CAA2C,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;gBAC3F,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,+CAA+C,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;YAClG,CAAC;QACH,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QACtE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IACvB,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,IAAI,CAAC;YACH,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC;YACP,0CAA0C;QAC5C,CAAC;QACD,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;IACjB,CAAC;IAEO,OAAO;QACb,IAAI,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CACpB,0CAA0C,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CACjE,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC;YACH,IAAI,CAAC,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;gBAC3C,aAAa,EAAE,IAAI;gBACnB,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;YACrC,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CACnB,kCAAkC,EACjC,GAAa,CAAC,OAAO,CACvB,CAAC;YACF,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;YACf,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO;QAExC,IAAI,IAAI,GAAgB,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,EAAE;iBACX,OAAO,CACN,8FAA8F,CAC/F;iBACA,GAAG,CAAC,IAAI,CAAC,cAAc,CAAgB,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CACpB,+CAA+C,EAC9C,GAAa,CAAC,OAAO,CACvB,CAAC;YACF,uDAAuD;YACvD,IAAI,CAAC;gBACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YAClB,CAAC;YAAC,MAAM,CAAC;gBACP,UAAU;YACZ,CAAC;YACD,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;YACf,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAE9B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;iBAChB,OAAO,CACN,0EAA0E,CAC3E;iBACA,GAAG,EAA8B,CAAC;YACrC,IAAI,GAAG,EAAE,KAAK;gBAAE,IAAI,CAAC,cAAc,GAAG,GAAG,CAAC,KAAK,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,2BAA2B;QAC7B,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,IAAI,CAAC,MAAM,WAAW,CAAC,CAAC;QACzE,CAAC;QAED,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAiB,CAAC;gBACrD,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;gBACxC,IAAI,KAAK,EAAE,CAAC;oBACV,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;oBAC3C,MAAM,EAAE,CAAC;gBACX,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,mCAAmC;YACrC,CAAC;QACH,CAAC;QACD,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,MAAM,SAAS,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,MAAoB;QACvC,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC;QAC7B,MAAM,KAAK,GAAG,EAAE,EAAE,WAAW,IAAI,IAAI,CAAC;QACtC,MAAM,MAAM,GAAG,EAAE,EAAE,YAAY,IAAI,IAAI,CAAC;QAExC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC;QACtD,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;QAE5B,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAClC,OAAO;YACL,UAAU,EAAE,SAAS;YACrB,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE;YACpC,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW;YACnC,YAAY,EAAE,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE;YAC9B,QAAQ,EAAE,QAAQ;YAClB,UAAU,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,IAAI,MAAM,CAAC,SAAS,EAAE,KAAK,IAAI,IAAI;YAC1E,YAAY,EAAE,KAAK;YACnB,aAAa,EAAE,MAAM;YACrB,uBAAuB,EAAE,EAAE,EAAE,oBAAoB,IAAI,IAAI;YACzD,2BAA2B,EAAE,EAAE,EAAE,wBAAwB,IAAI,IAAI;SAClE,CAAC;IACJ,CAAC;CACF;AAED,SAAS,IAAI,CAAC,GAAgC;IAC5C,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC"}