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
@@ -0,0 +1,7 @@
1
+ export interface FsMetrics {
2
+ file_size_bytes?: number;
3
+ lines_count?: number;
4
+ content_sha256?: string;
5
+ }
6
+ export declare function collectFsMetrics(filePath: string, includeHash?: boolean): FsMetrics;
7
+ //# sourceMappingURL=fs_metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs_metrics.d.ts","sourceRoot":"","sources":["../../src/collectors/fs_metrics.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,WAAW,UAAQ,GAClB,SAAS,CAiBX"}
@@ -0,0 +1,36 @@
1
+ import { statSync, readFileSync } from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ const MAX_INLINE_BYTES = 2 * 1024 * 1024;
4
+ export function collectFsMetrics(filePath, includeHash = false) {
5
+ const out = {};
6
+ try {
7
+ const stat = statSync(filePath);
8
+ if (!stat.isFile())
9
+ return out;
10
+ out.file_size_bytes = stat.size;
11
+ if (stat.size <= MAX_INLINE_BYTES) {
12
+ const buf = readFileSync(filePath);
13
+ out.lines_count = countLines(buf);
14
+ if (includeHash) {
15
+ out.content_sha256 = createHash("sha256").update(buf).digest("hex");
16
+ }
17
+ }
18
+ }
19
+ catch {
20
+ // path may not exist; callers are expected to handle missing metrics.
21
+ }
22
+ return out;
23
+ }
24
+ function countLines(buf) {
25
+ if (buf.length === 0)
26
+ return 0;
27
+ let count = 0;
28
+ for (let i = 0; i < buf.length; i++) {
29
+ if (buf[i] === 0x0a)
30
+ count++;
31
+ }
32
+ if (buf[buf.length - 1] !== 0x0a)
33
+ count++;
34
+ return count;
35
+ }
36
+ //# sourceMappingURL=fs_metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs_metrics.js","sourceRoot":"","sources":["../../src/collectors/fs_metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAQzC,MAAM,gBAAgB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzC,MAAM,UAAU,gBAAgB,CAC9B,QAAgB,EAChB,WAAW,GAAG,KAAK;IAEnB,MAAM,GAAG,GAAc,EAAE,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,OAAO,GAAG,CAAC;QAC/B,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC;QAChC,IAAI,IAAI,CAAC,IAAI,IAAI,gBAAgB,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACnC,GAAG,CAAC,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,WAAW,EAAE,CAAC;gBAChB,GAAG,CAAC,cAAc,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,sEAAsE;IACxE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;YAAE,KAAK,EAAE,CAAC;IAC/B,CAAC;IACD,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI;QAAE,KAAK,EAAE,CAAC;IAC1C,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ declare const ConfigFileSchema: z.ZodObject<{
3
+ ingest_url: z.ZodOptional<z.ZodString>;
4
+ ingest_hmac_key: z.ZodOptional<z.ZodString>;
5
+ developer_id: z.ZodOptional<z.ZodString>;
6
+ ide_name: z.ZodOptional<z.ZodEnum<["cursor", "claude_code", "other"]>>;
7
+ cursor_sqlite_path: z.ZodOptional<z.ZodString>;
8
+ claude_projects_root: z.ZodOptional<z.ZodString>;
9
+ batch_interval_ms: z.ZodOptional<z.ZodNumber>;
10
+ batch_max_events: z.ZodOptional<z.ZodNumber>;
11
+ offline_queue_path: z.ZodOptional<z.ZodString>;
12
+ session_file_path: z.ZodOptional<z.ZodString>;
13
+ enable_cursor_tailer: z.ZodOptional<z.ZodBoolean>;
14
+ enable_claude_tailer: z.ZodOptional<z.ZodBoolean>;
15
+ log_level: z.ZodOptional<z.ZodEnum<["silent", "error", "warn", "info", "debug"]>>;
16
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
17
+ ingest_url: z.ZodOptional<z.ZodString>;
18
+ ingest_hmac_key: z.ZodOptional<z.ZodString>;
19
+ developer_id: z.ZodOptional<z.ZodString>;
20
+ ide_name: z.ZodOptional<z.ZodEnum<["cursor", "claude_code", "other"]>>;
21
+ cursor_sqlite_path: z.ZodOptional<z.ZodString>;
22
+ claude_projects_root: z.ZodOptional<z.ZodString>;
23
+ batch_interval_ms: z.ZodOptional<z.ZodNumber>;
24
+ batch_max_events: z.ZodOptional<z.ZodNumber>;
25
+ offline_queue_path: z.ZodOptional<z.ZodString>;
26
+ session_file_path: z.ZodOptional<z.ZodString>;
27
+ enable_cursor_tailer: z.ZodOptional<z.ZodBoolean>;
28
+ enable_claude_tailer: z.ZodOptional<z.ZodBoolean>;
29
+ log_level: z.ZodOptional<z.ZodEnum<["silent", "error", "warn", "info", "debug"]>>;
30
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
31
+ ingest_url: z.ZodOptional<z.ZodString>;
32
+ ingest_hmac_key: z.ZodOptional<z.ZodString>;
33
+ developer_id: z.ZodOptional<z.ZodString>;
34
+ ide_name: z.ZodOptional<z.ZodEnum<["cursor", "claude_code", "other"]>>;
35
+ cursor_sqlite_path: z.ZodOptional<z.ZodString>;
36
+ claude_projects_root: z.ZodOptional<z.ZodString>;
37
+ batch_interval_ms: z.ZodOptional<z.ZodNumber>;
38
+ batch_max_events: z.ZodOptional<z.ZodNumber>;
39
+ offline_queue_path: z.ZodOptional<z.ZodString>;
40
+ session_file_path: z.ZodOptional<z.ZodString>;
41
+ enable_cursor_tailer: z.ZodOptional<z.ZodBoolean>;
42
+ enable_claude_tailer: z.ZodOptional<z.ZodBoolean>;
43
+ log_level: z.ZodOptional<z.ZodEnum<["silent", "error", "warn", "info", "debug"]>>;
44
+ }, z.ZodTypeAny, "passthrough">>;
45
+ export interface ResolvedConfig {
46
+ ingestUrl: string;
47
+ ingestHmacKey: string;
48
+ developerId: string;
49
+ ideName: "cursor" | "claude_code" | "other";
50
+ cursorSqlitePath: string;
51
+ claudeProjectsRoot: string;
52
+ batchIntervalMs: number;
53
+ batchMaxEvents: number;
54
+ offlineQueuePath: string;
55
+ sessionFilePath: string;
56
+ enableCursorTailer: boolean;
57
+ enableClaudeTailer: boolean;
58
+ logLevel: "silent" | "error" | "warn" | "info" | "debug";
59
+ }
60
+ export declare function saveConfig(updates: Partial<z.infer<typeof ConfigFileSchema>>): void;
61
+ export declare function loadConfig(): ResolvedConfig;
62
+ export {};
63
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAgBN,CAAC;AAEjB,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;IAC5C,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAC1D;AA+DD,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC,GAAG,IAAI,CAOnF;AAED,wBAAgB,UAAU,IAAI,cAAc,CAwD3C"}
package/dist/config.js ADDED
@@ -0,0 +1,108 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { z } from "zod";
5
+ const ConfigFileSchema = z
6
+ .object({
7
+ ingest_url: z.string().url().optional(),
8
+ ingest_hmac_key: z.string().optional(),
9
+ developer_id: z.string().optional(),
10
+ ide_name: z.enum(["cursor", "claude_code", "other"]).optional(),
11
+ cursor_sqlite_path: z.string().optional(),
12
+ claude_projects_root: z.string().optional(),
13
+ batch_interval_ms: z.number().int().positive().optional(),
14
+ batch_max_events: z.number().int().positive().optional(),
15
+ offline_queue_path: z.string().optional(),
16
+ session_file_path: z.string().optional(),
17
+ enable_cursor_tailer: z.boolean().optional(),
18
+ enable_claude_tailer: z.boolean().optional(),
19
+ log_level: z.enum(["silent", "error", "warn", "info", "debug"]).optional(),
20
+ })
21
+ .passthrough();
22
+ const CONFIG_DIR = join(homedir(), ".spect8");
23
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
24
+ function defaultCursorSqlitePath() {
25
+ if (process.platform === "darwin") {
26
+ return join(homedir(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
27
+ }
28
+ if (process.platform === "win32") {
29
+ const appData = process.env.APPDATA ?? join(homedir(), "AppData", "Roaming");
30
+ return join(appData, "Cursor", "User", "globalStorage", "state.vscdb");
31
+ }
32
+ return join(homedir(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
33
+ }
34
+ function defaultClaudeProjectsRoot() {
35
+ return join(homedir(), ".claude", "projects");
36
+ }
37
+ function loadFileConfig() {
38
+ if (!existsSync(CONFIG_FILE)) {
39
+ return {};
40
+ }
41
+ try {
42
+ const raw = readFileSync(CONFIG_FILE, "utf8");
43
+ return ConfigFileSchema.parse(JSON.parse(raw));
44
+ }
45
+ catch (err) {
46
+ throw new Error(`failed to read ${CONFIG_FILE}: ${err.message}`);
47
+ }
48
+ }
49
+ function requireString(name, envVar, fromFile) {
50
+ const val = process.env[envVar] ?? fromFile;
51
+ if (!val || val.trim() === "") {
52
+ throw new Error(`missing required setting '${name}'. Set env var ${envVar} or add '${name}' to ${CONFIG_FILE}.`);
53
+ }
54
+ return val;
55
+ }
56
+ export function saveConfig(updates) {
57
+ const current = loadFileConfig();
58
+ const updated = { ...current, ...updates };
59
+ if (!existsSync(CONFIG_DIR)) {
60
+ mkdirSync(CONFIG_DIR, { recursive: true });
61
+ }
62
+ writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2), "utf8");
63
+ }
64
+ export function loadConfig() {
65
+ const file = loadFileConfig();
66
+ if (!existsSync(CONFIG_DIR)) {
67
+ mkdirSync(CONFIG_DIR, { recursive: true });
68
+ }
69
+ const ingestUrl = process.env.SPECT8_INGEST_URL ?? file.ingest_url ?? "https://spect8-production.up.railway.app";
70
+ const ingestHmacKey = process.env.SPECT8_INGEST_HMAC_KEY ?? file.ingest_hmac_key ?? "";
71
+ const developerId = process.env.SPECT8_DEVELOPER_ID ?? file.developer_id ?? "";
72
+ return {
73
+ ingestUrl: ingestUrl.replace(/\/+$/, ""),
74
+ ingestHmacKey,
75
+ developerId,
76
+ ideName: process.env.SPECT8_IDE_NAME ??
77
+ file.ide_name ??
78
+ "other",
79
+ cursorSqlitePath: process.env.SPECT8_CURSOR_SQLITE_PATH ??
80
+ file.cursor_sqlite_path ??
81
+ defaultCursorSqlitePath(),
82
+ claudeProjectsRoot: process.env.SPECT8_CLAUDE_PROJECTS_ROOT ??
83
+ file.claude_projects_root ??
84
+ defaultClaudeProjectsRoot(),
85
+ batchIntervalMs: Number(process.env.SPECT8_BATCH_INTERVAL_MS) ||
86
+ file.batch_interval_ms ||
87
+ 500,
88
+ batchMaxEvents: Number(process.env.SPECT8_BATCH_MAX_EVENTS) ||
89
+ file.batch_max_events ||
90
+ 50,
91
+ offlineQueuePath: process.env.SPECT8_OFFLINE_QUEUE_PATH ??
92
+ file.offline_queue_path ??
93
+ join(CONFIG_DIR, "offline_queue.db"),
94
+ sessionFilePath: process.env.SPECT8_SESSION_FILE ??
95
+ file.session_file_path ??
96
+ join(CONFIG_DIR, "session"),
97
+ enableCursorTailer: process.env.SPECT8_ENABLE_CURSOR_TAILER
98
+ ? process.env.SPECT8_ENABLE_CURSOR_TAILER === "1"
99
+ : file.enable_cursor_tailer ?? true,
100
+ enableClaudeTailer: process.env.SPECT8_ENABLE_CLAUDE_TAILER
101
+ ? process.env.SPECT8_ENABLE_CLAUDE_TAILER === "1"
102
+ : file.enable_claude_tailer ?? true,
103
+ logLevel: process.env.SPECT8_LOG_LEVEL ??
104
+ file.log_level ??
105
+ "info",
106
+ };
107
+ }
108
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,gBAAgB,GAAG,CAAC;KACvB,MAAM,CAAC;IACN,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACvC,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACtC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC/D,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3C,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACzD,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACxD,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACxC,oBAAoB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC5C,oBAAoB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC5C,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC3E,CAAC;KACD,WAAW,EAAE,CAAC;AAkBjB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEpD,SAAS,uBAAuB;IAC9B,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,IAAI,CACT,OAAO,EAAE,EACT,SAAS,EACT,qBAAqB,EACrB,QAAQ,EACR,MAAM,EACN,eAAe,EACf,aAAa,CACd,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QAC7E,OAAO,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,IAAI,CACT,OAAO,EAAE,EACT,SAAS,EACT,QAAQ,EACR,MAAM,EACN,eAAe,EACf,aAAa,CACd,CAAC;AACJ,CAAC;AAED,SAAS,yBAAyB;IAChC,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,cAAc;IACrB,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC9C,OAAO,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,kBAAkB,WAAW,KAAM,GAAa,CAAC,OAAO,EAAE,CAC3D,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CACpB,IAAY,EACZ,MAAc,EACd,QAA4B;IAE5B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC;IAC5C,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,6BAA6B,IAAI,kBAAkB,MAAM,YAAY,IAAI,QAAQ,WAAW,GAAG,CAChG,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAkD;IAC3E,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;IAC3C,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAE9B,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,IAAI,CAAC,UAAU,IAAI,0CAA0C,CAAC;IACjH,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC;IACvF,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;IAE/E,OAAO;QACL,SAAS,EAAE,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACxC,aAAa;QACb,WAAW;QACX,OAAO,EACJ,OAAO,CAAC,GAAG,CAAC,eAAyD;YACtE,IAAI,CAAC,QAAQ;YACb,OAAO;QACT,gBAAgB,EACd,OAAO,CAAC,GAAG,CAAC,yBAAyB;YACrC,IAAI,CAAC,kBAAkB;YACvB,uBAAuB,EAAE;QAC3B,kBAAkB,EAChB,OAAO,CAAC,GAAG,CAAC,2BAA2B;YACvC,IAAI,CAAC,oBAAoB;YACzB,yBAAyB,EAAE;QAC7B,eAAe,EACb,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;YAC5C,IAAI,CAAC,iBAAiB;YACtB,GAAG;QACL,cAAc,EACZ,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC;YAC3C,IAAI,CAAC,gBAAgB;YACrB,EAAE;QACJ,gBAAgB,EACd,OAAO,CAAC,GAAG,CAAC,yBAAyB;YACrC,IAAI,CAAC,kBAAkB;YACvB,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC;QACtC,eAAe,EACb,OAAO,CAAC,GAAG,CAAC,mBAAmB;YAC/B,IAAI,CAAC,iBAAiB;YACtB,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC;QAC7B,kBAAkB,EAChB,OAAO,CAAC,GAAG,CAAC,2BAA2B;YACrC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,KAAK,GAAG;YACjD,CAAC,CAAC,IAAI,CAAC,oBAAoB,IAAI,IAAI;QACvC,kBAAkB,EAChB,OAAO,CAAC,GAAG,CAAC,2BAA2B;YACrC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,KAAK,GAAG;YACjD,CAAC,CAAC,IAAI,CAAC,oBAAoB,IAAI,IAAI;QACvC,QAAQ,EACL,OAAO,CAAC,GAAG,CAAC,gBAA2D;YACxE,IAAI,CAAC,SAAS;YACd,MAAM;KACT,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code `PostToolUse` hook.
4
+ *
5
+ * Invoked after every tool call by the IDE. We re-parse the hook payload from
6
+ * stdin (JSON, as per Claude Code's hook contract) and emit a ToolEvent of the
7
+ * matching kind. Unknown tool names are still captured as `run_command` so we
8
+ * never silently lose data.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=claude_post_tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude_post_tool.d.ts","sourceRoot":"","sources":["../../src/hooks/claude_post_tool.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG"}
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code `PostToolUse` hook.
4
+ *
5
+ * Invoked after every tool call by the IDE. We re-parse the hook payload from
6
+ * stdin (JSON, as per Claude Code's hook contract) and emit a ToolEvent of the
7
+ * matching kind. Unknown tool names are still captured as `run_command` so we
8
+ * never silently lose data.
9
+ */
10
+ import { readFileSync } from "node:fs";
11
+ import { setupHookRuntime, makeToolEvent, finaliseHook } from "./shared.js";
12
+ function readStdin() {
13
+ try {
14
+ const raw = readFileSync(0, "utf8");
15
+ if (!raw.trim())
16
+ return {};
17
+ return JSON.parse(raw);
18
+ }
19
+ catch {
20
+ return {};
21
+ }
22
+ }
23
+ function toToolEventKind(name) {
24
+ const n = (name ?? "").toLowerCase();
25
+ if (n.includes("read") && n.includes("file")) {
26
+ return { tool: "read_file", extra: {} };
27
+ }
28
+ if (n.includes("edit") ||
29
+ n.includes("write") ||
30
+ n === "multiedit" ||
31
+ n === "create_file") {
32
+ return { tool: "write_file", extra: {} };
33
+ }
34
+ if (n.includes("grep") || n.includes("search") || n === "glob") {
35
+ return { tool: "search_files", extra: {} };
36
+ }
37
+ if (n.includes("bash") || n.includes("run") || n.includes("command")) {
38
+ return { tool: "run_command", extra: {} };
39
+ }
40
+ if (n.includes("diff")) {
41
+ return { tool: "git_diff", extra: {} };
42
+ }
43
+ return { tool: "run_command", extra: {} };
44
+ }
45
+ async function main() {
46
+ const bundle = setupHookRuntime();
47
+ const payload = readStdin();
48
+ const { tool } = toToolEventKind(payload.tool_name);
49
+ const input = payload.tool_input ?? {};
50
+ const filePath = typeof input.file_path === "string"
51
+ ? input.file_path
52
+ : typeof input.path === "string"
53
+ ? input.path
54
+ : undefined;
55
+ const command = typeof input.command === "string"
56
+ ? input.command
57
+ : typeof input.cmd === "string"
58
+ ? input.cmd
59
+ : undefined;
60
+ const query = typeof input.query === "string"
61
+ ? input.query
62
+ : typeof input.pattern === "string"
63
+ ? input.pattern
64
+ : undefined;
65
+ const event = makeToolEvent(bundle, tool, {
66
+ file_path: filePath,
67
+ command,
68
+ search_query: query,
69
+ });
70
+ bundle.batcher.enqueueEvent(event);
71
+ await finaliseHook(bundle);
72
+ }
73
+ main().catch((err) => {
74
+ process.stderr.write(`[spect8][claude_post_tool] ${err.message}\n`);
75
+ process.exit(0);
76
+ });
77
+ //# sourceMappingURL=claude_post_tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude_post_tool.js","sourceRoot":"","sources":["../../src/hooks/claude_post_tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code `SessionStart` hook.
4
+ *
5
+ * Rotates the local session id so every Claude Code invocation maps to a
6
+ * distinct session in Spect8. Lightweight - no ingest traffic.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=claude_session_start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude_session_start.d.ts","sourceRoot":"","sources":["../../src/hooks/claude_session_start.ts"],"names":[],"mappings":";AACA;;;;;GAKG"}
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code `SessionStart` hook.
4
+ *
5
+ * Rotates the local session id so every Claude Code invocation maps to a
6
+ * distinct session in Spect8. Lightweight - no ingest traffic.
7
+ */
8
+ import { rotateSessionId } from "../session/session_id.js";
9
+ import { loadConfig } from "../config.js";
10
+ try {
11
+ const config = loadConfig();
12
+ const id = rotateSessionId(config.sessionFilePath);
13
+ process.stderr.write(`[spect8][claude_session_start] new session ${id}\n`);
14
+ }
15
+ catch (err) {
16
+ process.stderr.write(`[spect8][claude_session_start] ${err.message}\n`);
17
+ }
18
+ //# sourceMappingURL=claude_session_start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude_session_start.js","sourceRoot":"","sources":["../../src/hooks/claude_session_start.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,IAAI,CAAC;IACH,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,EAAE,GAAG,eAAe,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,EAAE,IAAI,CAAC,CAAC;AAC7E,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kCAAmC,GAAa,CAAC,OAAO,IAAI,CAC7D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code `Stop` hook.
4
+ *
5
+ * Fires when a session closes. We scan the Claude Code projects root for
6
+ * JSONL files modified since the last boot and flush their `message.usage`
7
+ * blocks to `/api/v1/token_events`.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=claude_stop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude_stop.d.ts","sourceRoot":"","sources":["../../src/hooks/claude_stop.ts"],"names":[],"mappings":";AACA;;;;;;GAMG"}
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code `Stop` hook.
4
+ *
5
+ * Fires when a session closes. We scan the Claude Code projects root for
6
+ * JSONL files modified since the last boot and flush their `message.usage`
7
+ * blocks to `/api/v1/token_events`.
8
+ */
9
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { homedir } from "node:os";
12
+ import { setupHookRuntime, collectTokenEventsFromClaudeJsonl, finaliseHook, } from "./shared.js";
13
+ const STATE_PATH = join(homedir(), ".spect8", "last_claude_scan_ms");
14
+ const DEFAULT_LOOKBACK_MS = 7 * 24 * 60 * 60 * 1000;
15
+ function readWatermark() {
16
+ if (!existsSync(STATE_PATH))
17
+ return Date.now() - DEFAULT_LOOKBACK_MS;
18
+ try {
19
+ const raw = readFileSync(STATE_PATH, "utf8").trim();
20
+ const n = Number(raw);
21
+ return Number.isFinite(n) ? n : Date.now() - DEFAULT_LOOKBACK_MS;
22
+ }
23
+ catch {
24
+ return Date.now() - DEFAULT_LOOKBACK_MS;
25
+ }
26
+ }
27
+ function writeWatermark(ms) {
28
+ try {
29
+ writeFileSync(STATE_PATH, String(ms), "utf8");
30
+ }
31
+ catch {
32
+ // non-fatal
33
+ }
34
+ }
35
+ async function main() {
36
+ const bundle = setupHookRuntime();
37
+ const since = readWatermark();
38
+ const now = Date.now();
39
+ const events = collectTokenEventsFromClaudeJsonl(bundle, since);
40
+ for (const ev of events) {
41
+ bundle.batcher.enqueueTokenEvent(ev);
42
+ }
43
+ await finaliseHook(bundle);
44
+ writeWatermark(now);
45
+ process.stderr.write(`[spect8][claude_stop] flushed ${events.length} token events\n`);
46
+ }
47
+ main().catch((err) => {
48
+ process.stderr.write(`[spect8][claude_stop] ${err.message}\n`);
49
+ process.exit(0);
50
+ });
51
+ //# sourceMappingURL=claude_stop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude_stop.js","sourceRoot":"","sources":["../../src/hooks/claude_stop.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EACL,gBAAgB,EAChB,iCAAiC,EACjC,YAAY,GACb,MAAM,aAAa,CAAC;AAErB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,qBAAqB,CAAC,CAAC;AACrE,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEpD,SAAS,aAAa;IACpB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,mBAAmB,CAAC;IACrE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACpD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,mBAAmB,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,mBAAmB,CAAC;IAC1C,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,EAAU;IAChC,IAAI,CAAC;QACH,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,MAAM,MAAM,GAAG,iCAAiC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAChE,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,CAAC;QACxB,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;IAC3B,cAAc,CAAC,GAAG,CAAC,CAAC;IACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,iCAAiC,MAAM,CAAC,MAAM,iBAAiB,CAChE,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAA0B,GAAa,CAAC,OAAO,IAAI,CAAC,CAAC;IAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Tiny dispatcher so Claude Code's `hooks.json` can run a single bin with
4
+ * different first-arg: session-start | post-tool | stop.
5
+ */
6
+ declare const cmd: string;
7
+ declare function main(): Promise<void>;
8
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/hooks/cli.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,QAAA,MAAM,GAAG,QAAwC,CAAC;AAElD,iBAAe,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAkBnC"}
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Tiny dispatcher so Claude Code's `hooks.json` can run a single bin with
5
+ * different first-arg: session-start | post-tool | stop.
6
+ */
7
+ const cmd = (process.argv[2] ?? "").toLowerCase();
8
+ async function main() {
9
+ if (cmd === "session-start" || cmd === "sessionstart") {
10
+ await import("./claude_session_start.js");
11
+ return;
12
+ }
13
+ if (cmd === "post-tool" || cmd === "posttooluse" || cmd === "posttool") {
14
+ await import("./claude_post_tool.js");
15
+ return;
16
+ }
17
+ if (cmd === "stop") {
18
+ await import("./claude_stop.js");
19
+ return;
20
+ }
21
+ process.stderr.write(`[spect8][hook] unknown subcommand '${cmd}'. ` +
22
+ `Valid: session-start | post-tool | stop\n`);
23
+ process.exit(2);
24
+ }
25
+ void main();
26
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/hooks/cli.ts"],"names":[],"mappings":";;AACA;;;GAGG;AAEH,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAElD,KAAK,UAAU,IAAI;IACjB,IAAI,GAAG,KAAK,eAAe,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;QACtD,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC;QAC1C,OAAO;IACT,CAAC;IACD,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,aAAa,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;QACvE,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;QACtC,OAAO;IACT,CAAC;IACD,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;QACnB,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;QACjC,OAAO;IACT,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,sCAAsC,GAAG,KAAK;QAC5C,2CAA2C,CAC9C,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,KAAK,IAAI,EAAE,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { IngestClient } from "../transport/ingest_client.js";
2
+ import { OfflineQueue } from "../transport/offline_queue.js";
3
+ import { Batcher } from "../transport/batcher.js";
4
+ import type { ToolEvent, TokenEvent } from "../types.js";
5
+ export interface HookBundle {
6
+ sessionId: string;
7
+ developerId: string;
8
+ ideName: "cursor" | "claude_code" | "other";
9
+ batcher: Batcher;
10
+ client: IngestClient;
11
+ queue: OfflineQueue;
12
+ projectsRoot: string;
13
+ }
14
+ /**
15
+ * Bootstraps the same transport stack used by the long-lived MCP server so
16
+ * hook scripts can share the offline queue and HMAC key.
17
+ */
18
+ export declare function setupHookRuntime(): HookBundle;
19
+ export declare function makeToolEvent(bundle: HookBundle, tool: ToolEvent["tool_name"], extra?: Partial<ToolEvent>): ToolEvent;
20
+ /**
21
+ * Scan every `*.jsonl` under the Claude projects root and emit a
22
+ * `TokenEvent` for every assistant message carrying a `message.usage`. Very
23
+ * tolerant of shape drift - unknown entries are skipped rather than thrown.
24
+ */
25
+ export declare function collectTokenEventsFromClaudeJsonl(bundle: HookBundle, since_ms: number): TokenEvent[];
26
+ export declare function finaliseHook(bundle: HookBundle): Promise<void>;
27
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/hooks/shared.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAGlD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,CA6B7C;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,SAAS,CAAC,WAAW,CAAC,EAC5B,KAAK,GAAE,OAAO,CAAC,SAAS,CAAM,GAC7B,SAAS,CAWX;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,GACf,UAAU,EAAE,CAgDd;AAiBD,wBAAsB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAMpE"}