@contextstream/mcp-server 0.4.49 → 0.4.51

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.
package/README.md CHANGED
@@ -157,6 +157,33 @@ claude mcp update contextstream -e CONTEXTSTREAM_API_KEY=your_key
157
157
 
158
158
  </details>
159
159
 
160
+ <details>
161
+ <summary><b>GitHub Copilot CLI</b></summary>
162
+
163
+ Use the Copilot CLI to interactively add the MCP server:
164
+
165
+ ```bash
166
+ /mcp add
167
+ ```
168
+
169
+ Or add to `~/.copilot/mcp-config.json`:
170
+
171
+ ```json
172
+ {
173
+ "mcpServers": {
174
+ "contextstream": {
175
+ "command": "npx",
176
+ "args": ["-y", "@contextstream/mcp-server"],
177
+ "env": { "CONTEXTSTREAM_API_KEY": "your_key" }
178
+ }
179
+ }
180
+ }
181
+ ```
182
+
183
+ For more information, see the [GitHub Copilot CLI documentation](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli).
184
+
185
+ </details>
186
+
160
187
  ---
161
188
 
162
189
  ## Links
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
4
10
  var __esm = (fn, res) => function __init() {
5
11
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
12
  };
@@ -28,6 +34,7 @@ __export(hooks_config_exports, {
28
34
  getClineHooksDir: () => getClineHooksDir,
29
35
  getCursorHooksConfigPath: () => getCursorHooksConfigPath,
30
36
  getCursorHooksDir: () => getCursorHooksDir,
37
+ getHookCommand: () => getHookCommand,
31
38
  getHooksDir: () => getHooksDir,
32
39
  getIndexStatusPath: () => getIndexStatusPath,
33
40
  getKiloCodeHooksDir: () => getKiloCodeHooksDir,
@@ -53,6 +60,19 @@ __export(hooks_config_exports, {
53
60
  import * as fs from "node:fs/promises";
54
61
  import * as path from "node:path";
55
62
  import { homedir } from "node:os";
63
+ import { fileURLToPath } from "node:url";
64
+ function getHookCommand(hookName) {
65
+ try {
66
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
67
+ const indexPath = path.join(__dirname, "index.js");
68
+ const fs3 = __require("node:fs");
69
+ if (fs3.existsSync(indexPath)) {
70
+ return `node ${indexPath} hook ${hookName}`;
71
+ }
72
+ } catch {
73
+ }
74
+ return `npx @contextstream/mcp-server hook ${hookName}`;
75
+ }
56
76
  function getClaudeSettingsPath(scope, projectPath) {
57
77
  if (scope === "user") {
58
78
  return path.join(homedir(), ".claude", "settings.json");
@@ -72,19 +92,31 @@ function buildHooksConfig(options) {
72
92
  hooks: [
73
93
  {
74
94
  type: "command",
75
- command: "npx @contextstream/mcp-server hook user-prompt-submit",
95
+ command: getHookCommand("user-prompt-submit"),
76
96
  timeout: 5
77
97
  }
78
98
  ]
79
99
  }
80
100
  ];
101
+ if (options?.includeOnSaveIntent !== false) {
102
+ userPromptHooks.push({
103
+ matcher: "*",
104
+ hooks: [
105
+ {
106
+ type: "command",
107
+ command: getHookCommand("on-save-intent"),
108
+ timeout: 5
109
+ }
110
+ ]
111
+ });
112
+ }
81
113
  if (options?.includeMediaAware !== false) {
82
114
  userPromptHooks.push({
83
115
  matcher: "*",
84
116
  hooks: [
85
117
  {
86
118
  type: "command",
87
- command: "npx @contextstream/mcp-server hook media-aware",
119
+ command: getHookCommand("media-aware"),
88
120
  timeout: 5
89
121
  }
90
122
  ]
@@ -97,7 +129,7 @@ function buildHooksConfig(options) {
97
129
  hooks: [
98
130
  {
99
131
  type: "command",
100
- command: "npx @contextstream/mcp-server hook pre-tool-use",
132
+ command: getHookCommand("pre-tool-use"),
101
133
  timeout: 5
102
134
  }
103
135
  ]
@@ -108,12 +140,39 @@ function buildHooksConfig(options) {
108
140
  if (options?.includePreCompact !== false) {
109
141
  config.PreCompact = [
110
142
  {
111
- // Match both manual (/compact) and automatic compaction
112
143
  matcher: "*",
113
144
  hooks: [
114
145
  {
115
146
  type: "command",
116
- command: "npx @contextstream/mcp-server hook pre-compact",
147
+ command: getHookCommand("pre-compact"),
148
+ timeout: 10
149
+ }
150
+ ]
151
+ }
152
+ ];
153
+ }
154
+ if (options?.includeSessionInit !== false) {
155
+ config.SessionStart = [
156
+ {
157
+ matcher: "*",
158
+ hooks: [
159
+ {
160
+ type: "command",
161
+ command: getHookCommand("session-init"),
162
+ timeout: 10
163
+ }
164
+ ]
165
+ }
166
+ ];
167
+ }
168
+ if (options?.includeSessionEnd !== false) {
169
+ config.Stop = [
170
+ {
171
+ matcher: "*",
172
+ hooks: [
173
+ {
174
+ type: "command",
175
+ command: getHookCommand("session-end"),
117
176
  timeout: 10
118
177
  }
119
178
  ]
@@ -127,7 +186,7 @@ function buildHooksConfig(options) {
127
186
  hooks: [
128
187
  {
129
188
  type: "command",
130
- command: "npx @contextstream/mcp-server hook post-write",
189
+ command: getHookCommand("post-write"),
131
190
  timeout: 10
132
191
  }
133
192
  ]
@@ -139,12 +198,60 @@ function buildHooksConfig(options) {
139
198
  hooks: [
140
199
  {
141
200
  type: "command",
142
- command: "npx @contextstream/mcp-server hook auto-rules",
201
+ command: getHookCommand("auto-rules"),
143
202
  timeout: 15
144
203
  }
145
204
  ]
146
205
  });
147
206
  }
207
+ if (options?.includeOnBash !== false) {
208
+ postToolUseHooks.push({
209
+ matcher: "Bash",
210
+ hooks: [
211
+ {
212
+ type: "command",
213
+ command: getHookCommand("on-bash"),
214
+ timeout: 5
215
+ }
216
+ ]
217
+ });
218
+ }
219
+ if (options?.includeOnTask !== false) {
220
+ postToolUseHooks.push({
221
+ matcher: "Task",
222
+ hooks: [
223
+ {
224
+ type: "command",
225
+ command: getHookCommand("on-task"),
226
+ timeout: 5
227
+ }
228
+ ]
229
+ });
230
+ }
231
+ if (options?.includeOnRead !== false) {
232
+ postToolUseHooks.push({
233
+ matcher: "Read|Glob|Grep",
234
+ hooks: [
235
+ {
236
+ type: "command",
237
+ command: getHookCommand("on-read"),
238
+ timeout: 5
239
+ }
240
+ ]
241
+ });
242
+ }
243
+ if (options?.includeOnWeb !== false) {
244
+ postToolUseHooks.push({
245
+ matcher: "WebFetch|WebSearch",
246
+ hooks: [
247
+ {
248
+ type: "command",
249
+ command: getHookCommand("on-web"),
250
+ timeout: 5
251
+ }
252
+ ]
253
+ });
254
+ }
148
255
  if (postToolUseHooks.length > 0) {
149
256
  config.PostToolUse = postToolUseHooks;
150
257
  }
@@ -154,17 +261,17 @@ async function installHookScripts(options) {
154
261
  const hooksDir = getHooksDir();
155
262
  await fs.mkdir(hooksDir, { recursive: true });
156
263
  const result = {
157
- preToolUse: "npx @contextstream/mcp-server hook pre-tool-use",
158
- userPrompt: "npx @contextstream/mcp-server hook user-prompt-submit"
264
+ preToolUse: getHookCommand("pre-tool-use"),
265
+ userPrompt: getHookCommand("user-prompt-submit")
159
266
  };
160
267
  if (options?.includePreCompact !== false) {
161
- result.preCompact = "npx @contextstream/mcp-server hook pre-compact";
268
+ result.preCompact = getHookCommand("pre-compact");
162
269
  }
163
270
  if (options?.includeMediaAware !== false) {
164
- result.mediaAware = "npx @contextstream/mcp-server hook media-aware";
271
+ result.mediaAware = getHookCommand("media-aware");
165
272
  }
166
273
  if (options?.includeAutoRules !== false) {
167
- result.autoRules = "npx @contextstream/mcp-server hook auto-rules";
274
+ result.autoRules = getHookCommand("auto-rules");
168
275
  }
169
276
  return result;
170
277
  }
@@ -200,20 +307,20 @@ function mergeHooksIntoSettings(existingSettings, newHooks) {
200
307
  async function installClaudeCodeHooks(options) {
201
308
  const result = { scripts: [], settings: [] };
202
309
  result.scripts.push(
203
- "npx @contextstream/mcp-server hook pre-tool-use",
204
- "npx @contextstream/mcp-server hook user-prompt-submit"
310
+ getHookCommand("pre-tool-use"),
311
+ getHookCommand("user-prompt-submit")
205
312
  );
206
313
  if (options.includePreCompact !== false) {
207
- result.scripts.push("npx @contextstream/mcp-server hook pre-compact");
314
+ result.scripts.push(getHookCommand("pre-compact"));
208
315
  }
209
316
  if (options.includeMediaAware !== false) {
210
- result.scripts.push("npx @contextstream/mcp-server hook media-aware");
317
+ result.scripts.push(getHookCommand("media-aware"));
211
318
  }
212
319
  if (options.includePostWrite !== false) {
213
- result.scripts.push("npx @contextstream/mcp-server hook post-write");
320
+ result.scripts.push(getHookCommand("post-write"));
214
321
  }
215
322
  if (options.includeAutoRules !== false) {
216
- result.scripts.push("npx @contextstream/mcp-server hook auto-rules");
323
+ result.scripts.push(getHookCommand("auto-rules"));
217
324
  }
218
325
  const hooksConfig = buildHooksConfig({
219
326
  includePreCompact: options.includePreCompact,
@@ -491,6 +598,8 @@ async function installCursorHookScripts(options) {
491
598
  };
492
599
  const filteredPreToolUse = filterContextStreamHooks(existingConfig.hooks.preToolUse);
493
600
  const filteredBeforeSubmit = filterContextStreamHooks(existingConfig.hooks.beforeSubmitPrompt);
601
+ const preToolUseCommand = getHookCommand("pre-tool-use");
602
+ const userPromptCommand = getHookCommand("user-prompt-submit");
494
603
  const config = {
495
604
  version: 1,
496
605
  hooks: {
@@ -498,7 +607,7 @@ async function installCursorHookScripts(options) {
498
607
  preToolUse: [
499
608
  ...filteredPreToolUse,
500
609
  {
501
- command: "npx @contextstream/mcp-server hook pre-tool-use",
610
+ command: preToolUseCommand,
502
611
  type: "command",
503
612
  timeout: 5,
504
613
  matcher: { tool_name: "Glob|Grep|search_files|list_files|ripgrep" }
@@ -507,7 +616,7 @@ async function installCursorHookScripts(options) {
507
616
  beforeSubmitPrompt: [
508
617
  ...filteredBeforeSubmit,
509
618
  {
510
- command: "npx @contextstream/mcp-server hook user-prompt-submit",
619
+ command: userPromptCommand,
511
620
  type: "command",
512
621
  timeout: 5
513
622
  }
@@ -517,8 +626,8 @@ async function installCursorHookScripts(options) {
517
626
  await writeCursorHooksConfig(config, options.scope, options.projectPath);
518
627
  const configPath = getCursorHooksConfigPath(options.scope, options.projectPath);
519
628
  return {
520
- preToolUse: "npx @contextstream/mcp-server hook pre-tool-use",
521
- beforeSubmitPrompt: "npx @contextstream/mcp-server hook user-prompt-submit",
629
+ preToolUse: preToolUseCommand,
630
+ beforeSubmitPrompt: userPromptCommand,
522
631
  config: configPath
523
632
  };
524
633
  }
@@ -1327,11 +1436,13 @@ esac
1327
1436
 
1328
1437
  exit 0
1329
1438
  `;
1330
- CLINE_HOOK_WRAPPER = (hookName) => `#!/bin/bash
1439
+ CLINE_HOOK_WRAPPER = (hookName) => {
1440
+ const command = getHookCommand(hookName);
1441
+ return `#!/bin/bash
1331
1442
  # ContextStream ${hookName} Hook Wrapper for Cline/Roo/Kilo Code
1332
- # Calls the Node.js hook via npx
1333
- exec npx @contextstream/mcp-server hook ${hookName}
1443
+ exec ${command}
1334
1444
  `;
1445
+ };
1335
1446
  CURSOR_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
1336
1447
  """
1337
1448
  ContextStream PreToolUse Hook for Cursor
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/hooks/on-bash.ts
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { homedir } from "node:os";
7
+ var ENABLED = process.env.CONTEXTSTREAM_BASH_HOOK_ENABLED !== "false";
8
+ var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
9
+ var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
10
+ var WORKSPACE_ID = null;
11
+ function loadConfigFromMcpJson(cwd) {
12
+ let searchDir = path.resolve(cwd);
13
+ for (let i = 0; i < 5; i++) {
14
+ if (!API_KEY) {
15
+ const mcpPath = path.join(searchDir, ".mcp.json");
16
+ if (fs.existsSync(mcpPath)) {
17
+ try {
18
+ const content = fs.readFileSync(mcpPath, "utf-8");
19
+ const config = JSON.parse(content);
20
+ const csEnv = config.mcpServers?.contextstream?.env;
21
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
22
+ API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
23
+ }
24
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
25
+ API_URL = csEnv.CONTEXTSTREAM_API_URL;
26
+ }
27
+ } catch {
28
+ }
29
+ }
30
+ }
31
+ if (!WORKSPACE_ID) {
32
+ const csConfigPath = path.join(searchDir, ".contextstream", "config.json");
33
+ if (fs.existsSync(csConfigPath)) {
34
+ try {
35
+ const content = fs.readFileSync(csConfigPath, "utf-8");
36
+ const csConfig = JSON.parse(content);
37
+ if (csConfig.workspace_id) {
38
+ WORKSPACE_ID = csConfig.workspace_id;
39
+ }
40
+ } catch {
41
+ }
42
+ }
43
+ }
44
+ const parentDir = path.dirname(searchDir);
45
+ if (parentDir === searchDir) break;
46
+ searchDir = parentDir;
47
+ }
48
+ if (!API_KEY) {
49
+ const homeMcpPath = path.join(homedir(), ".mcp.json");
50
+ if (fs.existsSync(homeMcpPath)) {
51
+ try {
52
+ const content = fs.readFileSync(homeMcpPath, "utf-8");
53
+ const config = JSON.parse(content);
54
+ const csEnv = config.mcpServers?.contextstream?.env;
55
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
56
+ API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
57
+ }
58
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
59
+ API_URL = csEnv.CONTEXTSTREAM_API_URL;
60
+ }
61
+ } catch {
62
+ }
63
+ }
64
+ }
65
+ }
66
+ async function captureCommand(command, output, exitCode, isError, sessionId) {
67
+ if (!API_KEY) return;
68
+ const payload = {
69
+ event_type: isError ? "bash_error" : "bash_command",
70
+ title: isError ? `Bash Error: ${command.slice(0, 50)}...` : `Command: ${command.slice(0, 50)}...`,
71
+ content: JSON.stringify({
72
+ command,
73
+ output: output.slice(0, 2e3),
74
+ exit_code: exitCode,
75
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
76
+ }),
77
+ importance: isError ? "high" : "low",
78
+ tags: isError ? ["bash", "error", "command"] : ["bash", "command"],
79
+ source_type: "hook",
80
+ session_id: sessionId
81
+ };
82
+ if (WORKSPACE_ID) {
83
+ payload.workspace_id = WORKSPACE_ID;
84
+ }
85
+ try {
86
+ const controller = new AbortController();
87
+ const timeoutId = setTimeout(() => controller.abort(), 3e3);
88
+ await fetch(`${API_URL}/api/v1/memory/events`, {
89
+ method: "POST",
90
+ headers: {
91
+ "Content-Type": "application/json",
92
+ "X-API-Key": API_KEY
93
+ },
94
+ body: JSON.stringify(payload),
95
+ signal: controller.signal
96
+ });
97
+ clearTimeout(timeoutId);
98
+ } catch {
99
+ }
100
+ }
101
+ async function suggestLesson(command, error) {
102
+ const errorPatterns = [
103
+ {
104
+ pattern: /command not found/i,
105
+ lesson: `The command "${command.split(" ")[0]}" is not installed. Check if the package needs to be installed first.`
106
+ },
107
+ {
108
+ pattern: /permission denied/i,
109
+ lesson: "Permission denied. May need sudo or to check file permissions."
110
+ },
111
+ {
112
+ pattern: /no such file or directory/i,
113
+ lesson: "Path does not exist. Verify the file/directory path before running commands."
114
+ },
115
+ {
116
+ pattern: /EADDRINUSE|address already in use/i,
117
+ lesson: "Port is already in use. Kill the existing process or use a different port."
118
+ },
119
+ {
120
+ pattern: /npm ERR!|ERESOLVE/i,
121
+ lesson: "npm dependency conflict. Try `npm install --legacy-peer-deps` or check package versions."
122
+ },
123
+ {
124
+ pattern: /ENOENT.*package\.json/i,
125
+ lesson: "No package.json found. Make sure you're in the right directory or run `npm init`."
126
+ },
127
+ {
128
+ pattern: /git.*not a git repository/i,
129
+ lesson: "Not in a git repository. Run `git init` or navigate to a git repo."
130
+ }
131
+ ];
132
+ for (const { pattern, lesson } of errorPatterns) {
133
+ if (pattern.test(error)) {
134
+ return lesson;
135
+ }
136
+ }
137
+ return null;
138
+ }
139
+ async function runOnBashHook() {
140
+ if (!ENABLED) {
141
+ process.exit(0);
142
+ }
143
+ let inputData = "";
144
+ for await (const chunk of process.stdin) {
145
+ inputData += chunk;
146
+ }
147
+ if (!inputData.trim()) {
148
+ process.exit(0);
149
+ }
150
+ let input;
151
+ try {
152
+ input = JSON.parse(inputData);
153
+ } catch {
154
+ process.exit(0);
155
+ }
156
+ if (input.tool_name !== "Bash") {
157
+ process.exit(0);
158
+ }
159
+ const cwd = input.cwd || process.cwd();
160
+ loadConfigFromMcpJson(cwd);
161
+ const command = input.tool_input?.command || "";
162
+ const output = input.tool_result?.output || input.tool_result?.error || "";
163
+ const exitCode = input.tool_result?.exit_code ?? 0;
164
+ const sessionId = input.session_id || "unknown";
165
+ const isError = exitCode !== 0 || !!input.tool_result?.error;
166
+ captureCommand(command, output, exitCode, isError, sessionId).catch(() => {
167
+ });
168
+ if (isError) {
169
+ const lesson = await suggestLesson(command, output);
170
+ if (lesson) {
171
+ console.log(
172
+ JSON.stringify({
173
+ hookSpecificOutput: {
174
+ hookEventName: "PostToolUse",
175
+ additionalContext: `[ContextStream Insight] ${lesson}`
176
+ }
177
+ })
178
+ );
179
+ process.exit(0);
180
+ }
181
+ }
182
+ process.exit(0);
183
+ }
184
+ var isDirectRun = process.argv[1]?.includes("on-bash") || process.argv[2] === "on-bash";
185
+ if (isDirectRun) {
186
+ runOnBashHook().catch(() => process.exit(0));
187
+ }
188
+ export {
189
+ runOnBashHook
190
+ };
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/hooks/on-read.ts
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { homedir } from "node:os";
7
+ var ENABLED = process.env.CONTEXTSTREAM_READ_HOOK_ENABLED !== "false";
8
+ var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
9
+ var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
10
+ var WORKSPACE_ID = null;
11
+ var recentCaptures = /* @__PURE__ */ new Set();
12
+ var CAPTURE_WINDOW_MS = 6e4;
13
+ function loadConfigFromMcpJson(cwd) {
14
+ let searchDir = path.resolve(cwd);
15
+ for (let i = 0; i < 5; i++) {
16
+ if (!API_KEY) {
17
+ const mcpPath = path.join(searchDir, ".mcp.json");
18
+ if (fs.existsSync(mcpPath)) {
19
+ try {
20
+ const content = fs.readFileSync(mcpPath, "utf-8");
21
+ const config = JSON.parse(content);
22
+ const csEnv = config.mcpServers?.contextstream?.env;
23
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
24
+ API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
25
+ }
26
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
27
+ API_URL = csEnv.CONTEXTSTREAM_API_URL;
28
+ }
29
+ } catch {
30
+ }
31
+ }
32
+ }
33
+ if (!WORKSPACE_ID) {
34
+ const csConfigPath = path.join(searchDir, ".contextstream", "config.json");
35
+ if (fs.existsSync(csConfigPath)) {
36
+ try {
37
+ const content = fs.readFileSync(csConfigPath, "utf-8");
38
+ const csConfig = JSON.parse(content);
39
+ if (csConfig.workspace_id) {
40
+ WORKSPACE_ID = csConfig.workspace_id;
41
+ }
42
+ } catch {
43
+ }
44
+ }
45
+ }
46
+ const parentDir = path.dirname(searchDir);
47
+ if (parentDir === searchDir) break;
48
+ searchDir = parentDir;
49
+ }
50
+ if (!API_KEY) {
51
+ const homeMcpPath = path.join(homedir(), ".mcp.json");
52
+ if (fs.existsSync(homeMcpPath)) {
53
+ try {
54
+ const content = fs.readFileSync(homeMcpPath, "utf-8");
55
+ const config = JSON.parse(content);
56
+ const csEnv = config.mcpServers?.contextstream?.env;
57
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
58
+ API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
59
+ }
60
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
61
+ API_URL = csEnv.CONTEXTSTREAM_API_URL;
62
+ }
63
+ } catch {
64
+ }
65
+ }
66
+ }
67
+ }
68
+ async function captureExploration(toolName, target, resultSummary, sessionId) {
69
+ if (!API_KEY) return;
70
+ const cacheKey = `${toolName}:${target}`;
71
+ if (recentCaptures.has(cacheKey)) {
72
+ return;
73
+ }
74
+ recentCaptures.add(cacheKey);
75
+ setTimeout(() => recentCaptures.delete(cacheKey), CAPTURE_WINDOW_MS);
76
+ const payload = {
77
+ event_type: "file_exploration",
78
+ title: `${toolName}: ${target.slice(0, 50)}`,
79
+ content: JSON.stringify({
80
+ tool: toolName,
81
+ target,
82
+ result_summary: resultSummary.slice(0, 500),
83
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
84
+ }),
85
+ importance: "low",
86
+ tags: ["exploration", toolName.toLowerCase()],
87
+ source_type: "hook",
88
+ session_id: sessionId
89
+ };
90
+ if (WORKSPACE_ID) {
91
+ payload.workspace_id = WORKSPACE_ID;
92
+ }
93
+ try {
94
+ const controller = new AbortController();
95
+ const timeoutId = setTimeout(() => controller.abort(), 3e3);
96
+ await fetch(`${API_URL}/api/v1/memory/events`, {
97
+ method: "POST",
98
+ headers: {
99
+ "Content-Type": "application/json",
100
+ "X-API-Key": API_KEY
101
+ },
102
+ body: JSON.stringify(payload),
103
+ signal: controller.signal
104
+ });
105
+ clearTimeout(timeoutId);
106
+ } catch {
107
+ }
108
+ }
109
+ async function runOnReadHook() {
110
+ if (!ENABLED) {
111
+ process.exit(0);
112
+ }
113
+ let inputData = "";
114
+ for await (const chunk of process.stdin) {
115
+ inputData += chunk;
116
+ }
117
+ if (!inputData.trim()) {
118
+ process.exit(0);
119
+ }
120
+ let input;
121
+ try {
122
+ input = JSON.parse(inputData);
123
+ } catch {
124
+ process.exit(0);
125
+ }
126
+ const toolName = input.tool_name || "";
127
+ if (!["Read", "Glob", "Grep"].includes(toolName)) {
128
+ process.exit(0);
129
+ }
130
+ const cwd = input.cwd || process.cwd();
131
+ loadConfigFromMcpJson(cwd);
132
+ const sessionId = input.session_id || "unknown";
133
+ let target = "";
134
+ let resultSummary = "";
135
+ switch (toolName) {
136
+ case "Read":
137
+ target = input.tool_input?.file_path || "";
138
+ resultSummary = `Read file: ${target}`;
139
+ break;
140
+ case "Glob":
141
+ target = input.tool_input?.pattern || "";
142
+ const globFiles = input.tool_result?.files || [];
143
+ resultSummary = `Found ${globFiles.length} files matching ${target}`;
144
+ break;
145
+ case "Grep":
146
+ target = input.tool_input?.pattern || "";
147
+ const matches = input.tool_result?.matches || 0;
148
+ resultSummary = `Found ${matches} matches for "${target}"`;
149
+ break;
150
+ }
151
+ if (target) {
152
+ captureExploration(toolName, target, resultSummary, sessionId).catch(() => {
153
+ });
154
+ }
155
+ process.exit(0);
156
+ }
157
+ var isDirectRun = process.argv[1]?.includes("on-read") || process.argv[2] === "on-read";
158
+ if (isDirectRun) {
159
+ runOnReadHook().catch(() => process.exit(0));
160
+ }
161
+ export {
162
+ runOnReadHook
163
+ };