@contextstream/mcp-server 0.4.55 → 0.4.56

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 os = platform();
316
+ if (os === "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
  }
@@ -4756,10 +5480,61 @@ function parseTranscriptStats(transcriptPath) {
4756
5480
  }
4757
5481
  return stats;
4758
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) };
5529
+ }
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
  }
@@ -9300,179 +10080,8 @@ var coerce = {
9300
10080
  };
9301
10081
  var NEVER = INVALID;
9302
10082
 
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 });
9356
- }
9357
- const cacheFile = getCacheFilePath();
9358
- writeFileSync(
9359
- cacheFile,
9360
- JSON.stringify({
9361
- latestVersion,
9362
- checkedAt: Date.now()
9363
- })
9364
- );
9365
- } catch {
9366
- }
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;
9382
- }
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;
9390
- });
9391
- }
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);
9402
- }
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) {
9422
- return {
9423
- current: currentVersion,
9424
- latest: latestVersion,
9425
- behind: true,
9426
- upgrade_command: UPGRADE_COMMAND
9427
- };
9428
- }
9429
- } catch {
9430
- }
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));
9439
- }
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;
9446
- }
9447
- return 0;
9448
- } catch {
9449
- return 0;
9450
- }
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}`;
9459
- }
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.`;
9471
- }
9472
- return null;
9473
- }
9474
-
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;
@@ -10331,6 +10940,7 @@ var CacheKeys = {
10331
10940
  var globalCache = new MemoryCache();
10332
10941
 
10333
10942
  // src/client.ts
10943
+ init_version();
10334
10944
  var uuidSchema = external_exports.string().uuid();
10335
10945
  function unwrapApiResponse(result) {
10336
10946
  if (!result || typeof result !== "object") return result;
@@ -12647,7 +13257,8 @@ var ContextStreamClient = class {
12647
13257
  // Transcript save parameters
12648
13258
  ...params.save_exchange !== void 0 && { save_exchange: params.save_exchange },
12649
13259
  ...params.session_id !== void 0 && { session_id: params.session_id },
12650
- ...params.client_name !== void 0 && { client_name: params.client_name }
13260
+ ...params.client_name !== void 0 && { client_name: params.client_name },
13261
+ ...params.assistant_message !== void 0 && { assistant_message: params.assistant_message }
12651
13262
  }
12652
13263
  });
12653
13264
  const data = unwrapApiResponse(apiResult);
@@ -12664,10 +13275,17 @@ var ContextStreamClient = class {
12664
13275
  if (versionWarning2) {
12665
13276
  warnings2.push(versionWarning2);
12666
13277
  }
13278
+ const CS_HEADER2 = "\u2B21 ContextStream \u2014 Smart Context & Memory";
12667
13279
  let context2 = String(data?.context ?? "");
12668
13280
  const versionInstructions = getVersionInstructions(versionNotice2);
12669
13281
  if (versionInstructions) {
12670
- context2 = `${versionInstructions}
13282
+ context2 = `${CS_HEADER2}
13283
+
13284
+ ${versionInstructions}
13285
+
13286
+ ${context2}`;
13287
+ } else {
13288
+ context2 = `${CS_HEADER2}
12671
13289
 
12672
13290
  ${context2}`;
12673
13291
  }
@@ -12825,11 +13443,13 @@ ${context2}`;
12825
13443
  let charsUsed = 0;
12826
13444
  const maxChars = maxTokens * 4;
12827
13445
  let candidateContext;
13446
+ const CS_HEADER = "\u2B21 ContextStream \u2014 Smart Context & Memory";
13447
+ const CS_HEADER_MINIFIED = "CS:active";
12828
13448
  const CS_FIRST_RULE = "\u26A0\uFE0F STOP: Before Glob/Grep/Read \u2192 search(mode=hybrid) FIRST. Local tools ONLY if 0 results.";
12829
13449
  const CS_FIRST_RULE_MINIFIED = "R:CS-first|Idx:project.index_status->ingest|NoLocalScanUnlessCSempty";
12830
13450
  if (format === "minified") {
12831
- const parts = [CS_FIRST_RULE_MINIFIED];
12832
- charsUsed = CS_FIRST_RULE_MINIFIED.length + 1;
13451
+ const parts = [CS_HEADER_MINIFIED, CS_FIRST_RULE_MINIFIED];
13452
+ charsUsed = CS_HEADER_MINIFIED.length + CS_FIRST_RULE_MINIFIED.length + 2;
12833
13453
  for (const item of items) {
12834
13454
  const entry = `${item.type}:${item.value}`;
12835
13455
  if (charsUsed + entry.length + 1 > maxChars) break;
@@ -12837,12 +13457,12 @@ ${context2}`;
12837
13457
  charsUsed += entry.length + 1;
12838
13458
  }
12839
13459
  context = parts.join("|");
12840
- candidateContext = [CS_FIRST_RULE_MINIFIED, ...items.map((i) => `${i.type}:${i.value}`)].join(
13460
+ candidateContext = [CS_HEADER_MINIFIED, CS_FIRST_RULE_MINIFIED, ...items.map((i) => `${i.type}:${i.value}`)].join(
12841
13461
  "|"
12842
13462
  );
12843
13463
  } else if (format === "structured") {
12844
- const grouped = { R: [CS_FIRST_RULE] };
12845
- charsUsed = CS_FIRST_RULE.length + 10;
13464
+ const grouped = { _: [CS_HEADER], R: [CS_FIRST_RULE] };
13465
+ charsUsed = CS_HEADER.length + CS_FIRST_RULE.length + 15;
12846
13466
  for (const item of items) {
12847
13467
  if (charsUsed > maxChars) break;
12848
13468
  if (!grouped[item.type]) grouped[item.type] = [];
@@ -12850,15 +13470,15 @@ ${context2}`;
12850
13470
  charsUsed += item.value.length + 5;
12851
13471
  }
12852
13472
  context = JSON.stringify(grouped);
12853
- const candidateGrouped = { R: [CS_FIRST_RULE] };
13473
+ const candidateGrouped = { _: [CS_HEADER], R: [CS_FIRST_RULE] };
12854
13474
  for (const item of items) {
12855
13475
  if (!candidateGrouped[item.type]) candidateGrouped[item.type] = [];
12856
13476
  candidateGrouped[item.type].push(item.value);
12857
13477
  }
12858
13478
  candidateContext = JSON.stringify(candidateGrouped);
12859
13479
  } else {
12860
- const lines = [CS_FIRST_RULE, "", "[CTX]"];
12861
- charsUsed = CS_FIRST_RULE.length + 10;
13480
+ const lines = [CS_HEADER, "", CS_FIRST_RULE, "", "[CTX]"];
13481
+ charsUsed = CS_HEADER.length + CS_FIRST_RULE.length + 15;
12862
13482
  for (const item of items) {
12863
13483
  const line = `${item.type}:${item.value}`;
12864
13484
  if (charsUsed + line.length + 1 > maxChars) break;
@@ -12867,7 +13487,7 @@ ${context2}`;
12867
13487
  }
12868
13488
  lines.push("[/CTX]");
12869
13489
  context = lines.join("\n");
12870
- const candidateLines = [CS_FIRST_RULE, "", "[CTX]"];
13490
+ const candidateLines = [CS_HEADER, "", CS_FIRST_RULE, "", "[CTX]"];
12871
13491
  for (const item of items) {
12872
13492
  candidateLines.push(`${item.type}:${item.value}`);
12873
13493
  }
@@ -12876,7 +13496,9 @@ ${context2}`;
12876
13496
  }
12877
13497
  if (context.length === 0 && withDefaults.workspace_id) {
12878
13498
  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}
13499
+ context = format === "minified" ? `${CS_HEADER_MINIFIED}|${CS_FIRST_RULE_MINIFIED}|W:${wsHint}|[NO_MATCHES]` : `${CS_HEADER}
13500
+
13501
+ ${CS_FIRST_RULE}
12880
13502
 
12881
13503
  [CTX]
12882
13504
  W:${wsHint}
@@ -14306,6 +14928,7 @@ import * as path6 from "node:path";
14306
14928
  import { homedir as homedir3 } from "node:os";
14307
14929
 
14308
14930
  // src/rules-templates.ts
14931
+ init_version();
14309
14932
  var DEFAULT_CLAUDE_MCP_SERVER_NAME = "contextstream";
14310
14933
  var RULES_VERSION = VERSION === "unknown" ? "0.0.0" : VERSION;
14311
14934
  var CONTEXTSTREAM_TOOL_NAMES = [
@@ -14354,7 +14977,7 @@ var CONTEXTSTREAM_RULES_BOOTSTRAP = `
14354
14977
 
14355
14978
  **Hooks:** \`<system-reminder>\` tags contain injected instructions \u2014 follow them exactly.
14356
14979
 
14357
- **Notices:** [LESSONS_WARNING] \u2192 tell user first | [RULES_NOTICE] \u2192 run \`generate_rules()\` | [VERSION_NOTICE] \u2192 tell user to update
14980
+ **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
14981
 
14359
14982
  v${RULES_VERSION}
14360
14983
  `.trim();
@@ -14395,6 +15018,7 @@ var CONTEXTSTREAM_RULES_FULL = `
14395
15018
  ## Handle Notices from context()
14396
15019
 
14397
15020
  - **[LESSONS_WARNING]** \u2192 Tell user about past mistakes BEFORE proceeding
15021
+ - **[PREFERENCE]** \u2192 Follow user preferences (high-priority user memories)
14398
15022
  - **[RULES_NOTICE]** \u2192 Run \`generate_rules()\` to update
14399
15023
  - **[VERSION_NOTICE]** \u2192 Tell user to update MCP
14400
15024
 
@@ -14621,11 +15245,14 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
14621
15245
 
14622
15246
  **[RULES_NOTICE]** - Update rules via \`generate_rules()\` (or rerun setup).
14623
15247
 
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.
15248
+ **[VERSION_NOTICE]** or **[VERSION_CRITICAL]** - You **MUST** tell the user (be helpful, not annoying):
15249
+ 1. A new MCP server version is available with improvements
15250
+ 2. Provide update commands (user chooses preferred method):
15251
+ - macOS/Linux: \`curl -fsSL https://contextstream.io/scripts/setup.sh | bash\`
15252
+ - Windows: \`irm https://contextstream.io/scripts/setup.ps1 | iex\`
15253
+ - npm: \`npm install -g @contextstream/mcp-server@latest\`
15254
+ 3. Restart AI tool after updating
15255
+ **Mention once** - don't nag. Users often miss stderr warnings so this helps.
14629
15256
 
14630
15257
  **[LESSONS_WARNING]** - You **MUST** before proceeding:
14631
15258
  1. Read all lessons listed
@@ -14637,9 +15264,17 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
14637
15264
 
14638
15265
  ### Preferences & Lessons (Use Early)
14639
15266
 
14640
- - If preferences/style matter: \`session(action="user_context")\`
15267
+ **Preferences ([PREFERENCE] in context response):**
15268
+ - High-priority user memories that should guide your behavior
15269
+ - Surfaced automatically via \`context()\` warnings field
15270
+ - To save: \`session(action="remember", content="...")\`
15271
+ - To retrieve explicitly: \`session(action="user_context")\`
15272
+
15273
+ **Lessons ([LESSONS_WARNING] in context response):**
15274
+ - Past mistakes to avoid - apply prevention steps
15275
+ - Surfaced automatically via \`context()\` warnings field
14641
15276
  - Before risky changes: \`session(action="get_lessons", query="<topic>")\`
14642
- - On frustration/corrections: \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\`
15277
+ - On mistakes: \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\`
14643
15278
 
14644
15279
  ---
14645
15280
 
@@ -14933,6 +15568,7 @@ Rules Version: ${RULES_VERSION}
14933
15568
  ## Handle Notices from context()
14934
15569
 
14935
15570
  - **[LESSONS_WARNING]** \u2192 Tell user about past mistakes BEFORE proceeding
15571
+ - **[PREFERENCE]** \u2192 Follow user preferences (high-priority user memories)
14936
15572
  - **[RULES_NOTICE]** \u2192 Run \`generate_rules()\`
14937
15573
  - **[VERSION_NOTICE]** \u2192 Tell user to update MCP
14938
15574
 
@@ -14974,9 +15610,13 @@ To disable automatic restoration:
14974
15610
 
14975
15611
  ### Notices - MUST HANDLE IMMEDIATELY
14976
15612
 
14977
- - **[VERSION_NOTICE]**: Tell the user about the update and command to run
15613
+ - **[VERSION_NOTICE]** or **[VERSION_CRITICAL]**: Tell user about the update in a helpful, non-annoying way. Provide update commands:
15614
+ - macOS/Linux: \`curl -fsSL https://contextstream.io/scripts/setup.sh | bash\`
15615
+ - Windows: \`irm https://contextstream.io/scripts/setup.ps1 | iex\`
15616
+ - npm: \`npm install -g @contextstream/mcp-server@latest\`
14978
15617
  - **[RULES_NOTICE]**: Run \`generate_rules(overwrite_existing=true)\` to update
14979
15618
  - **[LESSONS_WARNING]**: Read lessons, tell user about them, explain how you'll avoid past mistakes
15619
+ - **[PREFERENCE]**: Follow user preferences - these are high-priority user memories that should guide your behavior
14980
15620
 
14981
15621
  ### Plans & Tasks
14982
15622
 
@@ -15015,12 +15655,42 @@ You MUST follow these rules manually - there is no automatic enforcement.
15015
15655
  - Check response for \`indexing_status\`
15016
15656
  - If \`"started"\` or \`"refreshing"\`: wait before searching
15017
15657
 
15018
- 2. **Call \`context(user_message="<first_message>")\`** SECOND
15019
- - Gets task-specific rules and lessons
15658
+ 2. **Generate a unique session_id** (e.g., \`"session-" + timestamp\` or a UUID)
15659
+ - Use this SAME session_id for ALL context() calls in this conversation
15660
+ - This groups all turns together in the transcript
15661
+
15662
+ 3. **Call \`context(user_message="<first_message>", save_exchange=true, session_id="<your-session-id>")\`**
15663
+ - Gets task-specific rules, lessons, and preferences
15020
15664
  - Check for [LESSONS_WARNING] - past mistakes to avoid
15665
+ - Check for [PREFERENCE] - user preferences to follow
15021
15666
  - Check for [RULES_NOTICE] - update rules if needed
15667
+ - **save_exchange=true** saves each conversation turn for later retrieval
15668
+
15669
+ 4. **NEVER skip init/context** - you will miss critical context
15670
+
15671
+ ---
15672
+
15673
+ ## \u{1F4BE} AUTOMATIC TRANSCRIPT SAVING (CRITICAL)
15674
+
15675
+ **This editor does NOT have hooks to auto-save transcripts.**
15676
+ You MUST save each conversation turn manually:
15677
+
15678
+ ### On EVERY message (including the first):
15679
+ \`\`\`
15680
+ context(user_message="<user's message>", save_exchange=true, session_id="<session-id>")
15681
+ \`\`\`
15022
15682
 
15023
- 3. **NEVER skip init/context** - you will miss critical context
15683
+ ### Why save_exchange matters:
15684
+ - Transcripts enable searching past conversations
15685
+ - Allows context restoration after compaction
15686
+ - Provides conversation history for debugging
15687
+ - Required for the Transcripts page in the dashboard
15688
+
15689
+ ### Session ID Guidelines:
15690
+ - Generate ONCE at the start of the conversation
15691
+ - Use a unique identifier: \`"session-" + Date.now()\` or a UUID
15692
+ - Keep the SAME session_id for ALL context() calls in this session
15693
+ - Different sessions = different transcripts
15024
15694
 
15025
15695
  ---
15026
15696
 
@@ -15130,6 +15800,39 @@ memory(action="create_task", title="...", plan_id="...")
15130
15800
 
15131
15801
  \u274C DO NOT use built-in plan mode or write plans to markdown files.
15132
15802
 
15803
+ ---
15804
+
15805
+ ## \u{1F504} VERSION UPDATES (Check Periodically)
15806
+
15807
+ **This editor does NOT have hooks to check for updates automatically.**
15808
+ You should check for updates using \`help(action="version")\` periodically (e.g., at session start).
15809
+
15810
+ ### If the response includes [VERSION_NOTICE] or [VERSION_CRITICAL]:
15811
+
15812
+ **Tell the user** about the available update in a helpful, non-annoying way:
15813
+ - Frame it as "new features and improvements available"
15814
+ - Provide the update commands (user can choose their preferred method)
15815
+ - Don't nag repeatedly - mention once, then only if user asks
15816
+
15817
+ ### Update Commands (provide all options):
15818
+
15819
+ **macOS/Linux:**
15820
+ \`\`\`bash
15821
+ curl -fsSL https://contextstream.io/scripts/setup.sh | bash
15822
+ \`\`\`
15823
+
15824
+ **Windows (PowerShell):**
15825
+ \`\`\`powershell
15826
+ irm https://contextstream.io/scripts/setup.ps1 | iex
15827
+ \`\`\`
15828
+
15829
+ **npm (requires Node.js 18+):**
15830
+ \`\`\`bash
15831
+ npm install -g @contextstream/mcp-server@latest
15832
+ \`\`\`
15833
+
15834
+ After updating, user should restart their AI tool.
15835
+
15133
15836
  ---
15134
15837
  `;
15135
15838
  var NO_HOOKS_EDITORS = ["codex", "aider", "antigravity"];
@@ -15238,6 +15941,9 @@ function generateAllRuleFiles(options) {
15238
15941
  }).filter((r) => r !== null);
15239
15942
  }
15240
15943
 
15944
+ // src/tools.ts
15945
+ init_version();
15946
+
15241
15947
  // src/tool-catalog.ts
15242
15948
  var TOOL_CATALOG = [
15243
15949
  {
@@ -17241,12 +17947,6 @@ function registerTools(server, client, sessionManager) {
17241
17947
  function getToolAccessTier(toolName) {
17242
17948
  return proTools.has(toolName) ? "pro" : "free";
17243
17949
  }
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
17950
  async function gateIfProTool(toolName) {
17251
17951
  if (getToolAccessTier(toolName) !== "pro") return null;
17252
17952
  const planName = await client.getPlanName();
@@ -17542,22 +18242,18 @@ function registerTools(server, client, sessionManager) {
17542
18242
  };
17543
18243
  }
17544
18244
  function actuallyRegisterTool(name, config, handler) {
17545
- const accessLabel = getToolAccessLabel(name);
17546
- const showUpgrade = accessLabel !== "Free";
17547
18245
  let finalDescription;
17548
18246
  let finalSchema;
17549
18247
  if (COMPACT_SCHEMA_ENABLED) {
17550
18248
  finalDescription = compactifyDescription(config.description);
17551
18249
  finalSchema = config.inputSchema ? applyCompactParamDescriptions(config.inputSchema) : void 0;
17552
18250
  } else {
17553
- finalDescription = `${config.description}
17554
-
17555
- Access: ${accessLabel}${showUpgrade ? ` (upgrade: ${upgradeUrl2})` : ""}`;
18251
+ finalDescription = config.description;
17556
18252
  finalSchema = config.inputSchema ? applyParamDescriptions(config.inputSchema) : void 0;
17557
18253
  }
17558
18254
  const labeledConfig = {
17559
18255
  ...config,
17560
- title: COMPACT_SCHEMA_ENABLED ? config.title : `${config.title} (${accessLabel})`,
18256
+ title: config.title,
17561
18257
  description: finalDescription
17562
18258
  };
17563
18259
  const annotatedConfig = {
@@ -20935,7 +21631,8 @@ This saves ~80% tokens compared to including full chat history.`,
20935
21631
  context_threshold: external_exports.number().optional().describe("Custom context window threshold (defaults to 70k)"),
20936
21632
  save_exchange: external_exports.boolean().optional().describe("Save this exchange to the transcript for later search (background task)"),
20937
21633
  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')")
21634
+ client_name: external_exports.string().optional().describe("Client name for transcript metadata (e.g., 'claude', 'cursor')"),
21635
+ assistant_message: external_exports.string().optional().describe("Previous assistant response to save along with user message (for complete exchange capture)")
20939
21636
  })
20940
21637
  },
20941
21638
  async (input) => {
@@ -21031,7 +21728,8 @@ This saves ~80% tokens compared to including full chat history.`,
21031
21728
  context_threshold: contextThreshold,
21032
21729
  save_exchange: input.save_exchange,
21033
21730
  session_id: sessionId,
21034
- client_name: clientName
21731
+ client_name: clientName,
21732
+ assistant_message: input.assistant_message
21035
21733
  });
21036
21734
  if (sessionManager && result.token_estimate) {
21037
21735
  sessionManager.addTokens(result.token_estimate);
@@ -27117,7 +27815,7 @@ var SessionManager = class _SessionManager {
27117
27815
  buildContextSummary(context) {
27118
27816
  const parts = [];
27119
27817
  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)");
27818
+ parts.push("\u2B21 ContextStream \u2014 Smart Context & Memory");
27121
27819
  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
27820
  if (context.status === "requires_workspace_name") {
27123
27821
  parts.push("");
@@ -27345,6 +28043,7 @@ import { createServer } from "node:http";
27345
28043
  import { randomUUID as randomUUID3 } from "node:crypto";
27346
28044
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
27347
28045
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
28046
+ init_version();
27348
28047
  var HOST = process.env.MCP_HTTP_HOST || "0.0.0.0";
27349
28048
  var PORT = Number.parseInt(process.env.MCP_HTTP_PORT || "8787", 10);
27350
28049
  var MCP_PATH = process.env.MCP_HTTP_PATH || "/mcp";
@@ -27623,6 +28322,7 @@ async function runHttpGateway() {
27623
28322
  }
27624
28323
 
27625
28324
  // src/index.ts
28325
+ init_version();
27626
28326
  import { existsSync as existsSync17, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
27627
28327
  import { homedir as homedir19 } from "os";
27628
28328
  import { join as join23 } from "path";
@@ -27633,6 +28333,7 @@ import * as path8 from "node:path";
27633
28333
  import { homedir as homedir5 } from "node:os";
27634
28334
  import { stdin, stdout } from "node:process";
27635
28335
  import { createInterface } from "node:readline/promises";
28336
+ init_version();
27636
28337
 
27637
28338
  // src/credentials.ts
27638
28339
  import * as fs6 from "node:fs/promises";
@@ -27783,7 +28484,8 @@ var CONTEXTSTREAM_PREAMBLE_PATTERNS2 = [
27783
28484
  /^#\s+cline rules$/i,
27784
28485
  /^#\s+kilo code rules$/i,
27785
28486
  /^#\s+roo code rules$/i,
27786
- /^#\s+aider configuration$/i
28487
+ /^#\s+aider configuration$/i,
28488
+ /^#\s+antigravity agent rules$/i
27787
28489
  ];
27788
28490
  function wrapWithMarkers2(content) {
27789
28491
  return `${CONTEXTSTREAM_START_MARKER2}
@@ -28191,6 +28893,9 @@ function claudeDesktopConfigPath() {
28191
28893
  const appData = process.env.APPDATA || path8.join(home, "AppData", "Roaming");
28192
28894
  return path8.join(appData, "Claude", "claude_desktop_config.json");
28193
28895
  }
28896
+ if (process.platform === "linux") {
28897
+ return path8.join(home, ".config", "Claude", "claude_desktop_config.json");
28898
+ }
28194
28899
  return null;
28195
28900
  }
28196
28901
  async function upsertCodexTomlConfig(filePath, params) {
@@ -28576,6 +29281,20 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
28576
29281
  const contextPackEnabled = isPro;
28577
29282
  const showTiming = false;
28578
29283
  const restoreContextEnabled = true;
29284
+ console.log("\nAuto-Update:");
29285
+ console.log(" When enabled, ContextStream will automatically update to the latest version");
29286
+ console.log(" on new sessions (checks daily). You can disable this if you prefer manual updates.");
29287
+ const currentAutoUpdate = isAutoUpdateEnabled();
29288
+ const autoUpdateChoice = normalizeInput(
29289
+ await rl.question(`Enable auto-update? [${currentAutoUpdate ? "Y/n" : "y/N"}]: `)
29290
+ ).toLowerCase();
29291
+ const autoUpdateEnabled = autoUpdateChoice === "" ? currentAutoUpdate : autoUpdateChoice === "y" || autoUpdateChoice === "yes";
29292
+ setAutoUpdatePreference(autoUpdateEnabled);
29293
+ if (autoUpdateEnabled) {
29294
+ console.log(" \u2713 Auto-update enabled (disable anytime with CONTEXTSTREAM_AUTO_UPDATE=false)");
29295
+ } else {
29296
+ console.log(" \u2717 Auto-update disabled");
29297
+ }
28579
29298
  const editors = [
28580
29299
  "codex",
28581
29300
  "claude",