@contextstream/mcp-server 0.4.55 → 0.4.57

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/dist/index.js CHANGED
@@ -38,6 +38,388 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  mod
39
39
  ));
40
40
 
41
+ // src/version.ts
42
+ import { createRequire } from "module";
43
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
44
+ import { homedir, platform } from "os";
45
+ import { join } from "path";
46
+ import { spawn } from "child_process";
47
+ function getVersion() {
48
+ if (typeof __CONTEXTSTREAM_VERSION__ !== "undefined" && __CONTEXTSTREAM_VERSION__) {
49
+ return __CONTEXTSTREAM_VERSION__;
50
+ }
51
+ try {
52
+ const require2 = createRequire(import.meta.url);
53
+ const pkg = require2("../package.json");
54
+ const version = pkg?.version;
55
+ if (typeof version === "string" && version.trim()) return version.trim();
56
+ } catch {
57
+ }
58
+ return "unknown";
59
+ }
60
+ function compareVersions(v1, v2) {
61
+ const parts1 = v1.split(".").map(Number);
62
+ const parts2 = v2.split(".").map(Number);
63
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
64
+ const p1 = parts1[i] ?? 0;
65
+ const p2 = parts2[i] ?? 0;
66
+ if (p1 < p2) return -1;
67
+ if (p1 > p2) return 1;
68
+ }
69
+ return 0;
70
+ }
71
+ function getCacheFilePath() {
72
+ return join(homedir(), ".contextstream", "version-cache.json");
73
+ }
74
+ function readCache() {
75
+ try {
76
+ const cacheFile = getCacheFilePath();
77
+ if (!existsSync(cacheFile)) return null;
78
+ const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
79
+ if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
80
+ return data;
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+ function writeCache(latestVersion) {
86
+ try {
87
+ const configDir = join(homedir(), ".contextstream");
88
+ if (!existsSync(configDir)) {
89
+ mkdirSync(configDir, { recursive: true });
90
+ }
91
+ const cacheFile = getCacheFilePath();
92
+ writeFileSync(
93
+ cacheFile,
94
+ JSON.stringify({
95
+ latestVersion,
96
+ checkedAt: Date.now()
97
+ })
98
+ );
99
+ } catch {
100
+ }
101
+ }
102
+ async function fetchLatestVersion() {
103
+ try {
104
+ const controller = new AbortController();
105
+ const timeout = setTimeout(() => controller.abort(), 5e3);
106
+ const response = await fetch(NPM_LATEST_URL, {
107
+ signal: controller.signal,
108
+ headers: { Accept: "application/json" }
109
+ });
110
+ clearTimeout(timeout);
111
+ if (!response.ok) return null;
112
+ const data = await response.json();
113
+ return typeof data.version === "string" ? data.version : null;
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+ async function resolveLatestVersion() {
119
+ const cached = readCache();
120
+ if (cached) return cached.latestVersion;
121
+ if (!latestVersionPromise) {
122
+ latestVersionPromise = fetchLatestVersion().finally(() => {
123
+ latestVersionPromise = null;
124
+ });
125
+ }
126
+ const latestVersion = await latestVersionPromise;
127
+ if (latestVersion) {
128
+ writeCache(latestVersion);
129
+ }
130
+ return latestVersion;
131
+ }
132
+ async function checkForUpdates() {
133
+ const notice = await getUpdateNotice();
134
+ if (notice?.behind) {
135
+ showUpdateWarning(notice.current, notice.latest);
136
+ }
137
+ }
138
+ function showUpdateWarning(currentVersion, latestVersion) {
139
+ console.error("");
140
+ console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
141
+ console.error(`\u26A0\uFE0F Update available: v${currentVersion} \u2192 v${latestVersion}`);
142
+ console.error("");
143
+ console.error(` Run: ${UPGRADE_COMMAND}`);
144
+ console.error("");
145
+ console.error(" Then restart your AI tool to use the new version.");
146
+ console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
147
+ console.error("");
148
+ }
149
+ async function getUpdateNotice() {
150
+ const currentVersion = VERSION;
151
+ if (currentVersion === "unknown") return null;
152
+ try {
153
+ const latestVersion = await resolveLatestVersion();
154
+ if (!latestVersion) return null;
155
+ if (compareVersions(currentVersion, latestVersion) < 0) {
156
+ return {
157
+ current: currentVersion,
158
+ latest: latestVersion,
159
+ behind: true,
160
+ upgrade_command: UPGRADE_COMMAND
161
+ };
162
+ }
163
+ } catch {
164
+ }
165
+ return null;
166
+ }
167
+ function getVersionsBehind(current, latest) {
168
+ try {
169
+ const currentParts = current.split(".").map(Number);
170
+ const latestParts = latest.split(".").map(Number);
171
+ if ((latestParts[0] ?? 0) > (currentParts[0] ?? 0)) {
172
+ return 10 + ((latestParts[1] ?? 0) - (currentParts[1] ?? 0));
173
+ }
174
+ const minorDiff = (latestParts[1] ?? 0) - (currentParts[1] ?? 0);
175
+ const patchDiff = (latestParts[2] ?? 0) - (currentParts[2] ?? 0);
176
+ if (minorDiff > 0) {
177
+ return minorDiff;
178
+ } else if (minorDiff === 0 && patchDiff > 0) {
179
+ return 1;
180
+ }
181
+ return 0;
182
+ } catch {
183
+ return 0;
184
+ }
185
+ }
186
+ function getVersionWarning(notice) {
187
+ if (!notice?.behind) return null;
188
+ const versionsBehind = getVersionsBehind(notice.current, notice.latest);
189
+ if (versionsBehind >= 3) {
190
+ return `[VERSION_CRITICAL] MCP server is ${versionsBehind} versions behind (${notice.current} \u2192 ${notice.latest}). Update immediately: ${notice.upgrade_command}`;
191
+ } else if (versionsBehind >= 1) {
192
+ return `[VERSION_NOTICE] MCP server update available (${notice.current} \u2192 ${notice.latest}). Run: ${notice.upgrade_command}`;
193
+ }
194
+ return null;
195
+ }
196
+ function getVersionInstructions(notice) {
197
+ if (!notice?.behind) return null;
198
+ const versionsBehind = getVersionsBehind(notice.current, notice.latest);
199
+ if (versionsBehind >= 5) {
200
+ return `\u26A0\uFE0F CRITICAL: User's MCP is ${versionsBehind} versions behind. Mention update in EVERY response until updated.`;
201
+ } else if (versionsBehind >= 3) {
202
+ return `\u26A0\uFE0F User's MCP is ${versionsBehind} versions behind. Remind about update every 2-3 messages.`;
203
+ } else if (versionsBehind >= 1) {
204
+ return `Note: MCP update available (${notice.current}\u2192${notice.latest}). Mention once at session start.`;
205
+ }
206
+ return null;
207
+ }
208
+ function getVersionNoticeForHook(notice) {
209
+ if (!notice?.behind) return null;
210
+ const versionsBehind = getVersionsBehind(notice.current, notice.latest);
211
+ const severity = versionsBehind >= 5 ? "CRITICAL" : versionsBehind >= 3 ? "IMPORTANT" : "INFO";
212
+ let frequency = "";
213
+ let whatTheyAreMissing = "";
214
+ if (versionsBehind >= 5) {
215
+ frequency = "Mention this update in EVERY response until the user updates.";
216
+ whatTheyAreMissing = `The user is missing out on many new features, important bug fixes, and possible security patches. Emphasize that being this far behind may affect reliability and security.`;
217
+ } else if (versionsBehind >= 3) {
218
+ frequency = "Remind the user about this update every 2-3 messages.";
219
+ whatTheyAreMissing = `The user is missing out on several new features and important fixes that improve reliability and functionality.`;
220
+ } else {
221
+ frequency = "Mention this update once at session start, then only if user asks.";
222
+ whatTheyAreMissing = `Minor update with improvements and fixes available.`;
223
+ }
224
+ return `[VERSION_${severity}] MCP server is ${versionsBehind > 0 ? versionsBehind + " version(s)" : "a version"} behind (${notice.current} \u2192 ${notice.latest}).
225
+
226
+ ${whatTheyAreMissing}
227
+
228
+ ${frequency}
229
+
230
+ When mentioning the update, provide these commands (user can choose their preferred method):
231
+ - macOS/Linux: curl -fsSL https://contextstream.io/scripts/setup.sh | bash
232
+ - Windows: irm https://contextstream.io/scripts/setup.ps1 | iex
233
+ - npm: npm install -g @contextstream/mcp-server@latest
234
+
235
+ Be helpful but not annoying - frame it positively as access to new capabilities rather than criticism.`;
236
+ }
237
+ function isAutoUpdateEnabled() {
238
+ if (process.env.CONTEXTSTREAM_AUTO_UPDATE === "false") {
239
+ return false;
240
+ }
241
+ try {
242
+ const configPath = join(homedir(), ".contextstream", "config.json");
243
+ if (existsSync(configPath)) {
244
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
245
+ if (config.auto_update === false) {
246
+ return false;
247
+ }
248
+ }
249
+ } catch {
250
+ }
251
+ return true;
252
+ }
253
+ function setAutoUpdatePreference(enabled) {
254
+ try {
255
+ const configDir = join(homedir(), ".contextstream");
256
+ const configPath = join(configDir, "config.json");
257
+ let config = {};
258
+ if (existsSync(configPath)) {
259
+ config = JSON.parse(readFileSync(configPath, "utf-8"));
260
+ } else {
261
+ if (!existsSync(configDir)) {
262
+ mkdirSync(configDir, { recursive: true });
263
+ }
264
+ }
265
+ config.auto_update = enabled;
266
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
267
+ } catch {
268
+ }
269
+ }
270
+ async function attemptAutoUpdate() {
271
+ const currentVersion = VERSION;
272
+ if (!isAutoUpdateEnabled()) {
273
+ return {
274
+ attempted: false,
275
+ success: false,
276
+ previousVersion: currentVersion,
277
+ newVersion: null,
278
+ error: "Auto-update disabled"
279
+ };
280
+ }
281
+ const notice = await getUpdateNotice();
282
+ if (!notice?.behind) {
283
+ return {
284
+ attempted: false,
285
+ success: true,
286
+ previousVersion: currentVersion,
287
+ newVersion: null
288
+ };
289
+ }
290
+ const updateMethod = detectUpdateMethod();
291
+ try {
292
+ await runUpdate(updateMethod);
293
+ writeUpdateMarker(currentVersion, notice.latest);
294
+ return {
295
+ attempted: true,
296
+ success: true,
297
+ previousVersion: currentVersion,
298
+ newVersion: notice.latest
299
+ };
300
+ } catch (err) {
301
+ return {
302
+ attempted: true,
303
+ success: false,
304
+ previousVersion: currentVersion,
305
+ newVersion: notice.latest,
306
+ error: err instanceof Error ? err.message : "Update failed"
307
+ };
308
+ }
309
+ }
310
+ function detectUpdateMethod() {
311
+ const execPath = process.argv[1] || "";
312
+ if (execPath.includes("node_modules") || execPath.includes("npm")) {
313
+ return "npm";
314
+ }
315
+ const os2 = platform();
316
+ if (os2 === "win32") {
317
+ return "powershell";
318
+ }
319
+ return "curl";
320
+ }
321
+ async function runUpdate(method) {
322
+ return new Promise((resolve16, reject) => {
323
+ let command;
324
+ let args;
325
+ let shell;
326
+ switch (method) {
327
+ case "npm":
328
+ command = "npm";
329
+ args = ["install", "-g", "@contextstream/mcp-server@latest"];
330
+ shell = false;
331
+ break;
332
+ case "curl":
333
+ command = "bash";
334
+ args = ["-c", "curl -fsSL https://contextstream.io/scripts/setup.sh | bash"];
335
+ shell = false;
336
+ break;
337
+ case "powershell":
338
+ command = "powershell";
339
+ args = ["-Command", "irm https://contextstream.io/scripts/setup.ps1 | iex"];
340
+ shell = false;
341
+ break;
342
+ }
343
+ const proc = spawn(command, args, {
344
+ shell,
345
+ stdio: "ignore",
346
+ detached: true
347
+ });
348
+ proc.on("error", (err) => {
349
+ reject(err);
350
+ });
351
+ proc.on("close", (code) => {
352
+ if (code === 0) {
353
+ resolve16();
354
+ } else {
355
+ reject(new Error(`Update process exited with code ${code}`));
356
+ }
357
+ });
358
+ proc.unref();
359
+ setTimeout(() => resolve16(), 1e3);
360
+ });
361
+ }
362
+ function writeUpdateMarker(previousVersion, newVersion) {
363
+ try {
364
+ const markerPath = join(homedir(), ".contextstream", "update-pending.json");
365
+ const configDir = join(homedir(), ".contextstream");
366
+ if (!existsSync(configDir)) {
367
+ mkdirSync(configDir, { recursive: true });
368
+ }
369
+ writeFileSync(markerPath, JSON.stringify({
370
+ previousVersion,
371
+ newVersion,
372
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
373
+ }));
374
+ } catch {
375
+ }
376
+ }
377
+ function checkUpdateMarker() {
378
+ try {
379
+ const markerPath = join(homedir(), ".contextstream", "update-pending.json");
380
+ if (!existsSync(markerPath)) return null;
381
+ const marker = JSON.parse(readFileSync(markerPath, "utf-8"));
382
+ const updatedAt = new Date(marker.updatedAt);
383
+ const hourAgo = new Date(Date.now() - 60 * 60 * 1e3);
384
+ if (updatedAt < hourAgo) {
385
+ try {
386
+ __require("fs").unlinkSync(markerPath);
387
+ } catch {
388
+ }
389
+ return null;
390
+ }
391
+ return { previousVersion: marker.previousVersion, newVersion: marker.newVersion };
392
+ } catch {
393
+ return null;
394
+ }
395
+ }
396
+ function clearUpdateMarker() {
397
+ try {
398
+ const markerPath = join(homedir(), ".contextstream", "update-pending.json");
399
+ if (existsSync(markerPath)) {
400
+ __require("fs").unlinkSync(markerPath);
401
+ }
402
+ } catch {
403
+ }
404
+ }
405
+ var NPM_LATEST_URL, AUTO_UPDATE_ENABLED, UPDATE_COMMANDS, UPGRADE_COMMAND, VERSION, CACHE_TTL_MS, latestVersionPromise;
406
+ var init_version = __esm({
407
+ "src/version.ts"() {
408
+ "use strict";
409
+ NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
410
+ AUTO_UPDATE_ENABLED = process.env.CONTEXTSTREAM_AUTO_UPDATE !== "false";
411
+ UPDATE_COMMANDS = {
412
+ npm: "npm install -g @contextstream/mcp-server@latest",
413
+ macLinux: "curl -fsSL https://contextstream.io/scripts/setup.sh | bash",
414
+ windows: "irm https://contextstream.io/scripts/setup.ps1 | iex"
415
+ };
416
+ UPGRADE_COMMAND = UPDATE_COMMANDS.npm;
417
+ VERSION = getVersion();
418
+ CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
419
+ latestVersionPromise = null;
420
+ }
421
+ });
422
+
41
423
  // node_modules/ignore/index.js
42
424
  var require_ignore = __commonJS({
43
425
  "node_modules/ignore/index.js"(exports, module) {
@@ -2772,46 +3154,291 @@ function loadConfigFromMcpJson(cwd) {
2772
3154
  }
2773
3155
  }
2774
3156
  }
3157
+ function readTranscriptFile(transcriptPath) {
3158
+ try {
3159
+ const content = fs10.readFileSync(transcriptPath, "utf-8");
3160
+ const lines = content.trim().split("\n");
3161
+ const messages = [];
3162
+ for (const line of lines) {
3163
+ try {
3164
+ const entry = JSON.parse(line);
3165
+ if (entry.type === "user" || entry.type === "assistant") {
3166
+ const msg = entry.message;
3167
+ if (msg?.role && msg?.content) {
3168
+ let textContent = "";
3169
+ if (typeof msg.content === "string") {
3170
+ textContent = msg.content;
3171
+ } else if (Array.isArray(msg.content)) {
3172
+ textContent = msg.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
3173
+ }
3174
+ if (textContent) {
3175
+ messages.push({ role: msg.role, content: textContent });
3176
+ }
3177
+ }
3178
+ }
3179
+ } catch {
3180
+ }
3181
+ }
3182
+ return messages;
3183
+ } catch {
3184
+ return [];
3185
+ }
3186
+ }
3187
+ function extractLastExchange(input, editorFormat) {
3188
+ try {
3189
+ if (editorFormat === "claude" && input.transcript_path) {
3190
+ const messages = readTranscriptFile(input.transcript_path);
3191
+ if (messages.length < 2) return null;
3192
+ let lastAssistantIdx = -1;
3193
+ for (let i = messages.length - 1; i >= 0; i--) {
3194
+ if (messages[i].role === "assistant") {
3195
+ lastAssistantIdx = i;
3196
+ break;
3197
+ }
3198
+ }
3199
+ if (lastAssistantIdx < 1) return null;
3200
+ let lastUserIdx = -1;
3201
+ for (let i = lastAssistantIdx - 1; i >= 0; i--) {
3202
+ if (messages[i].role === "user") {
3203
+ lastUserIdx = i;
3204
+ break;
3205
+ }
3206
+ }
3207
+ if (lastUserIdx < 0) return null;
3208
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3209
+ return {
3210
+ userMessage: { role: "user", content: messages[lastUserIdx].content, timestamp: now },
3211
+ assistantMessage: { role: "assistant", content: messages[lastAssistantIdx].content, timestamp: now },
3212
+ sessionId: input.session_id
3213
+ };
3214
+ }
3215
+ if (editorFormat === "claude" && input.session?.messages) {
3216
+ const messages = input.session.messages;
3217
+ if (messages.length < 2) return null;
3218
+ let lastAssistantIdx = -1;
3219
+ for (let i = messages.length - 1; i >= 0; i--) {
3220
+ if (messages[i].role === "assistant") {
3221
+ lastAssistantIdx = i;
3222
+ break;
3223
+ }
3224
+ }
3225
+ if (lastAssistantIdx < 1) return null;
3226
+ let lastUserIdx = -1;
3227
+ for (let i = lastAssistantIdx - 1; i >= 0; i--) {
3228
+ if (messages[i].role === "user") {
3229
+ lastUserIdx = i;
3230
+ break;
3231
+ }
3232
+ }
3233
+ if (lastUserIdx < 0) return null;
3234
+ const userMsg = messages[lastUserIdx];
3235
+ const assistantMsg = messages[lastAssistantIdx];
3236
+ const extractContent = (content) => {
3237
+ if (typeof content === "string") return content;
3238
+ if (Array.isArray(content)) {
3239
+ return content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
3240
+ }
3241
+ return "";
3242
+ };
3243
+ const userContent = extractContent(userMsg.content);
3244
+ const assistantContent = extractContent(assistantMsg.content);
3245
+ if (!userContent || !assistantContent) return null;
3246
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3247
+ return {
3248
+ userMessage: { role: "user", content: userContent, timestamp: now },
3249
+ assistantMessage: { role: "assistant", content: assistantContent, timestamp: now },
3250
+ sessionId: input.session_id
3251
+ };
3252
+ }
3253
+ if ((editorFormat === "cursor" || editorFormat === "antigravity") && input.history) {
3254
+ const history = input.history;
3255
+ if (history.length < 2) return null;
3256
+ let lastAssistantIdx = -1;
3257
+ for (let i = history.length - 1; i >= 0; i--) {
3258
+ if (history[i].role === "assistant") {
3259
+ lastAssistantIdx = i;
3260
+ break;
3261
+ }
3262
+ }
3263
+ if (lastAssistantIdx < 1) return null;
3264
+ let lastUserIdx = -1;
3265
+ for (let i = lastAssistantIdx - 1; i >= 0; i--) {
3266
+ if (history[i].role === "user") {
3267
+ lastUserIdx = i;
3268
+ break;
3269
+ }
3270
+ }
3271
+ if (lastUserIdx < 0) return null;
3272
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3273
+ return {
3274
+ userMessage: { role: "user", content: history[lastUserIdx].content, timestamp: now },
3275
+ assistantMessage: { role: "assistant", content: history[lastAssistantIdx].content, timestamp: now },
3276
+ sessionId: input.conversationId || input.session_id
3277
+ };
3278
+ }
3279
+ return null;
3280
+ } catch {
3281
+ return null;
3282
+ }
3283
+ }
3284
+ async function saveLastExchange(exchange, cwd, clientName) {
3285
+ if (!API_KEY2) return;
3286
+ const sessionId = exchange.sessionId || `hook-${Buffer.from(cwd).toString("base64").slice(0, 16)}`;
3287
+ const payload = {
3288
+ session_id: sessionId,
3289
+ user_message: exchange.userMessage.content,
3290
+ assistant_message: exchange.assistantMessage.content,
3291
+ client_name: clientName
3292
+ };
3293
+ if (WORKSPACE_ID) {
3294
+ payload.workspace_id = WORKSPACE_ID;
3295
+ }
3296
+ if (PROJECT_ID) {
3297
+ payload.project_id = PROJECT_ID;
3298
+ }
3299
+ try {
3300
+ const controller = new AbortController();
3301
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
3302
+ await fetch(`${API_URL2}/api/v1/transcripts/exchange`, {
3303
+ method: "POST",
3304
+ headers: {
3305
+ "Content-Type": "application/json",
3306
+ "X-API-Key": API_KEY2
3307
+ },
3308
+ body: JSON.stringify(payload),
3309
+ signal: controller.signal
3310
+ });
3311
+ clearTimeout(timeoutId);
3312
+ } catch {
3313
+ }
3314
+ }
2775
3315
  async function fetchSessionContext() {
2776
3316
  if (!API_KEY2) return null;
2777
3317
  try {
2778
3318
  const controller = new AbortController();
2779
3319
  const timeoutId = setTimeout(() => controller.abort(), 3e3);
2780
- const url = new URL(`${API_URL2}/api/v1/context`);
2781
- if (WORKSPACE_ID) url.searchParams.set("workspace_id", WORKSPACE_ID);
2782
- if (PROJECT_ID) url.searchParams.set("project_id", PROJECT_ID);
2783
- url.searchParams.set("include_lessons", "true");
2784
- url.searchParams.set("include_decisions", "true");
2785
- url.searchParams.set("include_plans", "true");
2786
- url.searchParams.set("include_reminders", "true");
2787
- url.searchParams.set("limit", "3");
2788
- const response = await fetch(url.toString(), {
2789
- method: "GET",
3320
+ const url = `${API_URL2}/api/v1/context/smart`;
3321
+ const body = {
3322
+ user_message: "hook context fetch",
3323
+ max_tokens: 200,
3324
+ format: "readable"
3325
+ };
3326
+ if (WORKSPACE_ID) body.workspace_id = WORKSPACE_ID;
3327
+ if (PROJECT_ID) body.project_id = PROJECT_ID;
3328
+ const response = await fetch(url, {
3329
+ method: "POST",
2790
3330
  headers: {
2791
- "X-API-Key": API_KEY2
3331
+ "X-API-Key": API_KEY2,
3332
+ "Content-Type": "application/json"
2792
3333
  },
3334
+ body: JSON.stringify(body),
2793
3335
  signal: controller.signal
2794
3336
  });
2795
3337
  clearTimeout(timeoutId);
2796
3338
  if (response.ok) {
2797
- return await response.json();
3339
+ const data = await response.json();
3340
+ return transformSmartContextResponse(data);
2798
3341
  }
2799
3342
  return null;
2800
3343
  } catch {
2801
3344
  return null;
2802
3345
  }
2803
3346
  }
2804
- function buildEnhancedReminder(ctx, isNewSession2) {
3347
+ function transformSmartContextResponse(data) {
3348
+ try {
3349
+ const response = data;
3350
+ const result = {};
3351
+ if (response.data?.warnings && response.data.warnings.length > 0) {
3352
+ result.lessons = response.data.warnings.map((w) => ({
3353
+ title: "Lesson",
3354
+ trigger: "",
3355
+ prevention: w.replace(/^\[LESSONS_WARNING\]\s*/, "")
3356
+ }));
3357
+ }
3358
+ if (response.data?.items) {
3359
+ for (const item of response.data.items) {
3360
+ if (item.item_type === "preference") {
3361
+ if (!result.preferences) result.preferences = [];
3362
+ result.preferences.push({
3363
+ title: item.title,
3364
+ content: item.content,
3365
+ importance: item.metadata?.importance || "medium"
3366
+ });
3367
+ } else if (item.item_type === "plan") {
3368
+ if (!result.active_plans) result.active_plans = [];
3369
+ result.active_plans.push({
3370
+ title: item.title,
3371
+ status: "active"
3372
+ });
3373
+ } else if (item.item_type === "task") {
3374
+ if (!result.pending_tasks) result.pending_tasks = [];
3375
+ result.pending_tasks.push({
3376
+ title: item.title,
3377
+ status: "pending"
3378
+ });
3379
+ } else if (item.item_type === "reminder") {
3380
+ if (!result.reminders) result.reminders = [];
3381
+ result.reminders.push({
3382
+ title: item.title,
3383
+ content: item.content
3384
+ });
3385
+ }
3386
+ }
3387
+ }
3388
+ return result;
3389
+ } catch {
3390
+ return null;
3391
+ }
3392
+ }
3393
+ function buildClaudeReminder(ctx, versionNotice) {
3394
+ const parts = [];
3395
+ if (versionNotice?.behind) {
3396
+ const versionInfo = getVersionNoticeForHook(versionNotice);
3397
+ if (versionInfo) {
3398
+ parts.push(versionInfo);
3399
+ parts.push("");
3400
+ }
3401
+ }
3402
+ const highImportancePrefs = ctx?.preferences?.filter((p) => p.importance === "high") || [];
3403
+ if (highImportancePrefs.length > 0) {
3404
+ parts.push(`[USER PREFERENCES - Always respect these]`);
3405
+ for (const pref of highImportancePrefs.slice(0, 5)) {
3406
+ parts.push(`\u2022 ${pref.title}: ${pref.content}`);
3407
+ }
3408
+ parts.push("");
3409
+ }
3410
+ parts.push(REMINDER);
3411
+ return parts.join("\n");
3412
+ }
3413
+ function buildEnhancedReminder(ctx, isNewSession2, versionNotice) {
2805
3414
  const parts = [ENHANCED_REMINDER_HEADER];
3415
+ if (versionNotice?.behind) {
3416
+ const versionInfo = getVersionNoticeForHook(versionNotice);
3417
+ if (versionInfo) {
3418
+ parts.push(`## \u{1F504} UPDATE AVAILABLE
3419
+ `);
3420
+ parts.push(versionInfo);
3421
+ parts.push("");
3422
+ }
3423
+ }
2806
3424
  if (isNewSession2) {
2807
3425
  parts.push(`## \u{1F680} NEW SESSION DETECTED
2808
3426
  1. Call \`init(folder_path="...")\` - this triggers project indexing
2809
3427
  2. Wait for indexing: if \`init\` returns \`indexing_status: "started"\`, files are being indexed
2810
- 3. Then call \`context(user_message="...")\` for task-specific context
2811
- 4. Use \`search(mode="hybrid")\` for code discovery (not Glob/Grep/Read)
3428
+ 3. Generate a unique session_id (e.g., "session-" + timestamp or UUID) - use this for ALL context() calls
3429
+ 4. Call \`context(user_message="...", save_exchange=true, session_id="<your-session-id>")\` for task-specific context
3430
+ 5. Use \`search(mode="hybrid")\` for code discovery (not Glob/Grep/Read)
2812
3431
 
2813
3432
  `);
2814
3433
  }
3434
+ const highImportancePrefs = ctx?.preferences?.filter((p) => p.importance === "high") || [];
3435
+ if (highImportancePrefs.length > 0) {
3436
+ parts.push(`## \u2699\uFE0F USER PREFERENCES - Always respect these`);
3437
+ for (const pref of highImportancePrefs.slice(0, 5)) {
3438
+ parts.push(`- **${pref.title}**: ${pref.content}`);
3439
+ }
3440
+ parts.push("");
3441
+ }
2815
3442
  if (ctx?.lessons && ctx.lessons.length > 0) {
2816
3443
  parts.push(`## \u26A0\uFE0F LESSONS FROM PAST MISTAKES`);
2817
3444
  for (const lesson of ctx.lessons.slice(0, 3)) {
@@ -2929,20 +3556,26 @@ async function runUserPromptSubmitHook() {
2929
3556
  }
2930
3557
  const editorFormat = detectEditorFormat2(input);
2931
3558
  const cwd = input.cwd || process.cwd();
3559
+ loadConfigFromMcpJson(cwd);
3560
+ const versionNoticePromise = getUpdateNotice();
3561
+ const lastExchange = extractLastExchange(input, editorFormat);
3562
+ const clientName = editorFormat === "claude" ? "claude-code" : editorFormat;
3563
+ const saveExchangePromise = lastExchange ? saveLastExchange(lastExchange, cwd, clientName) : Promise.resolve();
2932
3564
  if (editorFormat === "claude") {
3565
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
3566
+ const claudeReminder = buildClaudeReminder(ctx, versionNotice);
2933
3567
  console.log(
2934
3568
  JSON.stringify({
2935
3569
  hookSpecificOutput: {
2936
3570
  hookEventName: "UserPromptSubmit",
2937
- additionalContext: REMINDER
3571
+ additionalContext: claudeReminder
2938
3572
  }
2939
3573
  })
2940
3574
  );
2941
3575
  } else if (editorFormat === "cline") {
2942
- loadConfigFromMcpJson(cwd);
2943
3576
  const newSession = isNewSession(input, editorFormat);
2944
- const ctx = await fetchSessionContext();
2945
- const enhancedReminder = buildEnhancedReminder(ctx, newSession);
3577
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
3578
+ const enhancedReminder = buildEnhancedReminder(ctx, newSession, versionNotice);
2946
3579
  console.log(
2947
3580
  JSON.stringify({
2948
3581
  cancel: false,
@@ -2950,10 +3583,12 @@ async function runUserPromptSubmitHook() {
2950
3583
  })
2951
3584
  );
2952
3585
  } else if (editorFormat === "cursor") {
2953
- loadConfigFromMcpJson(cwd);
2954
3586
  const newSession = isNewSession(input, editorFormat);
2955
- const ctx = await fetchSessionContext();
2956
- const cursorReminder = ctx?.lessons?.length ? `[CONTEXTSTREAM] \u26A0\uFE0F ${ctx.lessons.length} lessons from past mistakes. Use search(mode="hybrid") before Glob/Grep. Call context() first. After file edits: project(action="index") to re-index.` : `[CONTEXTSTREAM] Use search(mode="hybrid") before Glob/Grep/Read. Call context() first. After file edits: project(action="index") to re-index.`;
3587
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
3588
+ let cursorReminder = ctx?.lessons?.length ? `[CONTEXTSTREAM] \u26A0\uFE0F ${ctx.lessons.length} lessons from past mistakes. Call context(save_exchange=true, session_id="...") FIRST. Use search(mode="hybrid") before Glob/Grep. After file edits: project(action="index").` : `[CONTEXTSTREAM] Call context(save_exchange=true, session_id="...") FIRST. Use search(mode="hybrid") before Glob/Grep/Read. After file edits: project(action="index").`;
3589
+ if (versionNotice?.behind) {
3590
+ cursorReminder += ` [UPDATE v${versionNotice.current}\u2192${versionNotice.latest}]`;
3591
+ }
2957
3592
  console.log(
2958
3593
  JSON.stringify({
2959
3594
  continue: true,
@@ -2961,10 +3596,9 @@ async function runUserPromptSubmitHook() {
2961
3596
  })
2962
3597
  );
2963
3598
  } else if (editorFormat === "antigravity") {
2964
- loadConfigFromMcpJson(cwd);
2965
3599
  const newSession = isNewSession(input, editorFormat);
2966
- const ctx = await fetchSessionContext();
2967
- const enhancedReminder = buildEnhancedReminder(ctx, newSession);
3600
+ const [ctx, versionNotice] = await Promise.all([fetchSessionContext(), versionNoticePromise, saveExchangePromise]);
3601
+ const enhancedReminder = buildEnhancedReminder(ctx, newSession, versionNotice);
2968
3602
  console.log(
2969
3603
  JSON.stringify({
2970
3604
  cancel: false,
@@ -2978,6 +3612,7 @@ var ENABLED3, API_URL2, API_KEY2, WORKSPACE_ID, PROJECT_ID, REMINDER, ENHANCED_R
2978
3612
  var init_user_prompt_submit = __esm({
2979
3613
  "src/hooks/user-prompt-submit.ts"() {
2980
3614
  "use strict";
3615
+ init_version();
2981
3616
  ENABLED3 = process.env.CONTEXTSTREAM_REMINDER_ENABLED !== "false";
2982
3617
  API_URL2 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
2983
3618
  API_KEY2 = process.env.CONTEXTSTREAM_API_KEY || "";
@@ -2985,9 +3620,11 @@ var init_user_prompt_submit = __esm({
2985
3620
  PROJECT_ID = null;
2986
3621
  REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
2987
3622
 
2988
- 1. FIRST: Call mcp__contextstream__context(user_message="...") before ANY other tool
3623
+ 1. FIRST: Call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>") before ANY other tool
2989
3624
  - Returns: dynamic rules, lessons from past mistakes, relevant context
2990
3625
  - Check response for: [LESSONS_WARNING], [RULES_NOTICE], preferences
3626
+ - save_exchange=true saves each conversation turn for later retrieval
3627
+ - Use a consistent session_id for the entire conversation (generate once on first message)
2991
3628
 
2992
3629
  2. FOR CODE SEARCH: Check index status, then search appropriately
2993
3630
  \u26A0\uFE0F BEFORE searching: mcp__contextstream__project(action="index_status")
@@ -3014,7 +3651,7 @@ var init_user_prompt_submit = __esm({
3014
3651
 
3015
3652
  6. SKIP CONTEXTSTREAM: If user preference says "skip contextstream", use local tools instead
3016
3653
  [END]`;
3017
- ENHANCED_REMINDER_HEADER = `[CONTEXTSTREAM - ENHANCED CONTEXT]
3654
+ ENHANCED_REMINDER_HEADER = `\u2B21 ContextStream \u2014 Smart Context & Memory
3018
3655
 
3019
3656
  `;
3020
3657
  isDirectRun3 = process.argv[1]?.includes("user-prompt-submit") || process.argv[2] === "user-prompt-submit";
@@ -4559,13 +5196,44 @@ async function fetchSessionContext2() {
4559
5196
  return null;
4560
5197
  }
4561
5198
  }
4562
- function formatContext(ctx) {
5199
+ function formatContext(ctx, options = {}) {
5200
+ const parts = ["\u2B21 ContextStream \u2014 Smart Context & Memory", ""];
5201
+ if (options.updateMarker) {
5202
+ parts.push(`
5203
+ ## \u{1F389} UPDATE COMPLETE - RESTART CHAT SESSION`);
5204
+ parts.push(`ContextStream was auto-updated from v${options.updateMarker.previousVersion} to v${options.updateMarker.newVersion}.`);
5205
+ parts.push(`**Tell the user:** "ContextStream has been updated! Please start a new chat session (or restart your AI tool) to use the new version with the latest features and fixes."`);
5206
+ parts.push("");
5207
+ } else if (options.autoUpdateResult?.attempted) {
5208
+ if (options.autoUpdateResult.success) {
5209
+ parts.push(`
5210
+ ## \u{1F504} AUTO-UPDATE INITIATED`);
5211
+ parts.push(`ContextStream is updating from v${options.autoUpdateResult.previousVersion} to v${options.autoUpdateResult.newVersion}.`);
5212
+ parts.push(`**Tell the user:** "ContextStream is updating in the background. Please start a new chat session (or restart your AI tool) in a moment to use the new version."`);
5213
+ parts.push("");
5214
+ } else if (options.autoUpdateResult.error) {
5215
+ parts.push(`
5216
+ ## \u26A0\uFE0F AUTO-UPDATE FAILED`);
5217
+ parts.push(`Automatic update failed: ${options.autoUpdateResult.error}`);
5218
+ const versionInfo = getVersionNoticeForHook(options.versionNotice || null);
5219
+ if (versionInfo) {
5220
+ parts.push(versionInfo);
5221
+ }
5222
+ parts.push("");
5223
+ }
5224
+ } else if (options.versionNotice?.behind && !isAutoUpdateEnabled()) {
5225
+ const versionInfo = getVersionNoticeForHook(options.versionNotice);
5226
+ if (versionInfo) {
5227
+ parts.push(`
5228
+ ## \u{1F504} UPDATE AVAILABLE (auto-update disabled)`);
5229
+ parts.push(versionInfo);
5230
+ parts.push("");
5231
+ }
5232
+ }
4563
5233
  if (!ctx) {
4564
- return `[ContextStream Session Start]
4565
-
4566
- No stored context found. Call \`mcp__contextstream__context(user_message="starting new session")\` to initialize.`;
5234
+ parts.push('\nNo stored context found. Call `mcp__contextstream__context(user_message="starting new session")` to initialize.');
5235
+ return parts.join("\n");
4567
5236
  }
4568
- const parts = ["[ContextStream Session Start]"];
4569
5237
  if (ctx.lessons && ctx.lessons.length > 0) {
4570
5238
  parts.push("\n## \u26A0\uFE0F Lessons from Past Mistakes");
4571
5239
  for (const lesson of ctx.lessons.slice(0, 3)) {
@@ -4613,8 +5281,21 @@ async function runSessionInitHook() {
4613
5281
  }
4614
5282
  const cwd = input.cwd || process.cwd();
4615
5283
  loadConfigFromMcpJson8(cwd);
4616
- const context = await fetchSessionContext2();
4617
- const formattedContext = formatContext(context);
5284
+ const updateMarker = checkUpdateMarker();
5285
+ if (updateMarker) {
5286
+ clearUpdateMarker();
5287
+ }
5288
+ const [context, autoUpdateResult, versionNotice] = await Promise.all([
5289
+ fetchSessionContext2(),
5290
+ updateMarker ? Promise.resolve(null) : attemptAutoUpdate(),
5291
+ // Skip if already updated
5292
+ getUpdateNotice()
5293
+ ]);
5294
+ const formattedContext = formatContext(context, {
5295
+ autoUpdateResult,
5296
+ versionNotice,
5297
+ updateMarker
5298
+ });
4618
5299
  console.log(
4619
5300
  JSON.stringify({
4620
5301
  hookSpecificOutput: {
@@ -4629,6 +5310,7 @@ var ENABLED12, API_URL10, API_KEY10, WORKSPACE_ID8, PROJECT_ID2, isDirectRun12;
4629
5310
  var init_session_init = __esm({
4630
5311
  "src/hooks/session-init.ts"() {
4631
5312
  "use strict";
5313
+ init_version();
4632
5314
  ENABLED12 = process.env.CONTEXTSTREAM_SESSION_INIT_ENABLED !== "false";
4633
5315
  API_URL10 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
4634
5316
  API_KEY10 = process.env.CONTEXTSTREAM_API_KEY || "";
@@ -4669,15 +5351,18 @@ function loadConfigFromMcpJson9(cwd) {
4669
5351
  }
4670
5352
  }
4671
5353
  }
4672
- if (!WORKSPACE_ID9) {
5354
+ if (!WORKSPACE_ID9 || !PROJECT_ID3) {
4673
5355
  const csConfigPath = path20.join(searchDir, ".contextstream", "config.json");
4674
5356
  if (fs19.existsSync(csConfigPath)) {
4675
5357
  try {
4676
5358
  const content = fs19.readFileSync(csConfigPath, "utf-8");
4677
5359
  const csConfig = JSON.parse(content);
4678
- if (csConfig.workspace_id) {
5360
+ if (csConfig.workspace_id && !WORKSPACE_ID9) {
4679
5361
  WORKSPACE_ID9 = csConfig.workspace_id;
4680
5362
  }
5363
+ if (csConfig.project_id && !PROJECT_ID3) {
5364
+ PROJECT_ID3 = csConfig.project_id;
5365
+ }
4681
5366
  } catch {
4682
5367
  }
4683
5368
  }
@@ -4709,7 +5394,9 @@ function parseTranscriptStats(transcriptPath) {
4709
5394
  messageCount: 0,
4710
5395
  toolCallCount: 0,
4711
5396
  duration: 0,
4712
- filesModified: []
5397
+ filesModified: [],
5398
+ messages: [],
5399
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
4713
5400
  };
4714
5401
  if (!transcriptPath || !fs19.existsSync(transcriptPath)) {
4715
5402
  return stats;
@@ -4724,26 +5411,63 @@ function parseTranscriptStats(transcriptPath) {
4724
5411
  if (!line.trim()) continue;
4725
5412
  try {
4726
5413
  const entry = JSON.parse(line);
4727
- if (entry.type === "user" || entry.type === "assistant") {
4728
- stats.messageCount++;
4729
- } else if (entry.type === "tool_use") {
4730
- stats.toolCallCount++;
4731
- if (["Write", "Edit", "NotebookEdit"].includes(entry.name || "")) {
4732
- const filePath = entry.input?.file_path;
4733
- if (filePath) {
4734
- modifiedFiles.add(filePath);
4735
- }
4736
- }
4737
- }
5414
+ const msgType = entry.type || "";
5415
+ const timestamp = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
4738
5416
  if (entry.timestamp) {
4739
5417
  const ts = new Date(entry.timestamp);
4740
5418
  if (!firstTimestamp || ts < firstTimestamp) {
4741
5419
  firstTimestamp = ts;
5420
+ stats.startedAt = entry.timestamp;
4742
5421
  }
4743
5422
  if (!lastTimestamp || ts > lastTimestamp) {
4744
5423
  lastTimestamp = ts;
4745
5424
  }
4746
5425
  }
5426
+ if (msgType === "user" || entry.role === "user") {
5427
+ stats.messageCount++;
5428
+ const userContent = typeof entry.content === "string" ? entry.content : "";
5429
+ if (userContent) {
5430
+ stats.messages.push({
5431
+ role: "user",
5432
+ content: userContent,
5433
+ timestamp
5434
+ });
5435
+ }
5436
+ } else if (msgType === "assistant" || entry.role === "assistant") {
5437
+ stats.messageCount++;
5438
+ const assistantContent = typeof entry.content === "string" ? entry.content : "";
5439
+ if (assistantContent) {
5440
+ stats.messages.push({
5441
+ role: "assistant",
5442
+ content: assistantContent,
5443
+ timestamp
5444
+ });
5445
+ }
5446
+ } else if (msgType === "tool_use") {
5447
+ stats.toolCallCount++;
5448
+ const toolName = entry.name || "";
5449
+ const toolInput = entry.input || {};
5450
+ if (["Write", "Edit", "NotebookEdit"].includes(toolName)) {
5451
+ const filePath = toolInput.file_path || toolInput.notebook_path;
5452
+ if (filePath) {
5453
+ modifiedFiles.add(filePath);
5454
+ }
5455
+ }
5456
+ stats.messages.push({
5457
+ role: "assistant",
5458
+ content: `[Tool: ${toolName}]`,
5459
+ timestamp,
5460
+ tool_calls: { name: toolName, input: toolInput }
5461
+ });
5462
+ } else if (msgType === "tool_result") {
5463
+ const resultContent = typeof entry.content === "string" ? entry.content.slice(0, 2e3) : JSON.stringify(entry.content || {}).slice(0, 2e3);
5464
+ stats.messages.push({
5465
+ role: "tool",
5466
+ content: resultContent,
5467
+ timestamp,
5468
+ tool_results: { name: entry.name }
5469
+ });
5470
+ }
4747
5471
  } catch {
4748
5472
  continue;
4749
5473
  }
@@ -4751,15 +5475,66 @@ function parseTranscriptStats(transcriptPath) {
4751
5475
  if (firstTimestamp && lastTimestamp) {
4752
5476
  stats.duration = Math.round((lastTimestamp.getTime() - firstTimestamp.getTime()) / 1e3);
4753
5477
  }
4754
- stats.filesModified = Array.from(modifiedFiles);
4755
- } catch {
5478
+ stats.filesModified = Array.from(modifiedFiles);
5479
+ } catch {
5480
+ }
5481
+ return stats;
5482
+ }
5483
+ async function saveFullTranscript2(sessionId, stats, reason) {
5484
+ if (!API_KEY11) {
5485
+ return { success: false, message: "No API key configured" };
5486
+ }
5487
+ if (stats.messages.length === 0) {
5488
+ return { success: false, message: "No messages to save" };
5489
+ }
5490
+ const payload = {
5491
+ session_id: sessionId,
5492
+ messages: stats.messages,
5493
+ started_at: stats.startedAt,
5494
+ source_type: "session_end",
5495
+ title: `Session transcript (${reason})`,
5496
+ metadata: {
5497
+ reason,
5498
+ tool_call_count: stats.toolCallCount,
5499
+ files_modified: stats.filesModified.slice(0, 20),
5500
+ duration_seconds: stats.duration
5501
+ },
5502
+ tags: ["session_end", reason]
5503
+ };
5504
+ if (WORKSPACE_ID9) {
5505
+ payload.workspace_id = WORKSPACE_ID9;
5506
+ }
5507
+ if (PROJECT_ID3) {
5508
+ payload.project_id = PROJECT_ID3;
5509
+ }
5510
+ try {
5511
+ const controller = new AbortController();
5512
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
5513
+ const response = await fetch(`${API_URL11}/api/v1/transcripts`, {
5514
+ method: "POST",
5515
+ headers: {
5516
+ "Content-Type": "application/json",
5517
+ "X-API-Key": API_KEY11
5518
+ },
5519
+ body: JSON.stringify(payload),
5520
+ signal: controller.signal
5521
+ });
5522
+ clearTimeout(timeoutId);
5523
+ if (response.ok) {
5524
+ return { success: true, message: `Transcript saved (${stats.messages.length} messages)` };
5525
+ }
5526
+ return { success: false, message: `API error: ${response.status}` };
5527
+ } catch (error) {
5528
+ return { success: false, message: String(error) };
4756
5529
  }
4757
- return stats;
4758
5530
  }
4759
5531
  async function finalizeSession(sessionId, stats, reason) {
4760
5532
  if (!API_KEY11) return;
5533
+ if (SAVE_TRANSCRIPT && stats.messages.length > 0) {
5534
+ await saveFullTranscript2(sessionId, stats, reason);
5535
+ }
4761
5536
  const payload = {
4762
- event_type: "session_end",
5537
+ event_type: "manual_note",
4763
5538
  title: `Session Ended: ${reason}`,
4764
5539
  content: JSON.stringify({
4765
5540
  session_id: sessionId,
@@ -4775,8 +5550,7 @@ async function finalizeSession(sessionId, stats, reason) {
4775
5550
  }),
4776
5551
  importance: "low",
4777
5552
  tags: ["session", "end", reason],
4778
- source_type: "hook",
4779
- session_id: sessionId
5553
+ source_type: "hook"
4780
5554
  };
4781
5555
  if (WORKSPACE_ID9) {
4782
5556
  payload.workspace_id = WORKSPACE_ID9;
@@ -4823,14 +5597,16 @@ async function runSessionEndHook() {
4823
5597
  await finalizeSession(sessionId, stats, reason);
4824
5598
  process.exit(0);
4825
5599
  }
4826
- var ENABLED13, API_URL11, API_KEY11, WORKSPACE_ID9, isDirectRun13;
5600
+ var ENABLED13, SAVE_TRANSCRIPT, API_URL11, API_KEY11, WORKSPACE_ID9, PROJECT_ID3, isDirectRun13;
4827
5601
  var init_session_end = __esm({
4828
5602
  "src/hooks/session-end.ts"() {
4829
5603
  "use strict";
4830
5604
  ENABLED13 = process.env.CONTEXTSTREAM_SESSION_END_ENABLED !== "false";
5605
+ SAVE_TRANSCRIPT = process.env.CONTEXTSTREAM_SESSION_END_SAVE_TRANSCRIPT !== "false";
4831
5606
  API_URL11 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
4832
5607
  API_KEY11 = process.env.CONTEXTSTREAM_API_KEY || "";
4833
5608
  WORKSPACE_ID9 = null;
5609
+ PROJECT_ID3 = null;
4834
5610
  isDirectRun13 = process.argv[1]?.includes("session-end") || process.argv[2] === "session-end";
4835
5611
  if (isDirectRun13) {
4836
5612
  runSessionEndHook().catch(() => process.exit(0));
@@ -4988,10 +5764,11 @@ import * as fs20 from "node:fs";
4988
5764
  import * as path21 from "node:path";
4989
5765
  import { homedir as homedir18 } from "node:os";
4990
5766
  function maskApiKey2(key) {
4991
- if (!key || key.length < 10) return "***";
4992
- const prefix = key.slice(0, 6);
4993
- const suffix = key.slice(-4);
4994
- return `${prefix}...${suffix}`;
5767
+ if (!key || key.length < 8) return "***";
5768
+ const prefixMatch = key.match(/^([a-z]{2,3}_)/i);
5769
+ const prefix = prefixMatch ? prefixMatch[1] : "";
5770
+ const suffix = key.slice(-3);
5771
+ return `${prefix}***...${suffix}`;
4995
5772
  }
4996
5773
  function extractFromMcpConfig(config) {
4997
5774
  if (!config.mcpServers) return {};
@@ -5016,10 +5793,10 @@ function extractFromMcpConfig(config) {
5016
5793
  return {};
5017
5794
  }
5018
5795
  function getClaudeDesktopConfigPath() {
5019
- const platform = process.platform;
5020
- if (platform === "darwin") {
5796
+ const platform2 = process.platform;
5797
+ if (platform2 === "darwin") {
5021
5798
  return path21.join(homedir18(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
5022
- } else if (platform === "win32") {
5799
+ } else if (platform2 === "win32") {
5023
5800
  return path21.join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
5024
5801
  } else {
5025
5802
  return path21.join(homedir18(), ".config", "Claude", "claude_desktop_config.json");
@@ -5174,6 +5951,7 @@ async function validateApiKey(apiKey, apiUrl) {
5174
5951
  return {
5175
5952
  valid: true,
5176
5953
  masked_key: maskApiKey2(apiKey),
5954
+ // lgtm[js/clear-text-logging]
5177
5955
  email: data.email,
5178
5956
  name: data.name || data.full_name,
5179
5957
  plan: data.plan_name || data.plan || "free",
@@ -5183,12 +5961,14 @@ async function validateApiKey(apiKey, apiUrl) {
5183
5961
  return {
5184
5962
  valid: false,
5185
5963
  masked_key: maskApiKey2(apiKey),
5964
+ // lgtm[js/clear-text-logging]
5186
5965
  error: "Invalid or expired API key"
5187
5966
  };
5188
5967
  } else {
5189
5968
  return {
5190
5969
  valid: false,
5191
5970
  masked_key: maskApiKey2(apiKey),
5971
+ // lgtm[js/clear-text-logging]
5192
5972
  error: `API error: ${response.status}`
5193
5973
  };
5194
5974
  }
@@ -8918,561 +9698,390 @@ var ZodEffects = class extends ZodType {
8918
9698
  const result = effect.transform(base.value, checkCtx);
8919
9699
  if (result instanceof Promise) {
8920
9700
  throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`);
8921
- }
8922
- return { status: status.value, value: result };
8923
- } else {
8924
- return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => {
8925
- if (!isValid(base))
8926
- return INVALID;
8927
- return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({
8928
- status: status.value,
8929
- value: result
8930
- }));
8931
- });
8932
- }
8933
- }
8934
- util.assertNever(effect);
8935
- }
8936
- };
8937
- ZodEffects.create = (schema, effect, params) => {
8938
- return new ZodEffects({
8939
- schema,
8940
- typeName: ZodFirstPartyTypeKind.ZodEffects,
8941
- effect,
8942
- ...processCreateParams(params)
8943
- });
8944
- };
8945
- ZodEffects.createWithPreprocess = (preprocess, schema, params) => {
8946
- return new ZodEffects({
8947
- schema,
8948
- effect: { type: "preprocess", transform: preprocess },
8949
- typeName: ZodFirstPartyTypeKind.ZodEffects,
8950
- ...processCreateParams(params)
8951
- });
8952
- };
8953
- var ZodOptional = class extends ZodType {
8954
- _parse(input) {
8955
- const parsedType = this._getType(input);
8956
- if (parsedType === ZodParsedType.undefined) {
8957
- return OK(void 0);
8958
- }
8959
- return this._def.innerType._parse(input);
8960
- }
8961
- unwrap() {
8962
- return this._def.innerType;
8963
- }
8964
- };
8965
- ZodOptional.create = (type, params) => {
8966
- return new ZodOptional({
8967
- innerType: type,
8968
- typeName: ZodFirstPartyTypeKind.ZodOptional,
8969
- ...processCreateParams(params)
8970
- });
8971
- };
8972
- var ZodNullable = class extends ZodType {
8973
- _parse(input) {
8974
- const parsedType = this._getType(input);
8975
- if (parsedType === ZodParsedType.null) {
8976
- return OK(null);
8977
- }
8978
- return this._def.innerType._parse(input);
8979
- }
8980
- unwrap() {
8981
- return this._def.innerType;
8982
- }
8983
- };
8984
- ZodNullable.create = (type, params) => {
8985
- return new ZodNullable({
8986
- innerType: type,
8987
- typeName: ZodFirstPartyTypeKind.ZodNullable,
8988
- ...processCreateParams(params)
8989
- });
8990
- };
8991
- var ZodDefault = class extends ZodType {
8992
- _parse(input) {
8993
- const { ctx } = this._processInputParams(input);
8994
- let data = ctx.data;
8995
- if (ctx.parsedType === ZodParsedType.undefined) {
8996
- data = this._def.defaultValue();
8997
- }
8998
- return this._def.innerType._parse({
8999
- data,
9000
- path: ctx.path,
9001
- parent: ctx
9002
- });
9003
- }
9004
- removeDefault() {
9005
- return this._def.innerType;
9006
- }
9007
- };
9008
- ZodDefault.create = (type, params) => {
9009
- return new ZodDefault({
9010
- innerType: type,
9011
- typeName: ZodFirstPartyTypeKind.ZodDefault,
9012
- defaultValue: typeof params.default === "function" ? params.default : () => params.default,
9013
- ...processCreateParams(params)
9014
- });
9015
- };
9016
- var ZodCatch = class extends ZodType {
9017
- _parse(input) {
9018
- const { ctx } = this._processInputParams(input);
9019
- const newCtx = {
9020
- ...ctx,
9021
- common: {
9022
- ...ctx.common,
9023
- issues: []
9024
- }
9025
- };
9026
- const result = this._def.innerType._parse({
9027
- data: newCtx.data,
9028
- path: newCtx.path,
9029
- parent: {
9030
- ...newCtx
9031
- }
9032
- });
9033
- if (isAsync(result)) {
9034
- return result.then((result2) => {
9035
- return {
9036
- status: "valid",
9037
- value: result2.status === "valid" ? result2.value : this._def.catchValue({
9038
- get error() {
9039
- return new ZodError(newCtx.common.issues);
9040
- },
9041
- input: newCtx.data
9042
- })
9043
- };
9044
- });
9045
- } else {
9046
- return {
9047
- status: "valid",
9048
- value: result.status === "valid" ? result.value : this._def.catchValue({
9049
- get error() {
9050
- return new ZodError(newCtx.common.issues);
9051
- },
9052
- input: newCtx.data
9053
- })
9054
- };
9055
- }
9056
- }
9057
- removeCatch() {
9058
- return this._def.innerType;
9059
- }
9060
- };
9061
- ZodCatch.create = (type, params) => {
9062
- return new ZodCatch({
9063
- innerType: type,
9064
- typeName: ZodFirstPartyTypeKind.ZodCatch,
9065
- catchValue: typeof params.catch === "function" ? params.catch : () => params.catch,
9066
- ...processCreateParams(params)
9067
- });
9068
- };
9069
- var ZodNaN = class extends ZodType {
9070
- _parse(input) {
9071
- const parsedType = this._getType(input);
9072
- if (parsedType !== ZodParsedType.nan) {
9073
- const ctx = this._getOrReturnCtx(input);
9074
- addIssueToContext(ctx, {
9075
- code: ZodIssueCode.invalid_type,
9076
- expected: ZodParsedType.nan,
9077
- received: ctx.parsedType
9078
- });
9079
- return INVALID;
9080
- }
9081
- return { status: "valid", value: input.data };
9082
- }
9083
- };
9084
- ZodNaN.create = (params) => {
9085
- return new ZodNaN({
9086
- typeName: ZodFirstPartyTypeKind.ZodNaN,
9087
- ...processCreateParams(params)
9088
- });
9089
- };
9090
- var BRAND = /* @__PURE__ */ Symbol("zod_brand");
9091
- var ZodBranded = class extends ZodType {
9092
- _parse(input) {
9093
- const { ctx } = this._processInputParams(input);
9094
- const data = ctx.data;
9095
- return this._def.type._parse({
9096
- data,
9097
- path: ctx.path,
9098
- parent: ctx
9099
- });
9100
- }
9101
- unwrap() {
9102
- return this._def.type;
9103
- }
9104
- };
9105
- var ZodPipeline = class _ZodPipeline extends ZodType {
9106
- _parse(input) {
9107
- const { status, ctx } = this._processInputParams(input);
9108
- if (ctx.common.async) {
9109
- const handleAsync = async () => {
9110
- const inResult = await this._def.in._parseAsync({
9111
- data: ctx.data,
9112
- path: ctx.path,
9113
- parent: ctx
9114
- });
9115
- if (inResult.status === "aborted")
9116
- return INVALID;
9117
- if (inResult.status === "dirty") {
9118
- status.dirty();
9119
- return DIRTY(inResult.value);
9120
- } else {
9121
- return this._def.out._parseAsync({
9122
- data: inResult.value,
9123
- path: ctx.path,
9124
- parent: ctx
9125
- });
9126
- }
9127
- };
9128
- return handleAsync();
9129
- } else {
9130
- const inResult = this._def.in._parseSync({
9131
- data: ctx.data,
9132
- path: ctx.path,
9133
- parent: ctx
9134
- });
9135
- if (inResult.status === "aborted")
9136
- return INVALID;
9137
- if (inResult.status === "dirty") {
9138
- status.dirty();
9139
- return {
9140
- status: "dirty",
9141
- value: inResult.value
9142
- };
9701
+ }
9702
+ return { status: status.value, value: result };
9143
9703
  } else {
9144
- return this._def.out._parseSync({
9145
- data: inResult.value,
9146
- path: ctx.path,
9147
- parent: ctx
9704
+ return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => {
9705
+ if (!isValid(base))
9706
+ return INVALID;
9707
+ return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({
9708
+ status: status.value,
9709
+ value: result
9710
+ }));
9148
9711
  });
9149
9712
  }
9150
9713
  }
9151
- }
9152
- static create(a, b) {
9153
- return new _ZodPipeline({
9154
- in: a,
9155
- out: b,
9156
- typeName: ZodFirstPartyTypeKind.ZodPipeline
9157
- });
9714
+ util.assertNever(effect);
9158
9715
  }
9159
9716
  };
9160
- var ZodReadonly = class extends ZodType {
9717
+ ZodEffects.create = (schema, effect, params) => {
9718
+ return new ZodEffects({
9719
+ schema,
9720
+ typeName: ZodFirstPartyTypeKind.ZodEffects,
9721
+ effect,
9722
+ ...processCreateParams(params)
9723
+ });
9724
+ };
9725
+ ZodEffects.createWithPreprocess = (preprocess, schema, params) => {
9726
+ return new ZodEffects({
9727
+ schema,
9728
+ effect: { type: "preprocess", transform: preprocess },
9729
+ typeName: ZodFirstPartyTypeKind.ZodEffects,
9730
+ ...processCreateParams(params)
9731
+ });
9732
+ };
9733
+ var ZodOptional = class extends ZodType {
9161
9734
  _parse(input) {
9162
- const result = this._def.innerType._parse(input);
9163
- const freeze = (data) => {
9164
- if (isValid(data)) {
9165
- data.value = Object.freeze(data.value);
9166
- }
9167
- return data;
9168
- };
9169
- return isAsync(result) ? result.then((data) => freeze(data)) : freeze(result);
9735
+ const parsedType = this._getType(input);
9736
+ if (parsedType === ZodParsedType.undefined) {
9737
+ return OK(void 0);
9738
+ }
9739
+ return this._def.innerType._parse(input);
9170
9740
  }
9171
9741
  unwrap() {
9172
9742
  return this._def.innerType;
9173
9743
  }
9174
9744
  };
9175
- ZodReadonly.create = (type, params) => {
9176
- return new ZodReadonly({
9745
+ ZodOptional.create = (type, params) => {
9746
+ return new ZodOptional({
9177
9747
  innerType: type,
9178
- typeName: ZodFirstPartyTypeKind.ZodReadonly,
9748
+ typeName: ZodFirstPartyTypeKind.ZodOptional,
9179
9749
  ...processCreateParams(params)
9180
9750
  });
9181
9751
  };
9182
- function cleanParams(params, data) {
9183
- const p = typeof params === "function" ? params(data) : typeof params === "string" ? { message: params } : params;
9184
- const p2 = typeof p === "string" ? { message: p } : p;
9185
- return p2;
9186
- }
9187
- function custom(check, _params = {}, fatal) {
9188
- if (check)
9189
- return ZodAny.create().superRefine((data, ctx) => {
9190
- const r = check(data);
9191
- if (r instanceof Promise) {
9192
- return r.then((r2) => {
9193
- if (!r2) {
9194
- const params = cleanParams(_params, data);
9195
- const _fatal = params.fatal ?? fatal ?? true;
9196
- ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
9197
- }
9198
- });
9199
- }
9200
- if (!r) {
9201
- const params = cleanParams(_params, data);
9202
- const _fatal = params.fatal ?? fatal ?? true;
9203
- ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
9204
- }
9205
- return;
9206
- });
9207
- return ZodAny.create();
9208
- }
9209
- var late = {
9210
- object: ZodObject.lazycreate
9211
- };
9212
- var ZodFirstPartyTypeKind;
9213
- (function(ZodFirstPartyTypeKind2) {
9214
- ZodFirstPartyTypeKind2["ZodString"] = "ZodString";
9215
- ZodFirstPartyTypeKind2["ZodNumber"] = "ZodNumber";
9216
- ZodFirstPartyTypeKind2["ZodNaN"] = "ZodNaN";
9217
- ZodFirstPartyTypeKind2["ZodBigInt"] = "ZodBigInt";
9218
- ZodFirstPartyTypeKind2["ZodBoolean"] = "ZodBoolean";
9219
- ZodFirstPartyTypeKind2["ZodDate"] = "ZodDate";
9220
- ZodFirstPartyTypeKind2["ZodSymbol"] = "ZodSymbol";
9221
- ZodFirstPartyTypeKind2["ZodUndefined"] = "ZodUndefined";
9222
- ZodFirstPartyTypeKind2["ZodNull"] = "ZodNull";
9223
- ZodFirstPartyTypeKind2["ZodAny"] = "ZodAny";
9224
- ZodFirstPartyTypeKind2["ZodUnknown"] = "ZodUnknown";
9225
- ZodFirstPartyTypeKind2["ZodNever"] = "ZodNever";
9226
- ZodFirstPartyTypeKind2["ZodVoid"] = "ZodVoid";
9227
- ZodFirstPartyTypeKind2["ZodArray"] = "ZodArray";
9228
- ZodFirstPartyTypeKind2["ZodObject"] = "ZodObject";
9229
- ZodFirstPartyTypeKind2["ZodUnion"] = "ZodUnion";
9230
- ZodFirstPartyTypeKind2["ZodDiscriminatedUnion"] = "ZodDiscriminatedUnion";
9231
- ZodFirstPartyTypeKind2["ZodIntersection"] = "ZodIntersection";
9232
- ZodFirstPartyTypeKind2["ZodTuple"] = "ZodTuple";
9233
- ZodFirstPartyTypeKind2["ZodRecord"] = "ZodRecord";
9234
- ZodFirstPartyTypeKind2["ZodMap"] = "ZodMap";
9235
- ZodFirstPartyTypeKind2["ZodSet"] = "ZodSet";
9236
- ZodFirstPartyTypeKind2["ZodFunction"] = "ZodFunction";
9237
- ZodFirstPartyTypeKind2["ZodLazy"] = "ZodLazy";
9238
- ZodFirstPartyTypeKind2["ZodLiteral"] = "ZodLiteral";
9239
- ZodFirstPartyTypeKind2["ZodEnum"] = "ZodEnum";
9240
- ZodFirstPartyTypeKind2["ZodEffects"] = "ZodEffects";
9241
- ZodFirstPartyTypeKind2["ZodNativeEnum"] = "ZodNativeEnum";
9242
- ZodFirstPartyTypeKind2["ZodOptional"] = "ZodOptional";
9243
- ZodFirstPartyTypeKind2["ZodNullable"] = "ZodNullable";
9244
- ZodFirstPartyTypeKind2["ZodDefault"] = "ZodDefault";
9245
- ZodFirstPartyTypeKind2["ZodCatch"] = "ZodCatch";
9246
- ZodFirstPartyTypeKind2["ZodPromise"] = "ZodPromise";
9247
- ZodFirstPartyTypeKind2["ZodBranded"] = "ZodBranded";
9248
- ZodFirstPartyTypeKind2["ZodPipeline"] = "ZodPipeline";
9249
- ZodFirstPartyTypeKind2["ZodReadonly"] = "ZodReadonly";
9250
- })(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
9251
- var instanceOfType = (cls, params = {
9252
- message: `Input not instance of ${cls.name}`
9253
- }) => custom((data) => data instanceof cls, params);
9254
- var stringType = ZodString.create;
9255
- var numberType = ZodNumber.create;
9256
- var nanType = ZodNaN.create;
9257
- var bigIntType = ZodBigInt.create;
9258
- var booleanType = ZodBoolean.create;
9259
- var dateType = ZodDate.create;
9260
- var symbolType = ZodSymbol.create;
9261
- var undefinedType = ZodUndefined.create;
9262
- var nullType = ZodNull.create;
9263
- var anyType = ZodAny.create;
9264
- var unknownType = ZodUnknown.create;
9265
- var neverType = ZodNever.create;
9266
- var voidType = ZodVoid.create;
9267
- var arrayType = ZodArray.create;
9268
- var objectType = ZodObject.create;
9269
- var strictObjectType = ZodObject.strictCreate;
9270
- var unionType = ZodUnion.create;
9271
- var discriminatedUnionType = ZodDiscriminatedUnion.create;
9272
- var intersectionType = ZodIntersection.create;
9273
- var tupleType = ZodTuple.create;
9274
- var recordType = ZodRecord.create;
9275
- var mapType = ZodMap.create;
9276
- var setType = ZodSet.create;
9277
- var functionType = ZodFunction.create;
9278
- var lazyType = ZodLazy.create;
9279
- var literalType = ZodLiteral.create;
9280
- var enumType = ZodEnum.create;
9281
- var nativeEnumType = ZodNativeEnum.create;
9282
- var promiseType = ZodPromise.create;
9283
- var effectsType = ZodEffects.create;
9284
- var optionalType = ZodOptional.create;
9285
- var nullableType = ZodNullable.create;
9286
- var preprocessType = ZodEffects.createWithPreprocess;
9287
- var pipelineType = ZodPipeline.create;
9288
- var ostring = () => stringType().optional();
9289
- var onumber = () => numberType().optional();
9290
- var oboolean = () => booleanType().optional();
9291
- var coerce = {
9292
- string: ((arg) => ZodString.create({ ...arg, coerce: true })),
9293
- number: ((arg) => ZodNumber.create({ ...arg, coerce: true })),
9294
- boolean: ((arg) => ZodBoolean.create({
9295
- ...arg,
9296
- coerce: true
9297
- })),
9298
- bigint: ((arg) => ZodBigInt.create({ ...arg, coerce: true })),
9299
- date: ((arg) => ZodDate.create({ ...arg, coerce: true }))
9300
- };
9301
- var NEVER = INVALID;
9302
-
9303
- // src/version.ts
9304
- import { createRequire } from "module";
9305
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
9306
- import { homedir } from "os";
9307
- import { join } from "path";
9308
- var UPGRADE_COMMAND = "npm install -g @contextstream/mcp-server@latest";
9309
- var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
9310
- function getVersion() {
9311
- if (typeof __CONTEXTSTREAM_VERSION__ !== "undefined" && __CONTEXTSTREAM_VERSION__) {
9312
- return __CONTEXTSTREAM_VERSION__;
9313
- }
9314
- try {
9315
- const require2 = createRequire(import.meta.url);
9316
- const pkg = require2("../package.json");
9317
- const version = pkg?.version;
9318
- if (typeof version === "string" && version.trim()) return version.trim();
9319
- } catch {
9320
- }
9321
- return "unknown";
9322
- }
9323
- var VERSION = getVersion();
9324
- function compareVersions(v1, v2) {
9325
- const parts1 = v1.split(".").map(Number);
9326
- const parts2 = v2.split(".").map(Number);
9327
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
9328
- const p1 = parts1[i] ?? 0;
9329
- const p2 = parts2[i] ?? 0;
9330
- if (p1 < p2) return -1;
9331
- if (p1 > p2) return 1;
9332
- }
9333
- return 0;
9334
- }
9335
- var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
9336
- var latestVersionPromise = null;
9337
- function getCacheFilePath() {
9338
- return join(homedir(), ".contextstream", "version-cache.json");
9339
- }
9340
- function readCache() {
9341
- try {
9342
- const cacheFile = getCacheFilePath();
9343
- if (!existsSync(cacheFile)) return null;
9344
- const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
9345
- if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
9346
- return data;
9347
- } catch {
9348
- return null;
9349
- }
9350
- }
9351
- function writeCache(latestVersion) {
9352
- try {
9353
- const configDir = join(homedir(), ".contextstream");
9354
- if (!existsSync(configDir)) {
9355
- mkdirSync(configDir, { recursive: true });
9752
+ var ZodNullable = class extends ZodType {
9753
+ _parse(input) {
9754
+ const parsedType = this._getType(input);
9755
+ if (parsedType === ZodParsedType.null) {
9756
+ return OK(null);
9356
9757
  }
9357
- const cacheFile = getCacheFilePath();
9358
- writeFileSync(
9359
- cacheFile,
9360
- JSON.stringify({
9361
- latestVersion,
9362
- checkedAt: Date.now()
9363
- })
9364
- );
9365
- } catch {
9758
+ return this._def.innerType._parse(input);
9366
9759
  }
9367
- }
9368
- async function fetchLatestVersion() {
9369
- try {
9370
- const controller = new AbortController();
9371
- const timeout = setTimeout(() => controller.abort(), 5e3);
9372
- const response = await fetch(NPM_LATEST_URL, {
9373
- signal: controller.signal,
9374
- headers: { Accept: "application/json" }
9375
- });
9376
- clearTimeout(timeout);
9377
- if (!response.ok) return null;
9378
- const data = await response.json();
9379
- return typeof data.version === "string" ? data.version : null;
9380
- } catch {
9381
- return null;
9760
+ unwrap() {
9761
+ return this._def.innerType;
9382
9762
  }
9383
- }
9384
- async function resolveLatestVersion() {
9385
- const cached = readCache();
9386
- if (cached) return cached.latestVersion;
9387
- if (!latestVersionPromise) {
9388
- latestVersionPromise = fetchLatestVersion().finally(() => {
9389
- latestVersionPromise = null;
9763
+ };
9764
+ ZodNullable.create = (type, params) => {
9765
+ return new ZodNullable({
9766
+ innerType: type,
9767
+ typeName: ZodFirstPartyTypeKind.ZodNullable,
9768
+ ...processCreateParams(params)
9769
+ });
9770
+ };
9771
+ var ZodDefault = class extends ZodType {
9772
+ _parse(input) {
9773
+ const { ctx } = this._processInputParams(input);
9774
+ let data = ctx.data;
9775
+ if (ctx.parsedType === ZodParsedType.undefined) {
9776
+ data = this._def.defaultValue();
9777
+ }
9778
+ return this._def.innerType._parse({
9779
+ data,
9780
+ path: ctx.path,
9781
+ parent: ctx
9390
9782
  });
9391
9783
  }
9392
- const latestVersion = await latestVersionPromise;
9393
- if (latestVersion) {
9394
- writeCache(latestVersion);
9395
- }
9396
- return latestVersion;
9397
- }
9398
- async function checkForUpdates() {
9399
- const notice = await getUpdateNotice();
9400
- if (notice?.behind) {
9401
- showUpdateWarning(notice.current, notice.latest);
9784
+ removeDefault() {
9785
+ return this._def.innerType;
9402
9786
  }
9403
- }
9404
- function showUpdateWarning(currentVersion, latestVersion) {
9405
- console.error("");
9406
- console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
9407
- console.error(`\u26A0\uFE0F Update available: v${currentVersion} \u2192 v${latestVersion}`);
9408
- console.error("");
9409
- console.error(` Run: ${UPGRADE_COMMAND}`);
9410
- console.error("");
9411
- console.error(" Then restart your AI tool to use the new version.");
9412
- console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
9413
- console.error("");
9414
- }
9415
- async function getUpdateNotice() {
9416
- const currentVersion = VERSION;
9417
- if (currentVersion === "unknown") return null;
9418
- try {
9419
- const latestVersion = await resolveLatestVersion();
9420
- if (!latestVersion) return null;
9421
- if (compareVersions(currentVersion, latestVersion) < 0) {
9787
+ };
9788
+ ZodDefault.create = (type, params) => {
9789
+ return new ZodDefault({
9790
+ innerType: type,
9791
+ typeName: ZodFirstPartyTypeKind.ZodDefault,
9792
+ defaultValue: typeof params.default === "function" ? params.default : () => params.default,
9793
+ ...processCreateParams(params)
9794
+ });
9795
+ };
9796
+ var ZodCatch = class extends ZodType {
9797
+ _parse(input) {
9798
+ const { ctx } = this._processInputParams(input);
9799
+ const newCtx = {
9800
+ ...ctx,
9801
+ common: {
9802
+ ...ctx.common,
9803
+ issues: []
9804
+ }
9805
+ };
9806
+ const result = this._def.innerType._parse({
9807
+ data: newCtx.data,
9808
+ path: newCtx.path,
9809
+ parent: {
9810
+ ...newCtx
9811
+ }
9812
+ });
9813
+ if (isAsync(result)) {
9814
+ return result.then((result2) => {
9815
+ return {
9816
+ status: "valid",
9817
+ value: result2.status === "valid" ? result2.value : this._def.catchValue({
9818
+ get error() {
9819
+ return new ZodError(newCtx.common.issues);
9820
+ },
9821
+ input: newCtx.data
9822
+ })
9823
+ };
9824
+ });
9825
+ } else {
9422
9826
  return {
9423
- current: currentVersion,
9424
- latest: latestVersion,
9425
- behind: true,
9426
- upgrade_command: UPGRADE_COMMAND
9827
+ status: "valid",
9828
+ value: result.status === "valid" ? result.value : this._def.catchValue({
9829
+ get error() {
9830
+ return new ZodError(newCtx.common.issues);
9831
+ },
9832
+ input: newCtx.data
9833
+ })
9427
9834
  };
9428
9835
  }
9429
- } catch {
9430
9836
  }
9431
- return null;
9432
- }
9433
- function getVersionsBehind(current, latest) {
9434
- try {
9435
- const currentParts = current.split(".").map(Number);
9436
- const latestParts = latest.split(".").map(Number);
9437
- if ((latestParts[0] ?? 0) > (currentParts[0] ?? 0)) {
9438
- return 10 + ((latestParts[1] ?? 0) - (currentParts[1] ?? 0));
9837
+ removeCatch() {
9838
+ return this._def.innerType;
9839
+ }
9840
+ };
9841
+ ZodCatch.create = (type, params) => {
9842
+ return new ZodCatch({
9843
+ innerType: type,
9844
+ typeName: ZodFirstPartyTypeKind.ZodCatch,
9845
+ catchValue: typeof params.catch === "function" ? params.catch : () => params.catch,
9846
+ ...processCreateParams(params)
9847
+ });
9848
+ };
9849
+ var ZodNaN = class extends ZodType {
9850
+ _parse(input) {
9851
+ const parsedType = this._getType(input);
9852
+ if (parsedType !== ZodParsedType.nan) {
9853
+ const ctx = this._getOrReturnCtx(input);
9854
+ addIssueToContext(ctx, {
9855
+ code: ZodIssueCode.invalid_type,
9856
+ expected: ZodParsedType.nan,
9857
+ received: ctx.parsedType
9858
+ });
9859
+ return INVALID;
9439
9860
  }
9440
- const minorDiff = (latestParts[1] ?? 0) - (currentParts[1] ?? 0);
9441
- const patchDiff = (latestParts[2] ?? 0) - (currentParts[2] ?? 0);
9442
- if (minorDiff > 0) {
9443
- return minorDiff;
9444
- } else if (minorDiff === 0 && patchDiff > 0) {
9445
- return 1;
9861
+ return { status: "valid", value: input.data };
9862
+ }
9863
+ };
9864
+ ZodNaN.create = (params) => {
9865
+ return new ZodNaN({
9866
+ typeName: ZodFirstPartyTypeKind.ZodNaN,
9867
+ ...processCreateParams(params)
9868
+ });
9869
+ };
9870
+ var BRAND = /* @__PURE__ */ Symbol("zod_brand");
9871
+ var ZodBranded = class extends ZodType {
9872
+ _parse(input) {
9873
+ const { ctx } = this._processInputParams(input);
9874
+ const data = ctx.data;
9875
+ return this._def.type._parse({
9876
+ data,
9877
+ path: ctx.path,
9878
+ parent: ctx
9879
+ });
9880
+ }
9881
+ unwrap() {
9882
+ return this._def.type;
9883
+ }
9884
+ };
9885
+ var ZodPipeline = class _ZodPipeline extends ZodType {
9886
+ _parse(input) {
9887
+ const { status, ctx } = this._processInputParams(input);
9888
+ if (ctx.common.async) {
9889
+ const handleAsync = async () => {
9890
+ const inResult = await this._def.in._parseAsync({
9891
+ data: ctx.data,
9892
+ path: ctx.path,
9893
+ parent: ctx
9894
+ });
9895
+ if (inResult.status === "aborted")
9896
+ return INVALID;
9897
+ if (inResult.status === "dirty") {
9898
+ status.dirty();
9899
+ return DIRTY(inResult.value);
9900
+ } else {
9901
+ return this._def.out._parseAsync({
9902
+ data: inResult.value,
9903
+ path: ctx.path,
9904
+ parent: ctx
9905
+ });
9906
+ }
9907
+ };
9908
+ return handleAsync();
9909
+ } else {
9910
+ const inResult = this._def.in._parseSync({
9911
+ data: ctx.data,
9912
+ path: ctx.path,
9913
+ parent: ctx
9914
+ });
9915
+ if (inResult.status === "aborted")
9916
+ return INVALID;
9917
+ if (inResult.status === "dirty") {
9918
+ status.dirty();
9919
+ return {
9920
+ status: "dirty",
9921
+ value: inResult.value
9922
+ };
9923
+ } else {
9924
+ return this._def.out._parseSync({
9925
+ data: inResult.value,
9926
+ path: ctx.path,
9927
+ parent: ctx
9928
+ });
9929
+ }
9446
9930
  }
9447
- return 0;
9448
- } catch {
9449
- return 0;
9450
9931
  }
9451
- }
9452
- function getVersionWarning(notice) {
9453
- if (!notice?.behind) return null;
9454
- const versionsBehind = getVersionsBehind(notice.current, notice.latest);
9455
- if (versionsBehind >= 3) {
9456
- return `[VERSION_CRITICAL] MCP server is ${versionsBehind} versions behind (${notice.current} \u2192 ${notice.latest}). Update immediately: ${notice.upgrade_command}`;
9457
- } else if (versionsBehind >= 1) {
9458
- return `[VERSION_NOTICE] MCP server update available (${notice.current} \u2192 ${notice.latest}). Run: ${notice.upgrade_command}`;
9932
+ static create(a, b) {
9933
+ return new _ZodPipeline({
9934
+ in: a,
9935
+ out: b,
9936
+ typeName: ZodFirstPartyTypeKind.ZodPipeline
9937
+ });
9459
9938
  }
9460
- return null;
9461
- }
9462
- function getVersionInstructions(notice) {
9463
- if (!notice?.behind) return null;
9464
- const versionsBehind = getVersionsBehind(notice.current, notice.latest);
9465
- if (versionsBehind >= 5) {
9466
- return `\u26A0\uFE0F CRITICAL: User's MCP is ${versionsBehind} versions behind. Mention update in EVERY response until updated.`;
9467
- } else if (versionsBehind >= 3) {
9468
- return `\u26A0\uFE0F User's MCP is ${versionsBehind} versions behind. Remind about update every 2-3 messages.`;
9469
- } else if (versionsBehind >= 1) {
9470
- return `Note: MCP update available (${notice.current}\u2192${notice.latest}). Mention once at session start.`;
9939
+ };
9940
+ var ZodReadonly = class extends ZodType {
9941
+ _parse(input) {
9942
+ const result = this._def.innerType._parse(input);
9943
+ const freeze = (data) => {
9944
+ if (isValid(data)) {
9945
+ data.value = Object.freeze(data.value);
9946
+ }
9947
+ return data;
9948
+ };
9949
+ return isAsync(result) ? result.then((data) => freeze(data)) : freeze(result);
9950
+ }
9951
+ unwrap() {
9952
+ return this._def.innerType;
9471
9953
  }
9472
- return null;
9954
+ };
9955
+ ZodReadonly.create = (type, params) => {
9956
+ return new ZodReadonly({
9957
+ innerType: type,
9958
+ typeName: ZodFirstPartyTypeKind.ZodReadonly,
9959
+ ...processCreateParams(params)
9960
+ });
9961
+ };
9962
+ function cleanParams(params, data) {
9963
+ const p = typeof params === "function" ? params(data) : typeof params === "string" ? { message: params } : params;
9964
+ const p2 = typeof p === "string" ? { message: p } : p;
9965
+ return p2;
9966
+ }
9967
+ function custom(check, _params = {}, fatal) {
9968
+ if (check)
9969
+ return ZodAny.create().superRefine((data, ctx) => {
9970
+ const r = check(data);
9971
+ if (r instanceof Promise) {
9972
+ return r.then((r2) => {
9973
+ if (!r2) {
9974
+ const params = cleanParams(_params, data);
9975
+ const _fatal = params.fatal ?? fatal ?? true;
9976
+ ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
9977
+ }
9978
+ });
9979
+ }
9980
+ if (!r) {
9981
+ const params = cleanParams(_params, data);
9982
+ const _fatal = params.fatal ?? fatal ?? true;
9983
+ ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
9984
+ }
9985
+ return;
9986
+ });
9987
+ return ZodAny.create();
9473
9988
  }
9989
+ var late = {
9990
+ object: ZodObject.lazycreate
9991
+ };
9992
+ var ZodFirstPartyTypeKind;
9993
+ (function(ZodFirstPartyTypeKind2) {
9994
+ ZodFirstPartyTypeKind2["ZodString"] = "ZodString";
9995
+ ZodFirstPartyTypeKind2["ZodNumber"] = "ZodNumber";
9996
+ ZodFirstPartyTypeKind2["ZodNaN"] = "ZodNaN";
9997
+ ZodFirstPartyTypeKind2["ZodBigInt"] = "ZodBigInt";
9998
+ ZodFirstPartyTypeKind2["ZodBoolean"] = "ZodBoolean";
9999
+ ZodFirstPartyTypeKind2["ZodDate"] = "ZodDate";
10000
+ ZodFirstPartyTypeKind2["ZodSymbol"] = "ZodSymbol";
10001
+ ZodFirstPartyTypeKind2["ZodUndefined"] = "ZodUndefined";
10002
+ ZodFirstPartyTypeKind2["ZodNull"] = "ZodNull";
10003
+ ZodFirstPartyTypeKind2["ZodAny"] = "ZodAny";
10004
+ ZodFirstPartyTypeKind2["ZodUnknown"] = "ZodUnknown";
10005
+ ZodFirstPartyTypeKind2["ZodNever"] = "ZodNever";
10006
+ ZodFirstPartyTypeKind2["ZodVoid"] = "ZodVoid";
10007
+ ZodFirstPartyTypeKind2["ZodArray"] = "ZodArray";
10008
+ ZodFirstPartyTypeKind2["ZodObject"] = "ZodObject";
10009
+ ZodFirstPartyTypeKind2["ZodUnion"] = "ZodUnion";
10010
+ ZodFirstPartyTypeKind2["ZodDiscriminatedUnion"] = "ZodDiscriminatedUnion";
10011
+ ZodFirstPartyTypeKind2["ZodIntersection"] = "ZodIntersection";
10012
+ ZodFirstPartyTypeKind2["ZodTuple"] = "ZodTuple";
10013
+ ZodFirstPartyTypeKind2["ZodRecord"] = "ZodRecord";
10014
+ ZodFirstPartyTypeKind2["ZodMap"] = "ZodMap";
10015
+ ZodFirstPartyTypeKind2["ZodSet"] = "ZodSet";
10016
+ ZodFirstPartyTypeKind2["ZodFunction"] = "ZodFunction";
10017
+ ZodFirstPartyTypeKind2["ZodLazy"] = "ZodLazy";
10018
+ ZodFirstPartyTypeKind2["ZodLiteral"] = "ZodLiteral";
10019
+ ZodFirstPartyTypeKind2["ZodEnum"] = "ZodEnum";
10020
+ ZodFirstPartyTypeKind2["ZodEffects"] = "ZodEffects";
10021
+ ZodFirstPartyTypeKind2["ZodNativeEnum"] = "ZodNativeEnum";
10022
+ ZodFirstPartyTypeKind2["ZodOptional"] = "ZodOptional";
10023
+ ZodFirstPartyTypeKind2["ZodNullable"] = "ZodNullable";
10024
+ ZodFirstPartyTypeKind2["ZodDefault"] = "ZodDefault";
10025
+ ZodFirstPartyTypeKind2["ZodCatch"] = "ZodCatch";
10026
+ ZodFirstPartyTypeKind2["ZodPromise"] = "ZodPromise";
10027
+ ZodFirstPartyTypeKind2["ZodBranded"] = "ZodBranded";
10028
+ ZodFirstPartyTypeKind2["ZodPipeline"] = "ZodPipeline";
10029
+ ZodFirstPartyTypeKind2["ZodReadonly"] = "ZodReadonly";
10030
+ })(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
10031
+ var instanceOfType = (cls, params = {
10032
+ message: `Input not instance of ${cls.name}`
10033
+ }) => custom((data) => data instanceof cls, params);
10034
+ var stringType = ZodString.create;
10035
+ var numberType = ZodNumber.create;
10036
+ var nanType = ZodNaN.create;
10037
+ var bigIntType = ZodBigInt.create;
10038
+ var booleanType = ZodBoolean.create;
10039
+ var dateType = ZodDate.create;
10040
+ var symbolType = ZodSymbol.create;
10041
+ var undefinedType = ZodUndefined.create;
10042
+ var nullType = ZodNull.create;
10043
+ var anyType = ZodAny.create;
10044
+ var unknownType = ZodUnknown.create;
10045
+ var neverType = ZodNever.create;
10046
+ var voidType = ZodVoid.create;
10047
+ var arrayType = ZodArray.create;
10048
+ var objectType = ZodObject.create;
10049
+ var strictObjectType = ZodObject.strictCreate;
10050
+ var unionType = ZodUnion.create;
10051
+ var discriminatedUnionType = ZodDiscriminatedUnion.create;
10052
+ var intersectionType = ZodIntersection.create;
10053
+ var tupleType = ZodTuple.create;
10054
+ var recordType = ZodRecord.create;
10055
+ var mapType = ZodMap.create;
10056
+ var setType = ZodSet.create;
10057
+ var functionType = ZodFunction.create;
10058
+ var lazyType = ZodLazy.create;
10059
+ var literalType = ZodLiteral.create;
10060
+ var enumType = ZodEnum.create;
10061
+ var nativeEnumType = ZodNativeEnum.create;
10062
+ var promiseType = ZodPromise.create;
10063
+ var effectsType = ZodEffects.create;
10064
+ var optionalType = ZodOptional.create;
10065
+ var nullableType = ZodNullable.create;
10066
+ var preprocessType = ZodEffects.createWithPreprocess;
10067
+ var pipelineType = ZodPipeline.create;
10068
+ var ostring = () => stringType().optional();
10069
+ var onumber = () => numberType().optional();
10070
+ var oboolean = () => booleanType().optional();
10071
+ var coerce = {
10072
+ string: ((arg) => ZodString.create({ ...arg, coerce: true })),
10073
+ number: ((arg) => ZodNumber.create({ ...arg, coerce: true })),
10074
+ boolean: ((arg) => ZodBoolean.create({
10075
+ ...arg,
10076
+ coerce: true
10077
+ })),
10078
+ bigint: ((arg) => ZodBigInt.create({ ...arg, coerce: true })),
10079
+ date: ((arg) => ZodDate.create({ ...arg, coerce: true }))
10080
+ };
10081
+ var NEVER = INVALID;
9474
10082
 
9475
10083
  // src/config.ts
10084
+ init_version();
9476
10085
  var DEFAULT_API_URL = "https://api.contextstream.io";
9477
10086
  function parseBooleanEnv(value) {
9478
10087
  if (value === void 0) return void 0;
@@ -9764,6 +10373,10 @@ function rewriteNotFoundMessage(input) {
9764
10373
  // src/files.ts
9765
10374
  import * as fs2 from "fs";
9766
10375
  import * as path2 from "path";
10376
+ import { exec } from "child_process";
10377
+ import { promisify } from "util";
10378
+ import * as os from "os";
10379
+ import * as crypto from "crypto";
9767
10380
 
9768
10381
  // src/ignore.ts
9769
10382
  var import_ignore = __toESM(require_ignore(), 1);
@@ -9837,6 +10450,7 @@ async function loadIgnorePatterns(projectRoot) {
9837
10450
  }
9838
10451
 
9839
10452
  // src/files.ts
10453
+ var execAsync = promisify(exec);
9840
10454
  var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
9841
10455
  // Rust
9842
10456
  "rs",
@@ -9944,12 +10558,116 @@ var MAX_FILE_SIZE = 1024 * 1024;
9944
10558
  var MAX_BATCH_BYTES = 10 * 1024 * 1024;
9945
10559
  var LARGE_FILE_THRESHOLD = 2 * 1024 * 1024;
9946
10560
  var MAX_FILES_PER_BATCH = 200;
10561
+ var gitContextCache = /* @__PURE__ */ new Map();
10562
+ function getMachineId() {
10563
+ const hostname2 = os.hostname();
10564
+ const hash = crypto.createHash("sha256").update(hostname2).digest("hex");
10565
+ return hash.substring(0, 12);
10566
+ }
10567
+ async function isGitRepo(rootPath) {
10568
+ try {
10569
+ await execAsync("git rev-parse --is-inside-work-tree", {
10570
+ cwd: rootPath,
10571
+ timeout: 5e3
10572
+ });
10573
+ return true;
10574
+ } catch {
10575
+ return false;
10576
+ }
10577
+ }
10578
+ async function getGitBranch(rootPath) {
10579
+ try {
10580
+ const { stdout: stdout2 } = await execAsync("git branch --show-current", {
10581
+ cwd: rootPath,
10582
+ timeout: 5e3
10583
+ });
10584
+ const branch = stdout2.trim();
10585
+ return branch || void 0;
10586
+ } catch {
10587
+ return void 0;
10588
+ }
10589
+ }
10590
+ async function getGitDefaultBranch(rootPath) {
10591
+ try {
10592
+ const { stdout: stdout2 } = await execAsync(
10593
+ "git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null",
10594
+ { cwd: rootPath, timeout: 5e3 }
10595
+ );
10596
+ const match = stdout2.trim().match(/refs\/remotes\/origin\/(.+)/);
10597
+ if (match) return match[1];
10598
+ } catch {
10599
+ }
10600
+ try {
10601
+ const { stdout: stdout2 } = await execAsync(
10602
+ "git config --get init.defaultBranch 2>/dev/null",
10603
+ { cwd: rootPath, timeout: 5e3 }
10604
+ );
10605
+ const branch = stdout2.trim();
10606
+ if (branch) return branch;
10607
+ } catch {
10608
+ }
10609
+ try {
10610
+ const { stdout: stdout2 } = await execAsync("git branch --list main master", {
10611
+ cwd: rootPath,
10612
+ timeout: 5e3
10613
+ });
10614
+ const branches = stdout2.trim().split("\n").map((b) => b.replace(/^\*?\s*/, "").trim()).filter(Boolean);
10615
+ if (branches.includes("main")) return "main";
10616
+ if (branches.includes("master")) return "master";
10617
+ } catch {
10618
+ }
10619
+ return void 0;
10620
+ }
10621
+ async function getFileGitInfo(rootPath, relativePath) {
10622
+ try {
10623
+ const { stdout: stdout2 } = await execAsync(
10624
+ `git log -1 --format="%H %ct" -- "${relativePath}"`,
10625
+ { cwd: rootPath, timeout: 5e3 }
10626
+ );
10627
+ const parts = stdout2.trim().split(" ");
10628
+ if (parts.length >= 2) {
10629
+ const sha = parts[0];
10630
+ const unixTimestamp = parseInt(parts[1], 10);
10631
+ if (sha && !isNaN(unixTimestamp)) {
10632
+ const timestamp = new Date(unixTimestamp * 1e3).toISOString();
10633
+ return { sha, timestamp };
10634
+ }
10635
+ }
10636
+ } catch {
10637
+ }
10638
+ return void 0;
10639
+ }
10640
+ async function getGitContext(rootPath) {
10641
+ const cached = gitContextCache.get(rootPath);
10642
+ if (cached) return cached;
10643
+ const machineId = getMachineId();
10644
+ const isRepo = await isGitRepo(rootPath);
10645
+ if (!isRepo) {
10646
+ const context2 = { isGitRepo: false, machineId };
10647
+ gitContextCache.set(rootPath, context2);
10648
+ return context2;
10649
+ }
10650
+ const [branch, defaultBranch] = await Promise.all([
10651
+ getGitBranch(rootPath),
10652
+ getGitDefaultBranch(rootPath)
10653
+ ]);
10654
+ const context = {
10655
+ isGitRepo: true,
10656
+ branch,
10657
+ defaultBranch,
10658
+ isDefaultBranch: branch !== void 0 && branch === defaultBranch,
10659
+ machineId
10660
+ };
10661
+ gitContextCache.set(rootPath, context);
10662
+ return context;
10663
+ }
9947
10664
  async function* readAllFilesInBatches(rootPath, options = {}) {
9948
10665
  const maxBatchBytes = options.maxBatchBytes ?? MAX_BATCH_BYTES;
9949
10666
  const largeFileThreshold = options.largeFileThreshold ?? LARGE_FILE_THRESHOLD;
9950
10667
  const maxFilesPerBatch = options.maxFilesPerBatch ?? options.batchSize ?? MAX_FILES_PER_BATCH;
9951
10668
  const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
9952
10669
  const ig = options.ignoreInstance ?? await loadIgnorePatterns(rootPath);
10670
+ const gitCtx = await getGitContext(rootPath);
9953
10671
  let batch = [];
9954
10672
  let currentBatchBytes = 0;
9955
10673
  async function* walkDir(dir, relativePath = "") {
@@ -9975,7 +10693,25 @@ async function* readAllFilesInBatches(rootPath, options = {}) {
9975
10693
  const stat2 = await fs2.promises.stat(fullPath);
9976
10694
  if (stat2.size > maxFileSize) continue;
9977
10695
  const content = await fs2.promises.readFile(fullPath, "utf-8");
9978
- yield { path: relPath, content, sizeBytes: stat2.size };
10696
+ const file = {
10697
+ path: relPath,
10698
+ content,
10699
+ sizeBytes: stat2.size,
10700
+ // Always include machine_id and source_modified_at
10701
+ machine_id: gitCtx.machineId,
10702
+ source_modified_at: stat2.mtime.toISOString()
10703
+ };
10704
+ if (gitCtx.isGitRepo) {
10705
+ file.git_branch = gitCtx.branch;
10706
+ file.git_default_branch = gitCtx.defaultBranch;
10707
+ file.is_default_branch = gitCtx.isDefaultBranch;
10708
+ const gitInfo = await getFileGitInfo(rootPath, relPath);
10709
+ if (gitInfo) {
10710
+ file.git_commit_sha = gitInfo.sha;
10711
+ file.git_commit_timestamp = gitInfo.timestamp;
10712
+ }
10713
+ }
10714
+ yield file;
9979
10715
  } catch {
9980
10716
  }
9981
10717
  }
@@ -9984,11 +10720,12 @@ async function* readAllFilesInBatches(rootPath, options = {}) {
9984
10720
  for await (const file of walkDir(rootPath)) {
9985
10721
  if (file.sizeBytes > largeFileThreshold) {
9986
10722
  if (batch.length > 0) {
9987
- yield batch.map(({ sizeBytes, ...rest }) => rest);
10723
+ yield batch.map(({ sizeBytes: sizeBytes2, ...rest }) => rest);
9988
10724
  batch = [];
9989
10725
  currentBatchBytes = 0;
9990
10726
  }
9991
- yield [{ path: file.path, content: file.content }];
10727
+ const { sizeBytes, ...fileData } = file;
10728
+ yield [fileData];
9992
10729
  continue;
9993
10730
  }
9994
10731
  const wouldExceedBytes = currentBatchBytes + file.sizeBytes > maxBatchBytes;
@@ -10014,6 +10751,7 @@ async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}
10014
10751
  const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
10015
10752
  const sinceMs = sinceTimestamp.getTime();
10016
10753
  const ig = options.ignoreInstance ?? await loadIgnorePatterns(rootPath);
10754
+ const gitCtx = await getGitContext(rootPath);
10017
10755
  let batch = [];
10018
10756
  let currentBatchBytes = 0;
10019
10757
  let filesScanned = 0;
@@ -10044,7 +10782,25 @@ async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}
10044
10782
  if (stat2.size > maxFileSize) continue;
10045
10783
  const content = await fs2.promises.readFile(fullPath, "utf-8");
10046
10784
  filesChanged++;
10047
- yield { path: relPath, content, sizeBytes: stat2.size };
10785
+ const file = {
10786
+ path: relPath,
10787
+ content,
10788
+ sizeBytes: stat2.size,
10789
+ // Always include machine_id and source_modified_at
10790
+ machine_id: gitCtx.machineId,
10791
+ source_modified_at: stat2.mtime.toISOString()
10792
+ };
10793
+ if (gitCtx.isGitRepo) {
10794
+ file.git_branch = gitCtx.branch;
10795
+ file.git_default_branch = gitCtx.defaultBranch;
10796
+ file.is_default_branch = gitCtx.isDefaultBranch;
10797
+ const gitInfo = await getFileGitInfo(rootPath, relPath);
10798
+ if (gitInfo) {
10799
+ file.git_commit_sha = gitInfo.sha;
10800
+ file.git_commit_timestamp = gitInfo.timestamp;
10801
+ }
10802
+ }
10803
+ yield file;
10048
10804
  } catch {
10049
10805
  }
10050
10806
  }
@@ -10053,11 +10809,12 @@ async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}
10053
10809
  for await (const file of walkDir(rootPath)) {
10054
10810
  if (file.sizeBytes > largeFileThreshold) {
10055
10811
  if (batch.length > 0) {
10056
- yield batch.map(({ sizeBytes, ...rest }) => rest);
10812
+ yield batch.map(({ sizeBytes: sizeBytes2, ...rest }) => rest);
10057
10813
  batch = [];
10058
10814
  currentBatchBytes = 0;
10059
10815
  }
10060
- yield [{ path: file.path, content: file.content }];
10816
+ const { sizeBytes, ...fileData } = file;
10817
+ yield [fileData];
10061
10818
  continue;
10062
10819
  }
10063
10820
  const wouldExceedBytes = currentBatchBytes + file.sizeBytes > maxBatchBytes;
@@ -10331,6 +11088,7 @@ var CacheKeys = {
10331
11088
  var globalCache = new MemoryCache();
10332
11089
 
10333
11090
  // src/client.ts
11091
+ init_version();
10334
11092
  var uuidSchema = external_exports.string().uuid();
10335
11093
  function unwrapApiResponse(result) {
10336
11094
  if (!result || typeof result !== "object") return result;
@@ -11153,6 +11911,26 @@ var ContextStreamClient = class {
11153
11911
  uuidSchema.parse(projectId);
11154
11912
  return request(this.config, `/projects/${projectId}/index/status`, { method: "GET" });
11155
11913
  }
11914
+ /**
11915
+ * Get index history for audit trail
11916
+ * Shows which files were indexed, when, by which machine, and from which branch
11917
+ */
11918
+ projectIndexHistory(projectId, params) {
11919
+ uuidSchema.parse(projectId);
11920
+ const queryParams = new URLSearchParams();
11921
+ if (params?.machine_id) queryParams.set("machine_id", params.machine_id);
11922
+ if (params?.branch) queryParams.set("branch", params.branch);
11923
+ if (params?.since) queryParams.set("since", params.since);
11924
+ if (params?.until) queryParams.set("until", params.until);
11925
+ if (params?.path_pattern) queryParams.set("path_pattern", params.path_pattern);
11926
+ if (params?.sort_by) queryParams.set("sort_by", params.sort_by);
11927
+ if (params?.sort_order) queryParams.set("sort_order", params.sort_order);
11928
+ if (params?.page) queryParams.set("page", params.page.toString());
11929
+ if (params?.limit) queryParams.set("limit", params.limit.toString());
11930
+ const queryString = queryParams.toString();
11931
+ const url = `/projects/${projectId}/index/history${queryString ? `?${queryString}` : ""}`;
11932
+ return request(this.config, url, { method: "GET" });
11933
+ }
11156
11934
  /**
11157
11935
  * Check if project ingestion is recommended and return a recommendation.
11158
11936
  * This is used by session_init to inform the AI about the project's index status.
@@ -11227,10 +12005,11 @@ var ContextStreamClient = class {
11227
12005
  * Ingest files for indexing
11228
12006
  * This uploads files to the API for indexing
11229
12007
  * @param projectId - Project UUID
11230
- * @param files - Array of files to ingest
12008
+ * @param files - Array of files to ingest (with optional version metadata)
11231
12009
  * @param options - Optional ingest options
11232
12010
  * @param options.write_to_disk - When true, write files to disk under QA_FILE_WRITE_ROOT before indexing
11233
12011
  * @param options.overwrite - Allow overwriting existing files when write_to_disk is enabled
12012
+ * @param options.force - When true, bypass version checking and force re-index all files
11234
12013
  */
11235
12014
  ingestFiles(projectId, files, options) {
11236
12015
  uuidSchema.parse(projectId);
@@ -11238,7 +12017,8 @@ var ContextStreamClient = class {
11238
12017
  body: {
11239
12018
  files,
11240
12019
  ...options?.write_to_disk !== void 0 && { write_to_disk: options.write_to_disk },
11241
- ...options?.overwrite !== void 0 && { overwrite: options.overwrite }
12020
+ ...options?.overwrite !== void 0 && { overwrite: options.overwrite },
12021
+ ...options?.force !== void 0 && { force: options.force }
11242
12022
  }
11243
12023
  });
11244
12024
  }
@@ -11336,6 +12116,23 @@ var ContextStreamClient = class {
11336
12116
  uuidSchema.parse(workspaceId);
11337
12117
  return request(this.config, `/workspaces/${workspaceId}/content`, { method: "GET" });
11338
12118
  }
12119
+ /**
12120
+ * Get workspace index settings for multi-machine sync configuration
12121
+ */
12122
+ getWorkspaceIndexSettings(workspaceId) {
12123
+ uuidSchema.parse(workspaceId);
12124
+ return request(this.config, `/workspaces/${workspaceId}/index-settings`, { method: "GET" });
12125
+ }
12126
+ /**
12127
+ * Update workspace index settings (admin only)
12128
+ */
12129
+ updateWorkspaceIndexSettings(workspaceId, settings) {
12130
+ uuidSchema.parse(workspaceId);
12131
+ return request(this.config, `/workspaces/${workspaceId}/index-settings`, {
12132
+ method: "PUT",
12133
+ body: settings
12134
+ });
12135
+ }
11339
12136
  // Memory extended operations
11340
12137
  getMemoryEvent(eventId) {
11341
12138
  uuidSchema.parse(eventId);
@@ -12647,7 +13444,8 @@ var ContextStreamClient = class {
12647
13444
  // Transcript save parameters
12648
13445
  ...params.save_exchange !== void 0 && { save_exchange: params.save_exchange },
12649
13446
  ...params.session_id !== void 0 && { session_id: params.session_id },
12650
- ...params.client_name !== void 0 && { client_name: params.client_name }
13447
+ ...params.client_name !== void 0 && { client_name: params.client_name },
13448
+ ...params.assistant_message !== void 0 && { assistant_message: params.assistant_message }
12651
13449
  }
12652
13450
  });
12653
13451
  const data = unwrapApiResponse(apiResult);
@@ -12664,10 +13462,17 @@ var ContextStreamClient = class {
12664
13462
  if (versionWarning2) {
12665
13463
  warnings2.push(versionWarning2);
12666
13464
  }
13465
+ const CS_HEADER2 = "\u2B21 ContextStream \u2014 Smart Context & Memory";
12667
13466
  let context2 = String(data?.context ?? "");
12668
13467
  const versionInstructions = getVersionInstructions(versionNotice2);
12669
13468
  if (versionInstructions) {
12670
- context2 = `${versionInstructions}
13469
+ context2 = `${CS_HEADER2}
13470
+
13471
+ ${versionInstructions}
13472
+
13473
+ ${context2}`;
13474
+ } else {
13475
+ context2 = `${CS_HEADER2}
12671
13476
 
12672
13477
  ${context2}`;
12673
13478
  }
@@ -12825,11 +13630,13 @@ ${context2}`;
12825
13630
  let charsUsed = 0;
12826
13631
  const maxChars = maxTokens * 4;
12827
13632
  let candidateContext;
13633
+ const CS_HEADER = "\u2B21 ContextStream \u2014 Smart Context & Memory";
13634
+ const CS_HEADER_MINIFIED = "CS:active";
12828
13635
  const CS_FIRST_RULE = "\u26A0\uFE0F STOP: Before Glob/Grep/Read \u2192 search(mode=hybrid) FIRST. Local tools ONLY if 0 results.";
12829
13636
  const CS_FIRST_RULE_MINIFIED = "R:CS-first|Idx:project.index_status->ingest|NoLocalScanUnlessCSempty";
12830
13637
  if (format === "minified") {
12831
- const parts = [CS_FIRST_RULE_MINIFIED];
12832
- charsUsed = CS_FIRST_RULE_MINIFIED.length + 1;
13638
+ const parts = [CS_HEADER_MINIFIED, CS_FIRST_RULE_MINIFIED];
13639
+ charsUsed = CS_HEADER_MINIFIED.length + CS_FIRST_RULE_MINIFIED.length + 2;
12833
13640
  for (const item of items) {
12834
13641
  const entry = `${item.type}:${item.value}`;
12835
13642
  if (charsUsed + entry.length + 1 > maxChars) break;
@@ -12837,12 +13644,12 @@ ${context2}`;
12837
13644
  charsUsed += entry.length + 1;
12838
13645
  }
12839
13646
  context = parts.join("|");
12840
- candidateContext = [CS_FIRST_RULE_MINIFIED, ...items.map((i) => `${i.type}:${i.value}`)].join(
13647
+ candidateContext = [CS_HEADER_MINIFIED, CS_FIRST_RULE_MINIFIED, ...items.map((i) => `${i.type}:${i.value}`)].join(
12841
13648
  "|"
12842
13649
  );
12843
13650
  } else if (format === "structured") {
12844
- const grouped = { R: [CS_FIRST_RULE] };
12845
- charsUsed = CS_FIRST_RULE.length + 10;
13651
+ const grouped = { _: [CS_HEADER], R: [CS_FIRST_RULE] };
13652
+ charsUsed = CS_HEADER.length + CS_FIRST_RULE.length + 15;
12846
13653
  for (const item of items) {
12847
13654
  if (charsUsed > maxChars) break;
12848
13655
  if (!grouped[item.type]) grouped[item.type] = [];
@@ -12850,15 +13657,15 @@ ${context2}`;
12850
13657
  charsUsed += item.value.length + 5;
12851
13658
  }
12852
13659
  context = JSON.stringify(grouped);
12853
- const candidateGrouped = { R: [CS_FIRST_RULE] };
13660
+ const candidateGrouped = { _: [CS_HEADER], R: [CS_FIRST_RULE] };
12854
13661
  for (const item of items) {
12855
13662
  if (!candidateGrouped[item.type]) candidateGrouped[item.type] = [];
12856
13663
  candidateGrouped[item.type].push(item.value);
12857
13664
  }
12858
13665
  candidateContext = JSON.stringify(candidateGrouped);
12859
13666
  } else {
12860
- const lines = [CS_FIRST_RULE, "", "[CTX]"];
12861
- charsUsed = CS_FIRST_RULE.length + 10;
13667
+ const lines = [CS_HEADER, "", CS_FIRST_RULE, "", "[CTX]"];
13668
+ charsUsed = CS_HEADER.length + CS_FIRST_RULE.length + 15;
12862
13669
  for (const item of items) {
12863
13670
  const line = `${item.type}:${item.value}`;
12864
13671
  if (charsUsed + line.length + 1 > maxChars) break;
@@ -12867,7 +13674,7 @@ ${context2}`;
12867
13674
  }
12868
13675
  lines.push("[/CTX]");
12869
13676
  context = lines.join("\n");
12870
- const candidateLines = [CS_FIRST_RULE, "", "[CTX]"];
13677
+ const candidateLines = [CS_HEADER, "", CS_FIRST_RULE, "", "[CTX]"];
12871
13678
  for (const item of items) {
12872
13679
  candidateLines.push(`${item.type}:${item.value}`);
12873
13680
  }
@@ -12876,7 +13683,9 @@ ${context2}`;
12876
13683
  }
12877
13684
  if (context.length === 0 && withDefaults.workspace_id) {
12878
13685
  const wsHint = items.find((i) => i.type === "W")?.value || withDefaults.workspace_id;
12879
- context = format === "minified" ? `${CS_FIRST_RULE_MINIFIED}|W:${wsHint}|[NO_MATCHES]` : `${CS_FIRST_RULE}
13686
+ context = format === "minified" ? `${CS_HEADER_MINIFIED}|${CS_FIRST_RULE_MINIFIED}|W:${wsHint}|[NO_MATCHES]` : `${CS_HEADER}
13687
+
13688
+ ${CS_FIRST_RULE}
12880
13689
 
12881
13690
  [CTX]
12882
13691
  W:${wsHint}
@@ -14306,6 +15115,7 @@ import * as path6 from "node:path";
14306
15115
  import { homedir as homedir3 } from "node:os";
14307
15116
 
14308
15117
  // src/rules-templates.ts
15118
+ init_version();
14309
15119
  var DEFAULT_CLAUDE_MCP_SERVER_NAME = "contextstream";
14310
15120
  var RULES_VERSION = VERSION === "unknown" ? "0.0.0" : VERSION;
14311
15121
  var CONTEXTSTREAM_TOOL_NAMES = [
@@ -14354,7 +15164,7 @@ var CONTEXTSTREAM_RULES_BOOTSTRAP = `
14354
15164
 
14355
15165
  **Hooks:** \`<system-reminder>\` tags contain injected instructions \u2014 follow them exactly.
14356
15166
 
14357
- **Notices:** [LESSONS_WARNING] \u2192 tell user first | [RULES_NOTICE] \u2192 run \`generate_rules()\` | [VERSION_NOTICE] \u2192 tell user to update
15167
+ **Notices:** [LESSONS_WARNING] \u2192 apply lessons | [PREFERENCE] \u2192 follow user preferences | [RULES_NOTICE] \u2192 run \`generate_rules()\` | [VERSION_NOTICE/CRITICAL] \u2192 tell user about update
14358
15168
 
14359
15169
  v${RULES_VERSION}
14360
15170
  `.trim();
@@ -14395,6 +15205,7 @@ var CONTEXTSTREAM_RULES_FULL = `
14395
15205
  ## Handle Notices from context()
14396
15206
 
14397
15207
  - **[LESSONS_WARNING]** \u2192 Tell user about past mistakes BEFORE proceeding
15208
+ - **[PREFERENCE]** \u2192 Follow user preferences (high-priority user memories)
14398
15209
  - **[RULES_NOTICE]** \u2192 Run \`generate_rules()\` to update
14399
15210
  - **[VERSION_NOTICE]** \u2192 Tell user to update MCP
14400
15211
 
@@ -14621,11 +15432,14 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
14621
15432
 
14622
15433
  **[RULES_NOTICE]** - Update rules via \`generate_rules()\` (or rerun setup).
14623
15434
 
14624
- **[VERSION_NOTICE]** - You **MUST IMMEDIATELY** tell the user:
14625
- 1. A new MCP server version is available
14626
- 2. The exact update command to run
14627
- 3. That they need to restart their AI tool after updating
14628
- **Do not skip this** - users often miss stderr warnings.
15435
+ **[VERSION_NOTICE]** or **[VERSION_CRITICAL]** - You **MUST** tell the user (be helpful, not annoying):
15436
+ 1. A new MCP server version is available with improvements
15437
+ 2. Provide update commands (user chooses preferred method):
15438
+ - macOS/Linux: \`curl -fsSL https://contextstream.io/scripts/setup.sh | bash\`
15439
+ - Windows: \`irm https://contextstream.io/scripts/setup.ps1 | iex\`
15440
+ - npm: \`npm install -g @contextstream/mcp-server@latest\`
15441
+ 3. Restart AI tool after updating
15442
+ **Mention once** - don't nag. Users often miss stderr warnings so this helps.
14629
15443
 
14630
15444
  **[LESSONS_WARNING]** - You **MUST** before proceeding:
14631
15445
  1. Read all lessons listed
@@ -14637,9 +15451,17 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
14637
15451
 
14638
15452
  ### Preferences & Lessons (Use Early)
14639
15453
 
14640
- - If preferences/style matter: \`session(action="user_context")\`
15454
+ **Preferences ([PREFERENCE] in context response):**
15455
+ - High-priority user memories that should guide your behavior
15456
+ - Surfaced automatically via \`context()\` warnings field
15457
+ - To save: \`session(action="remember", content="...")\`
15458
+ - To retrieve explicitly: \`session(action="user_context")\`
15459
+
15460
+ **Lessons ([LESSONS_WARNING] in context response):**
15461
+ - Past mistakes to avoid - apply prevention steps
15462
+ - Surfaced automatically via \`context()\` warnings field
14641
15463
  - Before risky changes: \`session(action="get_lessons", query="<topic>")\`
14642
- - On frustration/corrections: \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\`
15464
+ - On mistakes: \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\`
14643
15465
 
14644
15466
  ---
14645
15467
 
@@ -14933,6 +15755,7 @@ Rules Version: ${RULES_VERSION}
14933
15755
  ## Handle Notices from context()
14934
15756
 
14935
15757
  - **[LESSONS_WARNING]** \u2192 Tell user about past mistakes BEFORE proceeding
15758
+ - **[PREFERENCE]** \u2192 Follow user preferences (high-priority user memories)
14936
15759
  - **[RULES_NOTICE]** \u2192 Run \`generate_rules()\`
14937
15760
  - **[VERSION_NOTICE]** \u2192 Tell user to update MCP
14938
15761
 
@@ -14974,9 +15797,13 @@ To disable automatic restoration:
14974
15797
 
14975
15798
  ### Notices - MUST HANDLE IMMEDIATELY
14976
15799
 
14977
- - **[VERSION_NOTICE]**: Tell the user about the update and command to run
15800
+ - **[VERSION_NOTICE]** or **[VERSION_CRITICAL]**: Tell user about the update in a helpful, non-annoying way. Provide update commands:
15801
+ - macOS/Linux: \`curl -fsSL https://contextstream.io/scripts/setup.sh | bash\`
15802
+ - Windows: \`irm https://contextstream.io/scripts/setup.ps1 | iex\`
15803
+ - npm: \`npm install -g @contextstream/mcp-server@latest\`
14978
15804
  - **[RULES_NOTICE]**: Run \`generate_rules(overwrite_existing=true)\` to update
14979
15805
  - **[LESSONS_WARNING]**: Read lessons, tell user about them, explain how you'll avoid past mistakes
15806
+ - **[PREFERENCE]**: Follow user preferences - these are high-priority user memories that should guide your behavior
14980
15807
 
14981
15808
  ### Plans & Tasks
14982
15809
 
@@ -15015,12 +15842,42 @@ You MUST follow these rules manually - there is no automatic enforcement.
15015
15842
  - Check response for \`indexing_status\`
15016
15843
  - If \`"started"\` or \`"refreshing"\`: wait before searching
15017
15844
 
15018
- 2. **Call \`context(user_message="<first_message>")\`** SECOND
15019
- - Gets task-specific rules and lessons
15845
+ 2. **Generate a unique session_id** (e.g., \`"session-" + timestamp\` or a UUID)
15846
+ - Use this SAME session_id for ALL context() calls in this conversation
15847
+ - This groups all turns together in the transcript
15848
+
15849
+ 3. **Call \`context(user_message="<first_message>", save_exchange=true, session_id="<your-session-id>")\`**
15850
+ - Gets task-specific rules, lessons, and preferences
15020
15851
  - Check for [LESSONS_WARNING] - past mistakes to avoid
15852
+ - Check for [PREFERENCE] - user preferences to follow
15021
15853
  - Check for [RULES_NOTICE] - update rules if needed
15854
+ - **save_exchange=true** saves each conversation turn for later retrieval
15855
+
15856
+ 4. **NEVER skip init/context** - you will miss critical context
15857
+
15858
+ ---
15859
+
15860
+ ## \u{1F4BE} AUTOMATIC TRANSCRIPT SAVING (CRITICAL)
15861
+
15862
+ **This editor does NOT have hooks to auto-save transcripts.**
15863
+ You MUST save each conversation turn manually:
15864
+
15865
+ ### On EVERY message (including the first):
15866
+ \`\`\`
15867
+ context(user_message="<user's message>", save_exchange=true, session_id="<session-id>")
15868
+ \`\`\`
15022
15869
 
15023
- 3. **NEVER skip init/context** - you will miss critical context
15870
+ ### Why save_exchange matters:
15871
+ - Transcripts enable searching past conversations
15872
+ - Allows context restoration after compaction
15873
+ - Provides conversation history for debugging
15874
+ - Required for the Transcripts page in the dashboard
15875
+
15876
+ ### Session ID Guidelines:
15877
+ - Generate ONCE at the start of the conversation
15878
+ - Use a unique identifier: \`"session-" + Date.now()\` or a UUID
15879
+ - Keep the SAME session_id for ALL context() calls in this session
15880
+ - Different sessions = different transcripts
15024
15881
 
15025
15882
  ---
15026
15883
 
@@ -15130,6 +15987,39 @@ memory(action="create_task", title="...", plan_id="...")
15130
15987
 
15131
15988
  \u274C DO NOT use built-in plan mode or write plans to markdown files.
15132
15989
 
15990
+ ---
15991
+
15992
+ ## \u{1F504} VERSION UPDATES (Check Periodically)
15993
+
15994
+ **This editor does NOT have hooks to check for updates automatically.**
15995
+ You should check for updates using \`help(action="version")\` periodically (e.g., at session start).
15996
+
15997
+ ### If the response includes [VERSION_NOTICE] or [VERSION_CRITICAL]:
15998
+
15999
+ **Tell the user** about the available update in a helpful, non-annoying way:
16000
+ - Frame it as "new features and improvements available"
16001
+ - Provide the update commands (user can choose their preferred method)
16002
+ - Don't nag repeatedly - mention once, then only if user asks
16003
+
16004
+ ### Update Commands (provide all options):
16005
+
16006
+ **macOS/Linux:**
16007
+ \`\`\`bash
16008
+ curl -fsSL https://contextstream.io/scripts/setup.sh | bash
16009
+ \`\`\`
16010
+
16011
+ **Windows (PowerShell):**
16012
+ \`\`\`powershell
16013
+ irm https://contextstream.io/scripts/setup.ps1 | iex
16014
+ \`\`\`
16015
+
16016
+ **npm (requires Node.js 18+):**
16017
+ \`\`\`bash
16018
+ npm install -g @contextstream/mcp-server@latest
16019
+ \`\`\`
16020
+
16021
+ After updating, user should restart their AI tool.
16022
+
15133
16023
  ---
15134
16024
  `;
15135
16025
  var NO_HOOKS_EDITORS = ["codex", "aider", "antigravity"];
@@ -15238,6 +16128,9 @@ function generateAllRuleFiles(options) {
15238
16128
  }).filter((r) => r !== null);
15239
16129
  }
15240
16130
 
16131
+ // src/tools.ts
16132
+ init_version();
16133
+
15241
16134
  // src/tool-catalog.ts
15242
16135
  var TOOL_CATALOG = [
15243
16136
  {
@@ -17241,12 +18134,6 @@ function registerTools(server, client, sessionManager) {
17241
18134
  function getToolAccessTier(toolName) {
17242
18135
  return proTools.has(toolName) ? "pro" : "free";
17243
18136
  }
17244
- function getToolAccessLabel(toolName) {
17245
- const graphTier = graphToolTiers.get(toolName);
17246
- if (graphTier === "lite") return "Pro (Graph-Lite)";
17247
- if (graphTier === "full") return "Elite/Team (Full Graph)";
17248
- return getToolAccessTier(toolName) === "pro" ? "PRO" : "Free";
17249
- }
17250
18137
  async function gateIfProTool(toolName) {
17251
18138
  if (getToolAccessTier(toolName) !== "pro") return null;
17252
18139
  const planName = await client.getPlanName();
@@ -17542,22 +18429,18 @@ function registerTools(server, client, sessionManager) {
17542
18429
  };
17543
18430
  }
17544
18431
  function actuallyRegisterTool(name, config, handler) {
17545
- const accessLabel = getToolAccessLabel(name);
17546
- const showUpgrade = accessLabel !== "Free";
17547
18432
  let finalDescription;
17548
18433
  let finalSchema;
17549
18434
  if (COMPACT_SCHEMA_ENABLED) {
17550
18435
  finalDescription = compactifyDescription(config.description);
17551
18436
  finalSchema = config.inputSchema ? applyCompactParamDescriptions(config.inputSchema) : void 0;
17552
18437
  } else {
17553
- finalDescription = `${config.description}
17554
-
17555
- Access: ${accessLabel}${showUpgrade ? ` (upgrade: ${upgradeUrl2})` : ""}`;
18438
+ finalDescription = config.description;
17556
18439
  finalSchema = config.inputSchema ? applyParamDescriptions(config.inputSchema) : void 0;
17557
18440
  }
17558
18441
  const labeledConfig = {
17559
18442
  ...config,
17560
- title: COMPACT_SCHEMA_ENABLED ? config.title : `${config.title} (${accessLabel})`,
18443
+ title: config.title,
17561
18444
  description: finalDescription
17562
18445
  };
17563
18446
  const annotatedConfig = {
@@ -20935,7 +21818,8 @@ This saves ~80% tokens compared to including full chat history.`,
20935
21818
  context_threshold: external_exports.number().optional().describe("Custom context window threshold (defaults to 70k)"),
20936
21819
  save_exchange: external_exports.boolean().optional().describe("Save this exchange to the transcript for later search (background task)"),
20937
21820
  session_id: external_exports.string().optional().describe("Session ID for transcript association (required if save_exchange is true)"),
20938
- client_name: external_exports.string().optional().describe("Client name for transcript metadata (e.g., 'claude', 'cursor')")
21821
+ client_name: external_exports.string().optional().describe("Client name for transcript metadata (e.g., 'claude', 'cursor')"),
21822
+ assistant_message: external_exports.string().optional().describe("Previous assistant response to save along with user message (for complete exchange capture)")
20939
21823
  })
20940
21824
  },
20941
21825
  async (input) => {
@@ -21031,7 +21915,8 @@ This saves ~80% tokens compared to including full chat history.`,
21031
21915
  context_threshold: contextThreshold,
21032
21916
  save_exchange: input.save_exchange,
21033
21917
  session_id: sessionId,
21034
- client_name: clientName
21918
+ client_name: clientName,
21919
+ assistant_message: input.assistant_message
21035
21920
  });
21036
21921
  if (sessionManager && result.token_estimate) {
21037
21922
  sessionManager.addTokens(result.token_estimate);
@@ -24293,7 +25178,7 @@ ${formatContent(result)}`
24293
25178
  "project",
24294
25179
  {
24295
25180
  title: "Project",
24296
- description: `Project management. Actions: list, get, create, update, index (trigger indexing), overview, statistics, files, index_status, ingest_local (index local folder), team_projects (list all team projects - team plans only).`,
25181
+ description: `Project management. Actions: list, get, create, update, index (trigger indexing), overview, statistics, files, index_status, index_history (audit trail of indexed files), ingest_local (index local folder), team_projects (list all team projects - team plans only).`,
24297
25182
  inputSchema: external_exports.object({
24298
25183
  action: external_exports.enum([
24299
25184
  "list",
@@ -24305,6 +25190,7 @@ ${formatContent(result)}`
24305
25190
  "statistics",
24306
25191
  "files",
24307
25192
  "index_status",
25193
+ "index_history",
24308
25194
  "ingest_local",
24309
25195
  "team_projects"
24310
25196
  ]).describe("Action to perform"),
@@ -24319,6 +25205,15 @@ ${formatContent(result)}`
24319
25205
  path: external_exports.string().optional().describe("Local path to ingest"),
24320
25206
  overwrite: external_exports.boolean().optional(),
24321
25207
  write_to_disk: external_exports.boolean().optional(),
25208
+ force: external_exports.boolean().optional().describe("Force re-index all files, bypassing version check logic"),
25209
+ // Index history filters
25210
+ machine_id: external_exports.string().optional().describe("Filter by machine ID that indexed the files"),
25211
+ branch: external_exports.string().optional().describe("Filter by git branch"),
25212
+ since: external_exports.string().optional().describe("Filter files indexed after this timestamp (ISO 8601)"),
25213
+ until: external_exports.string().optional().describe("Filter files indexed before this timestamp (ISO 8601)"),
25214
+ path_pattern: external_exports.string().optional().describe("Filter by file path pattern (partial match)"),
25215
+ sort_by: external_exports.enum(["path", "indexed", "size"]).optional().describe("Sort field (default: indexed)"),
25216
+ sort_order: external_exports.enum(["asc", "desc"]).optional().describe("Sort order (default: desc)"),
24322
25217
  // Pagination
24323
25218
  page: external_exports.number().optional(),
24324
25219
  page_size: external_exports.number().optional()
@@ -24417,6 +25312,25 @@ ${formatContent(result)}`
24417
25312
  content: [{ type: "text", text: formatContent(result) }]
24418
25313
  };
24419
25314
  }
25315
+ case "index_history": {
25316
+ if (!projectId) {
25317
+ return errorResult("index_history requires: project_id");
25318
+ }
25319
+ const result = await client.projectIndexHistory(projectId, {
25320
+ machine_id: input.machine_id,
25321
+ branch: input.branch,
25322
+ since: input.since,
25323
+ until: input.until,
25324
+ path_pattern: input.path_pattern,
25325
+ sort_by: input.sort_by,
25326
+ sort_order: input.sort_order,
25327
+ page: input.page,
25328
+ limit: input.page_size
25329
+ });
25330
+ return {
25331
+ content: [{ type: "text", text: formatContent(result) }]
25332
+ };
25333
+ }
24420
25334
  case "ingest_local": {
24421
25335
  if (!input.path) {
24422
25336
  return errorResult("ingest_local requires: path");
@@ -24430,23 +25344,26 @@ ${formatContent(result)}`
24430
25344
  }
24431
25345
  const ingestOptions = {
24432
25346
  ...input.write_to_disk !== void 0 && { write_to_disk: input.write_to_disk },
24433
- ...input.overwrite !== void 0 && { overwrite: input.overwrite }
25347
+ ...input.overwrite !== void 0 && { overwrite: input.overwrite },
25348
+ ...input.force !== void 0 && { force: input.force }
24434
25349
  };
24435
25350
  startBackgroundIngest(projectId, validPath.resolvedPath, ingestOptions);
25351
+ const forceNote = input.force ? " (force mode - version checks bypassed)" : "";
24436
25352
  const result = {
24437
25353
  status: "started",
24438
- message: "Ingestion running in background",
25354
+ message: `Ingestion running in background${forceNote}`,
24439
25355
  project_id: projectId,
24440
25356
  path: validPath.resolvedPath,
24441
25357
  ...input.write_to_disk !== void 0 && { write_to_disk: input.write_to_disk },
24442
25358
  ...input.overwrite !== void 0 && { overwrite: input.overwrite },
25359
+ ...input.force !== void 0 && { force: input.force },
24443
25360
  note: "Use 'project' with action 'index_status' to monitor progress."
24444
25361
  };
24445
25362
  return {
24446
25363
  content: [
24447
25364
  {
24448
25365
  type: "text",
24449
- text: `Ingestion started in background for directory: ${validPath.resolvedPath}. Use 'project' with action 'index_status' to monitor progress.`
25366
+ text: `Ingestion started in background${forceNote} for directory: ${validPath.resolvedPath}. Use 'project' with action 'index_status' to monitor progress.`
24450
25367
  }
24451
25368
  ]
24452
25369
  };
@@ -24480,9 +25397,9 @@ ${formatContent(result)}`
24480
25397
  "workspace",
24481
25398
  {
24482
25399
  title: "Workspace",
24483
- description: `Workspace management. Actions: list, get, associate (link folder to workspace), bootstrap (create workspace and initialize), team_members (list members with access - team plans only).`,
25400
+ description: `Workspace management. Actions: list, get, associate (link folder to workspace), bootstrap (create workspace and initialize), team_members (list members with access - team plans only), index_settings (get/update multi-machine sync settings - admin only).`,
24484
25401
  inputSchema: external_exports.object({
24485
- action: external_exports.enum(["list", "get", "associate", "bootstrap", "team_members"]).describe("Action to perform"),
25402
+ action: external_exports.enum(["list", "get", "associate", "bootstrap", "team_members", "index_settings"]).describe("Action to perform"),
24486
25403
  workspace_id: external_exports.string().uuid().optional(),
24487
25404
  // Associate/bootstrap params
24488
25405
  folder_path: external_exports.string().optional(),
@@ -24494,6 +25411,12 @@ ${formatContent(result)}`
24494
25411
  visibility: external_exports.enum(["private", "public"]).optional(),
24495
25412
  auto_index: external_exports.boolean().optional(),
24496
25413
  context_hint: external_exports.string().optional(),
25414
+ // Index settings params (for update)
25415
+ branch_policy: external_exports.enum(["default_branch_wins", "newest_wins", "feature_branch_wins"]).optional().describe("Which branch takes priority: default_branch_wins (default), newest_wins, feature_branch_wins"),
25416
+ conflict_resolution: external_exports.enum(["newest_timestamp", "default_branch", "manual"]).optional().describe("How to resolve conflicts: newest_timestamp (default), default_branch, manual"),
25417
+ allowed_machines: external_exports.array(external_exports.string()).optional().describe("List of allowed machine IDs (empty = all allowed)"),
25418
+ auto_sync_enabled: external_exports.boolean().optional().describe("Whether to auto-sync from all machines (default: true)"),
25419
+ max_machines: external_exports.number().optional().describe("Maximum machines allowed to index (0 = unlimited)"),
24497
25420
  // Pagination
24498
25421
  page: external_exports.number().optional(),
24499
25422
  page_size: external_exports.number().optional()
@@ -24597,6 +25520,29 @@ ${formatContent(result)}`
24597
25520
  content: [{ type: "text", text: formatContent(teamMembers) }]
24598
25521
  };
24599
25522
  }
25523
+ case "index_settings": {
25524
+ if (!input.workspace_id) {
25525
+ return errorResult("index_settings requires: workspace_id");
25526
+ }
25527
+ const hasUpdateParams = input.branch_policy !== void 0 || input.conflict_resolution !== void 0 || input.allowed_machines !== void 0 || input.auto_sync_enabled !== void 0 || input.max_machines !== void 0;
25528
+ if (hasUpdateParams) {
25529
+ const result = await client.updateWorkspaceIndexSettings(input.workspace_id, {
25530
+ branch_policy: input.branch_policy,
25531
+ conflict_resolution: input.conflict_resolution,
25532
+ allowed_machines: input.allowed_machines,
25533
+ auto_sync_enabled: input.auto_sync_enabled,
25534
+ max_machines: input.max_machines
25535
+ });
25536
+ return {
25537
+ content: [{ type: "text", text: formatContent(result) }]
25538
+ };
25539
+ } else {
25540
+ const result = await client.getWorkspaceIndexSettings(input.workspace_id);
25541
+ return {
25542
+ content: [{ type: "text", text: formatContent(result) }]
25543
+ };
25544
+ }
25545
+ }
24600
25546
  default:
24601
25547
  return errorResult(`Unknown action: ${input.action}`);
24602
25548
  }
@@ -27117,7 +28063,7 @@ var SessionManager = class _SessionManager {
27117
28063
  buildContextSummary(context) {
27118
28064
  const parts = [];
27119
28065
  parts.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
27120
- parts.push("\u2B21 SMART CONTEXT (ContextStream)");
28066
+ parts.push("\u2B21 ContextStream \u2014 Smart Context & Memory");
27121
28067
  parts.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
27122
28068
  if (context.status === "requires_workspace_name") {
27123
28069
  parts.push("");
@@ -27345,6 +28291,7 @@ import { createServer } from "node:http";
27345
28291
  import { randomUUID as randomUUID3 } from "node:crypto";
27346
28292
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
27347
28293
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
28294
+ init_version();
27348
28295
  var HOST = process.env.MCP_HTTP_HOST || "0.0.0.0";
27349
28296
  var PORT = Number.parseInt(process.env.MCP_HTTP_PORT || "8787", 10);
27350
28297
  var MCP_PATH = process.env.MCP_HTTP_PATH || "/mcp";
@@ -27623,6 +28570,7 @@ async function runHttpGateway() {
27623
28570
  }
27624
28571
 
27625
28572
  // src/index.ts
28573
+ init_version();
27626
28574
  import { existsSync as existsSync17, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
27627
28575
  import { homedir as homedir19 } from "os";
27628
28576
  import { join as join23 } from "path";
@@ -27633,6 +28581,7 @@ import * as path8 from "node:path";
27633
28581
  import { homedir as homedir5 } from "node:os";
27634
28582
  import { stdin, stdout } from "node:process";
27635
28583
  import { createInterface } from "node:readline/promises";
28584
+ init_version();
27636
28585
 
27637
28586
  // src/credentials.ts
27638
28587
  import * as fs6 from "node:fs/promises";
@@ -27783,7 +28732,8 @@ var CONTEXTSTREAM_PREAMBLE_PATTERNS2 = [
27783
28732
  /^#\s+cline rules$/i,
27784
28733
  /^#\s+kilo code rules$/i,
27785
28734
  /^#\s+roo code rules$/i,
27786
- /^#\s+aider configuration$/i
28735
+ /^#\s+aider configuration$/i,
28736
+ /^#\s+antigravity agent rules$/i
27787
28737
  ];
27788
28738
  function wrapWithMarkers2(content) {
27789
28739
  return `${CONTEXTSTREAM_START_MARKER2}
@@ -28191,6 +29141,9 @@ function claudeDesktopConfigPath() {
28191
29141
  const appData = process.env.APPDATA || path8.join(home, "AppData", "Roaming");
28192
29142
  return path8.join(appData, "Claude", "claude_desktop_config.json");
28193
29143
  }
29144
+ if (process.platform === "linux") {
29145
+ return path8.join(home, ".config", "Claude", "claude_desktop_config.json");
29146
+ }
28194
29147
  return null;
28195
29148
  }
28196
29149
  async function upsertCodexTomlConfig(filePath, params) {
@@ -28576,6 +29529,20 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
28576
29529
  const contextPackEnabled = isPro;
28577
29530
  const showTiming = false;
28578
29531
  const restoreContextEnabled = true;
29532
+ console.log("\nAuto-Update:");
29533
+ console.log(" When enabled, ContextStream will automatically update to the latest version");
29534
+ console.log(" on new sessions (checks daily). You can disable this if you prefer manual updates.");
29535
+ const currentAutoUpdate = isAutoUpdateEnabled();
29536
+ const autoUpdateChoice = normalizeInput(
29537
+ await rl.question(`Enable auto-update? [${currentAutoUpdate ? "Y/n" : "y/N"}]: `)
29538
+ ).toLowerCase();
29539
+ const autoUpdateEnabled = autoUpdateChoice === "" ? currentAutoUpdate : autoUpdateChoice === "y" || autoUpdateChoice === "yes";
29540
+ setAutoUpdatePreference(autoUpdateEnabled);
29541
+ if (autoUpdateEnabled) {
29542
+ console.log(" \u2713 Auto-update enabled (disable anytime with CONTEXTSTREAM_AUTO_UPDATE=false)");
29543
+ } else {
29544
+ console.log(" \u2717 Auto-update disabled");
29545
+ }
28579
29546
  const editors = [
28580
29547
  "codex",
28581
29548
  "claude",