@contextstream/mcp-server 0.4.81 → 1.0.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.
@@ -1,317 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/hooks/pre-compact.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_PRECOMPACT_ENABLED !== "false";
8
- var AUTO_SAVE = process.env.CONTEXTSTREAM_PRECOMPACT_AUTO_SAVE !== "false";
9
- var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
10
- var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
11
- var WORKSPACE_ID = null;
12
- function loadConfigFromMcpJson(cwd) {
13
- let searchDir = path.resolve(cwd);
14
- for (let i = 0; i < 5; i++) {
15
- if (!API_KEY) {
16
- const mcpPath = path.join(searchDir, ".mcp.json");
17
- if (fs.existsSync(mcpPath)) {
18
- try {
19
- const content = fs.readFileSync(mcpPath, "utf-8");
20
- const config = JSON.parse(content);
21
- const csEnv = config.mcpServers?.contextstream?.env;
22
- if (csEnv?.CONTEXTSTREAM_API_KEY) {
23
- API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
24
- }
25
- if (csEnv?.CONTEXTSTREAM_API_URL) {
26
- API_URL = csEnv.CONTEXTSTREAM_API_URL;
27
- }
28
- } catch {
29
- }
30
- }
31
- }
32
- if (!WORKSPACE_ID) {
33
- const csConfigPath = path.join(searchDir, ".contextstream", "config.json");
34
- if (fs.existsSync(csConfigPath)) {
35
- try {
36
- const content = fs.readFileSync(csConfigPath, "utf-8");
37
- const csConfig = JSON.parse(content);
38
- if (csConfig.workspace_id) {
39
- WORKSPACE_ID = csConfig.workspace_id;
40
- }
41
- } catch {
42
- }
43
- }
44
- }
45
- const parentDir = path.dirname(searchDir);
46
- if (parentDir === searchDir) break;
47
- searchDir = parentDir;
48
- }
49
- if (!API_KEY) {
50
- const homeMcpPath = path.join(homedir(), ".mcp.json");
51
- if (fs.existsSync(homeMcpPath)) {
52
- try {
53
- const content = fs.readFileSync(homeMcpPath, "utf-8");
54
- const config = JSON.parse(content);
55
- const csEnv = config.mcpServers?.contextstream?.env;
56
- if (csEnv?.CONTEXTSTREAM_API_KEY) {
57
- API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
58
- }
59
- if (csEnv?.CONTEXTSTREAM_API_URL) {
60
- API_URL = csEnv.CONTEXTSTREAM_API_URL;
61
- }
62
- } catch {
63
- }
64
- }
65
- }
66
- }
67
- function parseTranscript(transcriptPath) {
68
- const activeFiles = /* @__PURE__ */ new Set();
69
- const recentMessages = [];
70
- const toolCalls = [];
71
- const messages = [];
72
- let startedAt = (/* @__PURE__ */ new Date()).toISOString();
73
- let firstTimestamp = true;
74
- try {
75
- const content = fs.readFileSync(transcriptPath, "utf-8");
76
- const lines = content.split("\n");
77
- for (const line of lines) {
78
- if (!line.trim()) continue;
79
- try {
80
- const entry = JSON.parse(line);
81
- const msgType = entry.type || "";
82
- const timestamp = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
83
- if (firstTimestamp && entry.timestamp) {
84
- startedAt = entry.timestamp;
85
- firstTimestamp = false;
86
- }
87
- if (msgType === "tool_use") {
88
- const toolName = entry.name || "";
89
- const toolInput = entry.input || {};
90
- toolCalls.push({ name: toolName, input: toolInput });
91
- if (["Read", "Write", "Edit", "NotebookEdit"].includes(toolName)) {
92
- const filePath = toolInput.file_path || toolInput.notebook_path;
93
- if (filePath) {
94
- activeFiles.add(filePath);
95
- }
96
- } else if (toolName === "Glob") {
97
- const pattern = toolInput.pattern;
98
- if (pattern) {
99
- activeFiles.add(`[glob:${pattern}]`);
100
- }
101
- }
102
- messages.push({
103
- role: "assistant",
104
- content: `[Tool: ${toolName}]`,
105
- timestamp,
106
- tool_calls: { name: toolName, input: toolInput }
107
- });
108
- } else if (msgType === "tool_result") {
109
- const resultContent = typeof entry.content === "string" ? entry.content.slice(0, 2e3) : JSON.stringify(entry.content || {}).slice(0, 2e3);
110
- messages.push({
111
- role: "tool",
112
- content: resultContent,
113
- timestamp,
114
- tool_results: { name: entry.name }
115
- });
116
- } else if (msgType === "user" || entry.role === "user") {
117
- const userContent = typeof entry.content === "string" ? entry.content : "";
118
- if (userContent) {
119
- messages.push({
120
- role: "user",
121
- content: userContent,
122
- timestamp
123
- });
124
- }
125
- } else if (msgType === "assistant" || entry.role === "assistant") {
126
- const assistantContent = typeof entry.content === "string" ? entry.content : "";
127
- if (assistantContent) {
128
- messages.push({
129
- role: "assistant",
130
- content: assistantContent,
131
- timestamp
132
- });
133
- if (assistantContent.length > 50) {
134
- recentMessages.push(assistantContent.slice(0, 500));
135
- }
136
- }
137
- }
138
- } catch {
139
- continue;
140
- }
141
- }
142
- } catch {
143
- }
144
- return {
145
- activeFiles: Array.from(activeFiles).slice(-20),
146
- // Last 20 files
147
- toolCallCount: toolCalls.length,
148
- messageCount: messages.length,
149
- lastTools: toolCalls.slice(-10).map((t) => t.name),
150
- // Last 10 tool names
151
- messages,
152
- startedAt
153
- };
154
- }
155
- async function saveFullTranscript(sessionId, transcriptData, trigger) {
156
- if (!API_KEY) {
157
- return { success: false, message: "No API key configured" };
158
- }
159
- if (transcriptData.messages.length === 0) {
160
- return { success: false, message: "No messages to save" };
161
- }
162
- const payload = {
163
- session_id: sessionId,
164
- messages: transcriptData.messages,
165
- started_at: transcriptData.startedAt,
166
- source_type: "pre_compact",
167
- title: `Pre-compaction save (${trigger})`,
168
- metadata: {
169
- trigger,
170
- active_files: transcriptData.activeFiles,
171
- tool_call_count: transcriptData.toolCallCount
172
- },
173
- tags: ["pre_compaction", trigger]
174
- };
175
- if (WORKSPACE_ID) {
176
- payload.workspace_id = WORKSPACE_ID;
177
- }
178
- try {
179
- const controller = new AbortController();
180
- const timeoutId = setTimeout(() => controller.abort(), 1e4);
181
- const response = await fetch(`${API_URL}/api/v1/transcripts`, {
182
- method: "POST",
183
- headers: {
184
- "Content-Type": "application/json",
185
- "X-API-Key": API_KEY
186
- },
187
- body: JSON.stringify(payload),
188
- signal: controller.signal
189
- });
190
- clearTimeout(timeoutId);
191
- if (response.ok) {
192
- return { success: true, message: `Transcript saved (${transcriptData.messages.length} messages)` };
193
- }
194
- return { success: false, message: `API error: ${response.status}` };
195
- } catch (error) {
196
- return { success: false, message: String(error) };
197
- }
198
- }
199
- async function saveSnapshot(sessionId, transcriptData, trigger) {
200
- if (!API_KEY) {
201
- return { success: false, message: "No API key configured" };
202
- }
203
- const snapshotContent = {
204
- session_id: sessionId,
205
- trigger,
206
- captured_at: null,
207
- // API will set timestamp
208
- active_files: transcriptData.activeFiles,
209
- tool_call_count: transcriptData.toolCallCount,
210
- last_tools: transcriptData.lastTools,
211
- auto_captured: true
212
- };
213
- const payload = {
214
- event_type: "session_snapshot",
215
- title: `Auto Pre-compaction Snapshot (${trigger})`,
216
- content: JSON.stringify(snapshotContent),
217
- importance: "high",
218
- tags: ["session_snapshot", "pre_compaction", "auto_captured"],
219
- source_type: "hook"
220
- };
221
- if (WORKSPACE_ID) {
222
- payload.workspace_id = WORKSPACE_ID;
223
- }
224
- try {
225
- const controller = new AbortController();
226
- const timeoutId = setTimeout(() => controller.abort(), 5e3);
227
- const response = await fetch(`${API_URL}/api/v1/memory/events`, {
228
- method: "POST",
229
- headers: {
230
- "Content-Type": "application/json",
231
- "X-API-Key": API_KEY
232
- },
233
- body: JSON.stringify(payload),
234
- signal: controller.signal
235
- });
236
- clearTimeout(timeoutId);
237
- if (response.ok) {
238
- return { success: true, message: "Snapshot saved" };
239
- }
240
- return { success: false, message: `API error: ${response.status}` };
241
- } catch (error) {
242
- return { success: false, message: String(error) };
243
- }
244
- }
245
- async function runPreCompactHook() {
246
- if (!ENABLED) {
247
- process.exit(0);
248
- }
249
- let inputData = "";
250
- for await (const chunk of process.stdin) {
251
- inputData += chunk;
252
- }
253
- if (!inputData.trim()) {
254
- process.exit(0);
255
- }
256
- let input;
257
- try {
258
- input = JSON.parse(inputData);
259
- } catch {
260
- process.exit(0);
261
- }
262
- const cwd = input.cwd || process.cwd();
263
- loadConfigFromMcpJson(cwd);
264
- const sessionId = input.session_id || "unknown";
265
- const transcriptPath = input.transcript_path || "";
266
- const trigger = input.trigger || "unknown";
267
- const customInstructions = input.custom_instructions || "";
268
- let transcriptData = {
269
- activeFiles: [],
270
- toolCallCount: 0,
271
- messageCount: 0,
272
- lastTools: [],
273
- messages: [],
274
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
275
- };
276
- if (transcriptPath && fs.existsSync(transcriptPath)) {
277
- transcriptData = parseTranscript(transcriptPath);
278
- }
279
- let autoSaveStatus = "";
280
- if (AUTO_SAVE && API_KEY) {
281
- const transcriptResult = await saveFullTranscript(sessionId, transcriptData, trigger);
282
- if (transcriptResult.success) {
283
- autoSaveStatus = `
284
- [ContextStream: ${transcriptResult.message}]`;
285
- } else {
286
- const { success, message } = await saveSnapshot(sessionId, transcriptData, trigger);
287
- if (success) {
288
- autoSaveStatus = `
289
- [ContextStream: Auto-saved snapshot with ${transcriptData.activeFiles.length} active files (transcript save failed: ${transcriptResult.message})]`;
290
- } else {
291
- autoSaveStatus = `
292
- [ContextStream: Auto-save failed - ${message}]`;
293
- }
294
- }
295
- }
296
- const filesList = transcriptData.activeFiles.slice(0, 5).join(", ") || "none detected";
297
- const context = `[CONTEXT COMPACTION - ${trigger.toUpperCase()}]${autoSaveStatus}
298
-
299
- Active files detected: ${filesList}
300
- Tool calls in session: ${transcriptData.toolCallCount}
301
-
302
- After compaction, call session_init(is_post_compact=true) to restore context.${customInstructions ? `
303
- User instructions: ${customInstructions}` : ""}`;
304
- console.log(
305
- JSON.stringify({
306
- additionalContext: context
307
- })
308
- );
309
- process.exit(0);
310
- }
311
- var isDirectRun = process.argv[1]?.includes("pre-compact") || process.argv[2] === "pre-compact";
312
- if (isDirectRun) {
313
- runPreCompactHook().catch(() => process.exit(0));
314
- }
315
- export {
316
- runPreCompactHook
317
- };