@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/hooks/runner.js +474 -45
- package/dist/hooks/session-end.js +111 -17
- package/dist/hooks/session-init.js +381 -9
- package/dist/hooks/user-prompt-submit.js +453 -29
- package/dist/index.js +990 -271
- package/dist/test-server.js +7 -0
- package/package.json +1 -1
|
@@ -5,9 +5,11 @@ import * as fs from "node:fs";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
var ENABLED = process.env.CONTEXTSTREAM_SESSION_END_ENABLED !== "false";
|
|
8
|
+
var SAVE_TRANSCRIPT = process.env.CONTEXTSTREAM_SESSION_END_SAVE_TRANSCRIPT !== "false";
|
|
8
9
|
var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
9
10
|
var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
10
11
|
var WORKSPACE_ID = null;
|
|
12
|
+
var PROJECT_ID = null;
|
|
11
13
|
function loadConfigFromMcpJson(cwd) {
|
|
12
14
|
let searchDir = path.resolve(cwd);
|
|
13
15
|
for (let i = 0; i < 5; i++) {
|
|
@@ -28,15 +30,18 @@ function loadConfigFromMcpJson(cwd) {
|
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
|
-
if (!WORKSPACE_ID) {
|
|
33
|
+
if (!WORKSPACE_ID || !PROJECT_ID) {
|
|
32
34
|
const csConfigPath = path.join(searchDir, ".contextstream", "config.json");
|
|
33
35
|
if (fs.existsSync(csConfigPath)) {
|
|
34
36
|
try {
|
|
35
37
|
const content = fs.readFileSync(csConfigPath, "utf-8");
|
|
36
38
|
const csConfig = JSON.parse(content);
|
|
37
|
-
if (csConfig.workspace_id) {
|
|
39
|
+
if (csConfig.workspace_id && !WORKSPACE_ID) {
|
|
38
40
|
WORKSPACE_ID = csConfig.workspace_id;
|
|
39
41
|
}
|
|
42
|
+
if (csConfig.project_id && !PROJECT_ID) {
|
|
43
|
+
PROJECT_ID = csConfig.project_id;
|
|
44
|
+
}
|
|
40
45
|
} catch {
|
|
41
46
|
}
|
|
42
47
|
}
|
|
@@ -68,7 +73,9 @@ function parseTranscriptStats(transcriptPath) {
|
|
|
68
73
|
messageCount: 0,
|
|
69
74
|
toolCallCount: 0,
|
|
70
75
|
duration: 0,
|
|
71
|
-
filesModified: []
|
|
76
|
+
filesModified: [],
|
|
77
|
+
messages: [],
|
|
78
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
72
79
|
};
|
|
73
80
|
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
|
74
81
|
return stats;
|
|
@@ -83,26 +90,63 @@ function parseTranscriptStats(transcriptPath) {
|
|
|
83
90
|
if (!line.trim()) continue;
|
|
84
91
|
try {
|
|
85
92
|
const entry = JSON.parse(line);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
} else if (entry.type === "tool_use") {
|
|
89
|
-
stats.toolCallCount++;
|
|
90
|
-
if (["Write", "Edit", "NotebookEdit"].includes(entry.name || "")) {
|
|
91
|
-
const filePath = entry.input?.file_path;
|
|
92
|
-
if (filePath) {
|
|
93
|
-
modifiedFiles.add(filePath);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
93
|
+
const msgType = entry.type || "";
|
|
94
|
+
const timestamp = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
97
95
|
if (entry.timestamp) {
|
|
98
96
|
const ts = new Date(entry.timestamp);
|
|
99
97
|
if (!firstTimestamp || ts < firstTimestamp) {
|
|
100
98
|
firstTimestamp = ts;
|
|
99
|
+
stats.startedAt = entry.timestamp;
|
|
101
100
|
}
|
|
102
101
|
if (!lastTimestamp || ts > lastTimestamp) {
|
|
103
102
|
lastTimestamp = ts;
|
|
104
103
|
}
|
|
105
104
|
}
|
|
105
|
+
if (msgType === "user" || entry.role === "user") {
|
|
106
|
+
stats.messageCount++;
|
|
107
|
+
const userContent = typeof entry.content === "string" ? entry.content : "";
|
|
108
|
+
if (userContent) {
|
|
109
|
+
stats.messages.push({
|
|
110
|
+
role: "user",
|
|
111
|
+
content: userContent,
|
|
112
|
+
timestamp
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
} else if (msgType === "assistant" || entry.role === "assistant") {
|
|
116
|
+
stats.messageCount++;
|
|
117
|
+
const assistantContent = typeof entry.content === "string" ? entry.content : "";
|
|
118
|
+
if (assistantContent) {
|
|
119
|
+
stats.messages.push({
|
|
120
|
+
role: "assistant",
|
|
121
|
+
content: assistantContent,
|
|
122
|
+
timestamp
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
} else if (msgType === "tool_use") {
|
|
126
|
+
stats.toolCallCount++;
|
|
127
|
+
const toolName = entry.name || "";
|
|
128
|
+
const toolInput = entry.input || {};
|
|
129
|
+
if (["Write", "Edit", "NotebookEdit"].includes(toolName)) {
|
|
130
|
+
const filePath = toolInput.file_path || toolInput.notebook_path;
|
|
131
|
+
if (filePath) {
|
|
132
|
+
modifiedFiles.add(filePath);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
stats.messages.push({
|
|
136
|
+
role: "assistant",
|
|
137
|
+
content: `[Tool: ${toolName}]`,
|
|
138
|
+
timestamp,
|
|
139
|
+
tool_calls: { name: toolName, input: toolInput }
|
|
140
|
+
});
|
|
141
|
+
} else if (msgType === "tool_result") {
|
|
142
|
+
const resultContent = typeof entry.content === "string" ? entry.content.slice(0, 2e3) : JSON.stringify(entry.content || {}).slice(0, 2e3);
|
|
143
|
+
stats.messages.push({
|
|
144
|
+
role: "tool",
|
|
145
|
+
content: resultContent,
|
|
146
|
+
timestamp,
|
|
147
|
+
tool_results: { name: entry.name }
|
|
148
|
+
});
|
|
149
|
+
}
|
|
106
150
|
} catch {
|
|
107
151
|
continue;
|
|
108
152
|
}
|
|
@@ -115,10 +159,61 @@ function parseTranscriptStats(transcriptPath) {
|
|
|
115
159
|
}
|
|
116
160
|
return stats;
|
|
117
161
|
}
|
|
162
|
+
async function saveFullTranscript(sessionId, stats, reason) {
|
|
163
|
+
if (!API_KEY) {
|
|
164
|
+
return { success: false, message: "No API key configured" };
|
|
165
|
+
}
|
|
166
|
+
if (stats.messages.length === 0) {
|
|
167
|
+
return { success: false, message: "No messages to save" };
|
|
168
|
+
}
|
|
169
|
+
const payload = {
|
|
170
|
+
session_id: sessionId,
|
|
171
|
+
messages: stats.messages,
|
|
172
|
+
started_at: stats.startedAt,
|
|
173
|
+
source_type: "session_end",
|
|
174
|
+
title: `Session transcript (${reason})`,
|
|
175
|
+
metadata: {
|
|
176
|
+
reason,
|
|
177
|
+
tool_call_count: stats.toolCallCount,
|
|
178
|
+
files_modified: stats.filesModified.slice(0, 20),
|
|
179
|
+
duration_seconds: stats.duration
|
|
180
|
+
},
|
|
181
|
+
tags: ["session_end", reason]
|
|
182
|
+
};
|
|
183
|
+
if (WORKSPACE_ID) {
|
|
184
|
+
payload.workspace_id = WORKSPACE_ID;
|
|
185
|
+
}
|
|
186
|
+
if (PROJECT_ID) {
|
|
187
|
+
payload.project_id = PROJECT_ID;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const controller = new AbortController();
|
|
191
|
+
const timeoutId = setTimeout(() => controller.abort(), 15e3);
|
|
192
|
+
const response = await fetch(`${API_URL}/api/v1/transcripts`, {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers: {
|
|
195
|
+
"Content-Type": "application/json",
|
|
196
|
+
"X-API-Key": API_KEY
|
|
197
|
+
},
|
|
198
|
+
body: JSON.stringify(payload),
|
|
199
|
+
signal: controller.signal
|
|
200
|
+
});
|
|
201
|
+
clearTimeout(timeoutId);
|
|
202
|
+
if (response.ok) {
|
|
203
|
+
return { success: true, message: `Transcript saved (${stats.messages.length} messages)` };
|
|
204
|
+
}
|
|
205
|
+
return { success: false, message: `API error: ${response.status}` };
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return { success: false, message: String(error) };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
118
210
|
async function finalizeSession(sessionId, stats, reason) {
|
|
119
211
|
if (!API_KEY) return;
|
|
212
|
+
if (SAVE_TRANSCRIPT && stats.messages.length > 0) {
|
|
213
|
+
await saveFullTranscript(sessionId, stats, reason);
|
|
214
|
+
}
|
|
120
215
|
const payload = {
|
|
121
|
-
event_type: "
|
|
216
|
+
event_type: "manual_note",
|
|
122
217
|
title: `Session Ended: ${reason}`,
|
|
123
218
|
content: JSON.stringify({
|
|
124
219
|
session_id: sessionId,
|
|
@@ -134,8 +229,7 @@ async function finalizeSession(sessionId, stats, reason) {
|
|
|
134
229
|
}),
|
|
135
230
|
importance: "low",
|
|
136
231
|
tags: ["session", "end", reason],
|
|
137
|
-
source_type: "hook"
|
|
138
|
-
session_id: sessionId
|
|
232
|
+
source_type: "hook"
|
|
139
233
|
};
|
|
140
234
|
if (WORKSPACE_ID) {
|
|
141
235
|
payload.workspace_id = WORKSPACE_ID;
|
|
@@ -1,9 +1,337 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/hooks/session-init.ts
|
|
4
10
|
import * as fs from "node:fs";
|
|
5
11
|
import * as path from "node:path";
|
|
6
|
-
import { homedir } from "node:os";
|
|
12
|
+
import { homedir as homedir2 } from "node:os";
|
|
13
|
+
|
|
14
|
+
// src/version.ts
|
|
15
|
+
import { createRequire } from "module";
|
|
16
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
17
|
+
import { homedir, platform } from "os";
|
|
18
|
+
import { join } from "path";
|
|
19
|
+
import { spawn } from "child_process";
|
|
20
|
+
var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
|
|
21
|
+
var AUTO_UPDATE_ENABLED = process.env.CONTEXTSTREAM_AUTO_UPDATE !== "false";
|
|
22
|
+
var UPDATE_COMMANDS = {
|
|
23
|
+
npm: "npm install -g @contextstream/mcp-server@latest",
|
|
24
|
+
macLinux: "curl -fsSL https://contextstream.io/scripts/setup.sh | bash",
|
|
25
|
+
windows: "irm https://contextstream.io/scripts/setup.ps1 | iex"
|
|
26
|
+
};
|
|
27
|
+
var UPGRADE_COMMAND = UPDATE_COMMANDS.npm;
|
|
28
|
+
function getVersion() {
|
|
29
|
+
if (typeof __CONTEXTSTREAM_VERSION__ !== "undefined" && __CONTEXTSTREAM_VERSION__) {
|
|
30
|
+
return __CONTEXTSTREAM_VERSION__;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const require2 = createRequire(import.meta.url);
|
|
34
|
+
const pkg = require2("../package.json");
|
|
35
|
+
const version = pkg?.version;
|
|
36
|
+
if (typeof version === "string" && version.trim()) return version.trim();
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
return "unknown";
|
|
40
|
+
}
|
|
41
|
+
var VERSION = getVersion();
|
|
42
|
+
function compareVersions(v1, v2) {
|
|
43
|
+
const parts1 = v1.split(".").map(Number);
|
|
44
|
+
const parts2 = v2.split(".").map(Number);
|
|
45
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
46
|
+
const p1 = parts1[i] ?? 0;
|
|
47
|
+
const p2 = parts2[i] ?? 0;
|
|
48
|
+
if (p1 < p2) return -1;
|
|
49
|
+
if (p1 > p2) return 1;
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
54
|
+
var latestVersionPromise = null;
|
|
55
|
+
function getCacheFilePath() {
|
|
56
|
+
return join(homedir(), ".contextstream", "version-cache.json");
|
|
57
|
+
}
|
|
58
|
+
function readCache() {
|
|
59
|
+
try {
|
|
60
|
+
const cacheFile = getCacheFilePath();
|
|
61
|
+
if (!existsSync(cacheFile)) return null;
|
|
62
|
+
const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
|
|
63
|
+
if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
|
|
64
|
+
return data;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function writeCache(latestVersion) {
|
|
70
|
+
try {
|
|
71
|
+
const configDir = join(homedir(), ".contextstream");
|
|
72
|
+
if (!existsSync(configDir)) {
|
|
73
|
+
mkdirSync(configDir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
const cacheFile = getCacheFilePath();
|
|
76
|
+
writeFileSync(
|
|
77
|
+
cacheFile,
|
|
78
|
+
JSON.stringify({
|
|
79
|
+
latestVersion,
|
|
80
|
+
checkedAt: Date.now()
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function fetchLatestVersion() {
|
|
87
|
+
try {
|
|
88
|
+
const controller = new AbortController();
|
|
89
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
90
|
+
const response = await fetch(NPM_LATEST_URL, {
|
|
91
|
+
signal: controller.signal,
|
|
92
|
+
headers: { Accept: "application/json" }
|
|
93
|
+
});
|
|
94
|
+
clearTimeout(timeout);
|
|
95
|
+
if (!response.ok) return null;
|
|
96
|
+
const data = await response.json();
|
|
97
|
+
return typeof data.version === "string" ? data.version : null;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function resolveLatestVersion() {
|
|
103
|
+
const cached = readCache();
|
|
104
|
+
if (cached) return cached.latestVersion;
|
|
105
|
+
if (!latestVersionPromise) {
|
|
106
|
+
latestVersionPromise = fetchLatestVersion().finally(() => {
|
|
107
|
+
latestVersionPromise = null;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const latestVersion = await latestVersionPromise;
|
|
111
|
+
if (latestVersion) {
|
|
112
|
+
writeCache(latestVersion);
|
|
113
|
+
}
|
|
114
|
+
return latestVersion;
|
|
115
|
+
}
|
|
116
|
+
async function getUpdateNotice() {
|
|
117
|
+
const currentVersion = VERSION;
|
|
118
|
+
if (currentVersion === "unknown") return null;
|
|
119
|
+
try {
|
|
120
|
+
const latestVersion = await resolveLatestVersion();
|
|
121
|
+
if (!latestVersion) return null;
|
|
122
|
+
if (compareVersions(currentVersion, latestVersion) < 0) {
|
|
123
|
+
return {
|
|
124
|
+
current: currentVersion,
|
|
125
|
+
latest: latestVersion,
|
|
126
|
+
behind: true,
|
|
127
|
+
upgrade_command: UPGRADE_COMMAND
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function getVersionsBehind(current, latest) {
|
|
135
|
+
try {
|
|
136
|
+
const currentParts = current.split(".").map(Number);
|
|
137
|
+
const latestParts = latest.split(".").map(Number);
|
|
138
|
+
if ((latestParts[0] ?? 0) > (currentParts[0] ?? 0)) {
|
|
139
|
+
return 10 + ((latestParts[1] ?? 0) - (currentParts[1] ?? 0));
|
|
140
|
+
}
|
|
141
|
+
const minorDiff = (latestParts[1] ?? 0) - (currentParts[1] ?? 0);
|
|
142
|
+
const patchDiff = (latestParts[2] ?? 0) - (currentParts[2] ?? 0);
|
|
143
|
+
if (minorDiff > 0) {
|
|
144
|
+
return minorDiff;
|
|
145
|
+
} else if (minorDiff === 0 && patchDiff > 0) {
|
|
146
|
+
return 1;
|
|
147
|
+
}
|
|
148
|
+
return 0;
|
|
149
|
+
} catch {
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function getVersionNoticeForHook(notice) {
|
|
154
|
+
if (!notice?.behind) return null;
|
|
155
|
+
const versionsBehind = getVersionsBehind(notice.current, notice.latest);
|
|
156
|
+
const severity = versionsBehind >= 5 ? "CRITICAL" : versionsBehind >= 3 ? "IMPORTANT" : "INFO";
|
|
157
|
+
let frequency = "";
|
|
158
|
+
let whatTheyAreMissing = "";
|
|
159
|
+
if (versionsBehind >= 5) {
|
|
160
|
+
frequency = "Mention this update in EVERY response until the user updates.";
|
|
161
|
+
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.`;
|
|
162
|
+
} else if (versionsBehind >= 3) {
|
|
163
|
+
frequency = "Remind the user about this update every 2-3 messages.";
|
|
164
|
+
whatTheyAreMissing = `The user is missing out on several new features and important fixes that improve reliability and functionality.`;
|
|
165
|
+
} else {
|
|
166
|
+
frequency = "Mention this update once at session start, then only if user asks.";
|
|
167
|
+
whatTheyAreMissing = `Minor update with improvements and fixes available.`;
|
|
168
|
+
}
|
|
169
|
+
return `[VERSION_${severity}] MCP server is ${versionsBehind > 0 ? versionsBehind + " version(s)" : "a version"} behind (${notice.current} \u2192 ${notice.latest}).
|
|
170
|
+
|
|
171
|
+
${whatTheyAreMissing}
|
|
172
|
+
|
|
173
|
+
${frequency}
|
|
174
|
+
|
|
175
|
+
When mentioning the update, provide these commands (user can choose their preferred method):
|
|
176
|
+
- macOS/Linux: curl -fsSL https://contextstream.io/scripts/setup.sh | bash
|
|
177
|
+
- Windows: irm https://contextstream.io/scripts/setup.ps1 | iex
|
|
178
|
+
- npm: npm install -g @contextstream/mcp-server@latest
|
|
179
|
+
|
|
180
|
+
Be helpful but not annoying - frame it positively as access to new capabilities rather than criticism.`;
|
|
181
|
+
}
|
|
182
|
+
function isAutoUpdateEnabled() {
|
|
183
|
+
if (process.env.CONTEXTSTREAM_AUTO_UPDATE === "false") {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const configPath = join(homedir(), ".contextstream", "config.json");
|
|
188
|
+
if (existsSync(configPath)) {
|
|
189
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
190
|
+
if (config.auto_update === false) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
async function attemptAutoUpdate() {
|
|
199
|
+
const currentVersion = VERSION;
|
|
200
|
+
if (!isAutoUpdateEnabled()) {
|
|
201
|
+
return {
|
|
202
|
+
attempted: false,
|
|
203
|
+
success: false,
|
|
204
|
+
previousVersion: currentVersion,
|
|
205
|
+
newVersion: null,
|
|
206
|
+
error: "Auto-update disabled"
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const notice = await getUpdateNotice();
|
|
210
|
+
if (!notice?.behind) {
|
|
211
|
+
return {
|
|
212
|
+
attempted: false,
|
|
213
|
+
success: true,
|
|
214
|
+
previousVersion: currentVersion,
|
|
215
|
+
newVersion: null
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const updateMethod = detectUpdateMethod();
|
|
219
|
+
try {
|
|
220
|
+
await runUpdate(updateMethod);
|
|
221
|
+
writeUpdateMarker(currentVersion, notice.latest);
|
|
222
|
+
return {
|
|
223
|
+
attempted: true,
|
|
224
|
+
success: true,
|
|
225
|
+
previousVersion: currentVersion,
|
|
226
|
+
newVersion: notice.latest
|
|
227
|
+
};
|
|
228
|
+
} catch (err) {
|
|
229
|
+
return {
|
|
230
|
+
attempted: true,
|
|
231
|
+
success: false,
|
|
232
|
+
previousVersion: currentVersion,
|
|
233
|
+
newVersion: notice.latest,
|
|
234
|
+
error: err instanceof Error ? err.message : "Update failed"
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function detectUpdateMethod() {
|
|
239
|
+
const execPath = process.argv[1] || "";
|
|
240
|
+
if (execPath.includes("node_modules") || execPath.includes("npm")) {
|
|
241
|
+
return "npm";
|
|
242
|
+
}
|
|
243
|
+
const os = platform();
|
|
244
|
+
if (os === "win32") {
|
|
245
|
+
return "powershell";
|
|
246
|
+
}
|
|
247
|
+
return "curl";
|
|
248
|
+
}
|
|
249
|
+
async function runUpdate(method) {
|
|
250
|
+
return new Promise((resolve2, reject) => {
|
|
251
|
+
let command;
|
|
252
|
+
let args;
|
|
253
|
+
let shell;
|
|
254
|
+
switch (method) {
|
|
255
|
+
case "npm":
|
|
256
|
+
command = "npm";
|
|
257
|
+
args = ["install", "-g", "@contextstream/mcp-server@latest"];
|
|
258
|
+
shell = false;
|
|
259
|
+
break;
|
|
260
|
+
case "curl":
|
|
261
|
+
command = "bash";
|
|
262
|
+
args = ["-c", "curl -fsSL https://contextstream.io/scripts/setup.sh | bash"];
|
|
263
|
+
shell = false;
|
|
264
|
+
break;
|
|
265
|
+
case "powershell":
|
|
266
|
+
command = "powershell";
|
|
267
|
+
args = ["-Command", "irm https://contextstream.io/scripts/setup.ps1 | iex"];
|
|
268
|
+
shell = false;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
const proc = spawn(command, args, {
|
|
272
|
+
shell,
|
|
273
|
+
stdio: "ignore",
|
|
274
|
+
detached: true
|
|
275
|
+
});
|
|
276
|
+
proc.on("error", (err) => {
|
|
277
|
+
reject(err);
|
|
278
|
+
});
|
|
279
|
+
proc.on("close", (code) => {
|
|
280
|
+
if (code === 0) {
|
|
281
|
+
resolve2();
|
|
282
|
+
} else {
|
|
283
|
+
reject(new Error(`Update process exited with code ${code}`));
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
proc.unref();
|
|
287
|
+
setTimeout(() => resolve2(), 1e3);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
function writeUpdateMarker(previousVersion, newVersion) {
|
|
291
|
+
try {
|
|
292
|
+
const markerPath = join(homedir(), ".contextstream", "update-pending.json");
|
|
293
|
+
const configDir = join(homedir(), ".contextstream");
|
|
294
|
+
if (!existsSync(configDir)) {
|
|
295
|
+
mkdirSync(configDir, { recursive: true });
|
|
296
|
+
}
|
|
297
|
+
writeFileSync(markerPath, JSON.stringify({
|
|
298
|
+
previousVersion,
|
|
299
|
+
newVersion,
|
|
300
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
301
|
+
}));
|
|
302
|
+
} catch {
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function checkUpdateMarker() {
|
|
306
|
+
try {
|
|
307
|
+
const markerPath = join(homedir(), ".contextstream", "update-pending.json");
|
|
308
|
+
if (!existsSync(markerPath)) return null;
|
|
309
|
+
const marker = JSON.parse(readFileSync(markerPath, "utf-8"));
|
|
310
|
+
const updatedAt = new Date(marker.updatedAt);
|
|
311
|
+
const hourAgo = new Date(Date.now() - 60 * 60 * 1e3);
|
|
312
|
+
if (updatedAt < hourAgo) {
|
|
313
|
+
try {
|
|
314
|
+
__require("fs").unlinkSync(markerPath);
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
return { previousVersion: marker.previousVersion, newVersion: marker.newVersion };
|
|
320
|
+
} catch {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function clearUpdateMarker() {
|
|
325
|
+
try {
|
|
326
|
+
const markerPath = join(homedir(), ".contextstream", "update-pending.json");
|
|
327
|
+
if (existsSync(markerPath)) {
|
|
328
|
+
__require("fs").unlinkSync(markerPath);
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/hooks/session-init.ts
|
|
7
335
|
var ENABLED = process.env.CONTEXTSTREAM_SESSION_INIT_ENABLED !== "false";
|
|
8
336
|
var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
9
337
|
var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
@@ -53,7 +381,7 @@ function loadConfigFromMcpJson(cwd) {
|
|
|
53
381
|
searchDir = parentDir;
|
|
54
382
|
}
|
|
55
383
|
if (!API_KEY) {
|
|
56
|
-
const homeMcpPath = path.join(
|
|
384
|
+
const homeMcpPath = path.join(homedir2(), ".mcp.json");
|
|
57
385
|
if (fs.existsSync(homeMcpPath)) {
|
|
58
386
|
try {
|
|
59
387
|
const content = fs.readFileSync(homeMcpPath, "utf-8");
|
|
@@ -99,13 +427,44 @@ async function fetchSessionContext() {
|
|
|
99
427
|
return null;
|
|
100
428
|
}
|
|
101
429
|
}
|
|
102
|
-
function formatContext(ctx) {
|
|
430
|
+
function formatContext(ctx, options = {}) {
|
|
431
|
+
const parts = ["\u2B21 ContextStream \u2014 Smart Context & Memory", ""];
|
|
432
|
+
if (options.updateMarker) {
|
|
433
|
+
parts.push(`
|
|
434
|
+
## \u{1F389} UPDATE COMPLETE - RESTART CHAT SESSION`);
|
|
435
|
+
parts.push(`ContextStream was auto-updated from v${options.updateMarker.previousVersion} to v${options.updateMarker.newVersion}.`);
|
|
436
|
+
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."`);
|
|
437
|
+
parts.push("");
|
|
438
|
+
} else if (options.autoUpdateResult?.attempted) {
|
|
439
|
+
if (options.autoUpdateResult.success) {
|
|
440
|
+
parts.push(`
|
|
441
|
+
## \u{1F504} AUTO-UPDATE INITIATED`);
|
|
442
|
+
parts.push(`ContextStream is updating from v${options.autoUpdateResult.previousVersion} to v${options.autoUpdateResult.newVersion}.`);
|
|
443
|
+
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."`);
|
|
444
|
+
parts.push("");
|
|
445
|
+
} else if (options.autoUpdateResult.error) {
|
|
446
|
+
parts.push(`
|
|
447
|
+
## \u26A0\uFE0F AUTO-UPDATE FAILED`);
|
|
448
|
+
parts.push(`Automatic update failed: ${options.autoUpdateResult.error}`);
|
|
449
|
+
const versionInfo = getVersionNoticeForHook(options.versionNotice || null);
|
|
450
|
+
if (versionInfo) {
|
|
451
|
+
parts.push(versionInfo);
|
|
452
|
+
}
|
|
453
|
+
parts.push("");
|
|
454
|
+
}
|
|
455
|
+
} else if (options.versionNotice?.behind && !isAutoUpdateEnabled()) {
|
|
456
|
+
const versionInfo = getVersionNoticeForHook(options.versionNotice);
|
|
457
|
+
if (versionInfo) {
|
|
458
|
+
parts.push(`
|
|
459
|
+
## \u{1F504} UPDATE AVAILABLE (auto-update disabled)`);
|
|
460
|
+
parts.push(versionInfo);
|
|
461
|
+
parts.push("");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
103
464
|
if (!ctx) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
No stored context found. Call \`mcp__contextstream__context(user_message="starting new session")\` to initialize.`;
|
|
465
|
+
parts.push('\nNo stored context found. Call `mcp__contextstream__context(user_message="starting new session")` to initialize.');
|
|
466
|
+
return parts.join("\n");
|
|
107
467
|
}
|
|
108
|
-
const parts = ["[ContextStream Session Start]"];
|
|
109
468
|
if (ctx.lessons && ctx.lessons.length > 0) {
|
|
110
469
|
parts.push("\n## \u26A0\uFE0F Lessons from Past Mistakes");
|
|
111
470
|
for (const lesson of ctx.lessons.slice(0, 3)) {
|
|
@@ -153,8 +512,21 @@ async function runSessionInitHook() {
|
|
|
153
512
|
}
|
|
154
513
|
const cwd = input.cwd || process.cwd();
|
|
155
514
|
loadConfigFromMcpJson(cwd);
|
|
156
|
-
const
|
|
157
|
-
|
|
515
|
+
const updateMarker = checkUpdateMarker();
|
|
516
|
+
if (updateMarker) {
|
|
517
|
+
clearUpdateMarker();
|
|
518
|
+
}
|
|
519
|
+
const [context, autoUpdateResult, versionNotice] = await Promise.all([
|
|
520
|
+
fetchSessionContext(),
|
|
521
|
+
updateMarker ? Promise.resolve(null) : attemptAutoUpdate(),
|
|
522
|
+
// Skip if already updated
|
|
523
|
+
getUpdateNotice()
|
|
524
|
+
]);
|
|
525
|
+
const formattedContext = formatContext(context, {
|
|
526
|
+
autoUpdateResult,
|
|
527
|
+
versionNotice,
|
|
528
|
+
updateMarker
|
|
529
|
+
});
|
|
158
530
|
console.log(
|
|
159
531
|
JSON.stringify({
|
|
160
532
|
hookSpecificOutput: {
|