@contextstream/mcp-server 0.4.36 → 0.4.38
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 +1719 -252
- package/dist/test-server.js +5 -2
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,496 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
10
|
+
}) : x)(function(x) {
|
|
11
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
|
+
});
|
|
14
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
15
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
16
|
+
};
|
|
3
17
|
var __export = (target, all) => {
|
|
4
18
|
for (var name in all)
|
|
5
19
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
20
|
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from))
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
25
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
35
|
+
mod
|
|
36
|
+
));
|
|
37
|
+
|
|
38
|
+
// node_modules/ignore/index.js
|
|
39
|
+
var require_ignore = __commonJS({
|
|
40
|
+
"node_modules/ignore/index.js"(exports, module) {
|
|
41
|
+
function makeArray(subject) {
|
|
42
|
+
return Array.isArray(subject) ? subject : [subject];
|
|
43
|
+
}
|
|
44
|
+
var UNDEFINED = void 0;
|
|
45
|
+
var EMPTY = "";
|
|
46
|
+
var SPACE = " ";
|
|
47
|
+
var ESCAPE = "\\";
|
|
48
|
+
var REGEX_TEST_BLANK_LINE = /^\s+$/;
|
|
49
|
+
var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
|
|
50
|
+
var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
|
|
51
|
+
var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
|
|
52
|
+
var REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
53
|
+
var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
|
|
54
|
+
var REGEX_TEST_TRAILING_SLASH = /\/$/;
|
|
55
|
+
var SLASH = "/";
|
|
56
|
+
var TMP_KEY_IGNORE = "node-ignore";
|
|
57
|
+
if (typeof Symbol !== "undefined") {
|
|
58
|
+
TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
|
|
59
|
+
}
|
|
60
|
+
var KEY_IGNORE = TMP_KEY_IGNORE;
|
|
61
|
+
var define = (object, key, value) => {
|
|
62
|
+
Object.defineProperty(object, key, { value });
|
|
63
|
+
return value;
|
|
64
|
+
};
|
|
65
|
+
var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
|
|
66
|
+
var RETURN_FALSE = () => false;
|
|
67
|
+
var sanitizeRange = (range) => range.replace(
|
|
68
|
+
REGEX_REGEXP_RANGE,
|
|
69
|
+
(match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
|
|
70
|
+
);
|
|
71
|
+
var cleanRangeBackSlash = (slashes) => {
|
|
72
|
+
const { length } = slashes;
|
|
73
|
+
return slashes.slice(0, length - length % 2);
|
|
74
|
+
};
|
|
75
|
+
var REPLACERS = [
|
|
76
|
+
[
|
|
77
|
+
// Remove BOM
|
|
78
|
+
// TODO:
|
|
79
|
+
// Other similar zero-width characters?
|
|
80
|
+
/^\uFEFF/,
|
|
81
|
+
() => EMPTY
|
|
82
|
+
],
|
|
83
|
+
// > Trailing spaces are ignored unless they are quoted with backslash ("\")
|
|
84
|
+
[
|
|
85
|
+
// (a\ ) -> (a )
|
|
86
|
+
// (a ) -> (a)
|
|
87
|
+
// (a ) -> (a)
|
|
88
|
+
// (a \ ) -> (a )
|
|
89
|
+
/((?:\\\\)*?)(\\?\s+)$/,
|
|
90
|
+
(_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
|
|
91
|
+
],
|
|
92
|
+
// Replace (\ ) with ' '
|
|
93
|
+
// (\ ) -> ' '
|
|
94
|
+
// (\\ ) -> '\\ '
|
|
95
|
+
// (\\\ ) -> '\\ '
|
|
96
|
+
[
|
|
97
|
+
/(\\+?)\s/g,
|
|
98
|
+
(_, m1) => {
|
|
99
|
+
const { length } = m1;
|
|
100
|
+
return m1.slice(0, length - length % 2) + SPACE;
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
// Escape metacharacters
|
|
104
|
+
// which is written down by users but means special for regular expressions.
|
|
105
|
+
// > There are 12 characters with special meanings:
|
|
106
|
+
// > - the backslash \,
|
|
107
|
+
// > - the caret ^,
|
|
108
|
+
// > - the dollar sign $,
|
|
109
|
+
// > - the period or dot .,
|
|
110
|
+
// > - the vertical bar or pipe symbol |,
|
|
111
|
+
// > - the question mark ?,
|
|
112
|
+
// > - the asterisk or star *,
|
|
113
|
+
// > - the plus sign +,
|
|
114
|
+
// > - the opening parenthesis (,
|
|
115
|
+
// > - the closing parenthesis ),
|
|
116
|
+
// > - and the opening square bracket [,
|
|
117
|
+
// > - the opening curly brace {,
|
|
118
|
+
// > These special characters are often called "metacharacters".
|
|
119
|
+
[
|
|
120
|
+
/[\\$.|*+(){^]/g,
|
|
121
|
+
(match) => `\\${match}`
|
|
122
|
+
],
|
|
123
|
+
[
|
|
124
|
+
// > a question mark (?) matches a single character
|
|
125
|
+
/(?!\\)\?/g,
|
|
126
|
+
() => "[^/]"
|
|
127
|
+
],
|
|
128
|
+
// leading slash
|
|
129
|
+
[
|
|
130
|
+
// > A leading slash matches the beginning of the pathname.
|
|
131
|
+
// > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
|
|
132
|
+
// A leading slash matches the beginning of the pathname
|
|
133
|
+
/^\//,
|
|
134
|
+
() => "^"
|
|
135
|
+
],
|
|
136
|
+
// replace special metacharacter slash after the leading slash
|
|
137
|
+
[
|
|
138
|
+
/\//g,
|
|
139
|
+
() => "\\/"
|
|
140
|
+
],
|
|
141
|
+
[
|
|
142
|
+
// > A leading "**" followed by a slash means match in all directories.
|
|
143
|
+
// > For example, "**/foo" matches file or directory "foo" anywhere,
|
|
144
|
+
// > the same as pattern "foo".
|
|
145
|
+
// > "**/foo/bar" matches file or directory "bar" anywhere that is directly
|
|
146
|
+
// > under directory "foo".
|
|
147
|
+
// Notice that the '*'s have been replaced as '\\*'
|
|
148
|
+
/^\^*\\\*\\\*\\\//,
|
|
149
|
+
// '**/foo' <-> 'foo'
|
|
150
|
+
() => "^(?:.*\\/)?"
|
|
151
|
+
],
|
|
152
|
+
// starting
|
|
153
|
+
[
|
|
154
|
+
// there will be no leading '/'
|
|
155
|
+
// (which has been replaced by section "leading slash")
|
|
156
|
+
// If starts with '**', adding a '^' to the regular expression also works
|
|
157
|
+
/^(?=[^^])/,
|
|
158
|
+
function startingReplacer() {
|
|
159
|
+
return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
// two globstars
|
|
163
|
+
[
|
|
164
|
+
// Use lookahead assertions so that we could match more than one `'/**'`
|
|
165
|
+
/\\\/\\\*\\\*(?=\\\/|$)/g,
|
|
166
|
+
// Zero, one or several directories
|
|
167
|
+
// should not use '*', or it will be replaced by the next replacer
|
|
168
|
+
// Check if it is not the last `'/**'`
|
|
169
|
+
(_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
|
|
170
|
+
],
|
|
171
|
+
// normal intermediate wildcards
|
|
172
|
+
[
|
|
173
|
+
// Never replace escaped '*'
|
|
174
|
+
// ignore rule '\*' will match the path '*'
|
|
175
|
+
// 'abc.*/' -> go
|
|
176
|
+
// 'abc.*' -> skip this rule,
|
|
177
|
+
// coz trailing single wildcard will be handed by [trailing wildcard]
|
|
178
|
+
/(^|[^\\]+)(\\\*)+(?=.+)/g,
|
|
179
|
+
// '*.js' matches '.js'
|
|
180
|
+
// '*.js' doesn't match 'abc'
|
|
181
|
+
(_, p1, p2) => {
|
|
182
|
+
const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
|
|
183
|
+
return p1 + unescaped;
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
[
|
|
187
|
+
// unescape, revert step 3 except for back slash
|
|
188
|
+
// For example, if a user escape a '\\*',
|
|
189
|
+
// after step 3, the result will be '\\\\\\*'
|
|
190
|
+
/\\\\\\(?=[$.|*+(){^])/g,
|
|
191
|
+
() => ESCAPE
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
// '\\\\' -> '\\'
|
|
195
|
+
/\\\\/g,
|
|
196
|
+
() => ESCAPE
|
|
197
|
+
],
|
|
198
|
+
[
|
|
199
|
+
// > The range notation, e.g. [a-zA-Z],
|
|
200
|
+
// > can be used to match one of the characters in a range.
|
|
201
|
+
// `\` is escaped by step 3
|
|
202
|
+
/(\\)?\[([^\]/]*?)(\\*)($|\])/g,
|
|
203
|
+
(match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
|
|
204
|
+
],
|
|
205
|
+
// ending
|
|
206
|
+
[
|
|
207
|
+
// 'js' will not match 'js.'
|
|
208
|
+
// 'ab' will not match 'abc'
|
|
209
|
+
/(?:[^*])$/,
|
|
210
|
+
// WTF!
|
|
211
|
+
// https://git-scm.com/docs/gitignore
|
|
212
|
+
// changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
|
|
213
|
+
// which re-fixes #24, #38
|
|
214
|
+
// > If there is a separator at the end of the pattern then the pattern
|
|
215
|
+
// > will only match directories, otherwise the pattern can match both
|
|
216
|
+
// > files and directories.
|
|
217
|
+
// 'js*' will not match 'a.js'
|
|
218
|
+
// 'js/' will not match 'a.js'
|
|
219
|
+
// 'js' will match 'a.js' and 'a.js/'
|
|
220
|
+
(match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
|
|
221
|
+
]
|
|
222
|
+
];
|
|
223
|
+
var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
|
|
224
|
+
var MODE_IGNORE = "regex";
|
|
225
|
+
var MODE_CHECK_IGNORE = "checkRegex";
|
|
226
|
+
var UNDERSCORE = "_";
|
|
227
|
+
var TRAILING_WILD_CARD_REPLACERS = {
|
|
228
|
+
[MODE_IGNORE](_, p1) {
|
|
229
|
+
const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
|
|
230
|
+
return `${prefix}(?=$|\\/$)`;
|
|
231
|
+
},
|
|
232
|
+
[MODE_CHECK_IGNORE](_, p1) {
|
|
233
|
+
const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
|
|
234
|
+
return `${prefix}(?=$|\\/$)`;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
var makeRegexPrefix = (pattern) => REPLACERS.reduce(
|
|
238
|
+
(prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
|
|
239
|
+
pattern
|
|
240
|
+
);
|
|
241
|
+
var isString = (subject) => typeof subject === "string";
|
|
242
|
+
var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
|
|
243
|
+
var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
|
|
244
|
+
var IgnoreRule = class {
|
|
245
|
+
constructor(pattern, mark, body, ignoreCase, negative, prefix) {
|
|
246
|
+
this.pattern = pattern;
|
|
247
|
+
this.mark = mark;
|
|
248
|
+
this.negative = negative;
|
|
249
|
+
define(this, "body", body);
|
|
250
|
+
define(this, "ignoreCase", ignoreCase);
|
|
251
|
+
define(this, "regexPrefix", prefix);
|
|
252
|
+
}
|
|
253
|
+
get regex() {
|
|
254
|
+
const key = UNDERSCORE + MODE_IGNORE;
|
|
255
|
+
if (this[key]) {
|
|
256
|
+
return this[key];
|
|
257
|
+
}
|
|
258
|
+
return this._make(MODE_IGNORE, key);
|
|
259
|
+
}
|
|
260
|
+
get checkRegex() {
|
|
261
|
+
const key = UNDERSCORE + MODE_CHECK_IGNORE;
|
|
262
|
+
if (this[key]) {
|
|
263
|
+
return this[key];
|
|
264
|
+
}
|
|
265
|
+
return this._make(MODE_CHECK_IGNORE, key);
|
|
266
|
+
}
|
|
267
|
+
_make(mode, key) {
|
|
268
|
+
const str = this.regexPrefix.replace(
|
|
269
|
+
REGEX_REPLACE_TRAILING_WILDCARD,
|
|
270
|
+
// It does not need to bind pattern
|
|
271
|
+
TRAILING_WILD_CARD_REPLACERS[mode]
|
|
272
|
+
);
|
|
273
|
+
const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
|
|
274
|
+
return define(this, key, regex);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var createRule = ({
|
|
278
|
+
pattern,
|
|
279
|
+
mark
|
|
280
|
+
}, ignoreCase) => {
|
|
281
|
+
let negative = false;
|
|
282
|
+
let body = pattern;
|
|
283
|
+
if (body.indexOf("!") === 0) {
|
|
284
|
+
negative = true;
|
|
285
|
+
body = body.substr(1);
|
|
286
|
+
}
|
|
287
|
+
body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
|
|
288
|
+
const regexPrefix = makeRegexPrefix(body);
|
|
289
|
+
return new IgnoreRule(
|
|
290
|
+
pattern,
|
|
291
|
+
mark,
|
|
292
|
+
body,
|
|
293
|
+
ignoreCase,
|
|
294
|
+
negative,
|
|
295
|
+
regexPrefix
|
|
296
|
+
);
|
|
297
|
+
};
|
|
298
|
+
var RuleManager = class {
|
|
299
|
+
constructor(ignoreCase) {
|
|
300
|
+
this._ignoreCase = ignoreCase;
|
|
301
|
+
this._rules = [];
|
|
302
|
+
}
|
|
303
|
+
_add(pattern) {
|
|
304
|
+
if (pattern && pattern[KEY_IGNORE]) {
|
|
305
|
+
this._rules = this._rules.concat(pattern._rules._rules);
|
|
306
|
+
this._added = true;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (isString(pattern)) {
|
|
310
|
+
pattern = {
|
|
311
|
+
pattern
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
if (checkPattern(pattern.pattern)) {
|
|
315
|
+
const rule = createRule(pattern, this._ignoreCase);
|
|
316
|
+
this._added = true;
|
|
317
|
+
this._rules.push(rule);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// @param {Array<string> | string | Ignore} pattern
|
|
321
|
+
add(pattern) {
|
|
322
|
+
this._added = false;
|
|
323
|
+
makeArray(
|
|
324
|
+
isString(pattern) ? splitPattern(pattern) : pattern
|
|
325
|
+
).forEach(this._add, this);
|
|
326
|
+
return this._added;
|
|
327
|
+
}
|
|
328
|
+
// Test one single path without recursively checking parent directories
|
|
329
|
+
//
|
|
330
|
+
// - checkUnignored `boolean` whether should check if the path is unignored,
|
|
331
|
+
// setting `checkUnignored` to `false` could reduce additional
|
|
332
|
+
// path matching.
|
|
333
|
+
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
334
|
+
// @returns {TestResult} true if a file is ignored
|
|
335
|
+
test(path9, checkUnignored, mode) {
|
|
336
|
+
let ignored = false;
|
|
337
|
+
let unignored = false;
|
|
338
|
+
let matchedRule;
|
|
339
|
+
this._rules.forEach((rule) => {
|
|
340
|
+
const { negative } = rule;
|
|
341
|
+
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const matched = rule[mode].test(path9);
|
|
345
|
+
if (!matched) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
ignored = !negative;
|
|
349
|
+
unignored = negative;
|
|
350
|
+
matchedRule = negative ? UNDEFINED : rule;
|
|
351
|
+
});
|
|
352
|
+
const ret = {
|
|
353
|
+
ignored,
|
|
354
|
+
unignored
|
|
355
|
+
};
|
|
356
|
+
if (matchedRule) {
|
|
357
|
+
ret.rule = matchedRule;
|
|
358
|
+
}
|
|
359
|
+
return ret;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
var throwError = (message, Ctor) => {
|
|
363
|
+
throw new Ctor(message);
|
|
364
|
+
};
|
|
365
|
+
var checkPath = (path9, originalPath, doThrow) => {
|
|
366
|
+
if (!isString(path9)) {
|
|
367
|
+
return doThrow(
|
|
368
|
+
`path must be a string, but got \`${originalPath}\``,
|
|
369
|
+
TypeError
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
if (!path9) {
|
|
373
|
+
return doThrow(`path must not be empty`, TypeError);
|
|
374
|
+
}
|
|
375
|
+
if (checkPath.isNotRelative(path9)) {
|
|
376
|
+
const r = "`path.relative()`d";
|
|
377
|
+
return doThrow(
|
|
378
|
+
`path should be a ${r} string, but got "${originalPath}"`,
|
|
379
|
+
RangeError
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
return true;
|
|
383
|
+
};
|
|
384
|
+
var isNotRelative = (path9) => REGEX_TEST_INVALID_PATH.test(path9);
|
|
385
|
+
checkPath.isNotRelative = isNotRelative;
|
|
386
|
+
checkPath.convert = (p) => p;
|
|
387
|
+
var Ignore2 = class {
|
|
388
|
+
constructor({
|
|
389
|
+
ignorecase = true,
|
|
390
|
+
ignoreCase = ignorecase,
|
|
391
|
+
allowRelativePaths = false
|
|
392
|
+
} = {}) {
|
|
393
|
+
define(this, KEY_IGNORE, true);
|
|
394
|
+
this._rules = new RuleManager(ignoreCase);
|
|
395
|
+
this._strictPathCheck = !allowRelativePaths;
|
|
396
|
+
this._initCache();
|
|
397
|
+
}
|
|
398
|
+
_initCache() {
|
|
399
|
+
this._ignoreCache = /* @__PURE__ */ Object.create(null);
|
|
400
|
+
this._testCache = /* @__PURE__ */ Object.create(null);
|
|
401
|
+
}
|
|
402
|
+
add(pattern) {
|
|
403
|
+
if (this._rules.add(pattern)) {
|
|
404
|
+
this._initCache();
|
|
405
|
+
}
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
408
|
+
// legacy
|
|
409
|
+
addPattern(pattern) {
|
|
410
|
+
return this.add(pattern);
|
|
411
|
+
}
|
|
412
|
+
// @returns {TestResult}
|
|
413
|
+
_test(originalPath, cache, checkUnignored, slices) {
|
|
414
|
+
const path9 = originalPath && checkPath.convert(originalPath);
|
|
415
|
+
checkPath(
|
|
416
|
+
path9,
|
|
417
|
+
originalPath,
|
|
418
|
+
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
419
|
+
);
|
|
420
|
+
return this._t(path9, cache, checkUnignored, slices);
|
|
421
|
+
}
|
|
422
|
+
checkIgnore(path9) {
|
|
423
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path9)) {
|
|
424
|
+
return this.test(path9);
|
|
425
|
+
}
|
|
426
|
+
const slices = path9.split(SLASH).filter(Boolean);
|
|
427
|
+
slices.pop();
|
|
428
|
+
if (slices.length) {
|
|
429
|
+
const parent = this._t(
|
|
430
|
+
slices.join(SLASH) + SLASH,
|
|
431
|
+
this._testCache,
|
|
432
|
+
true,
|
|
433
|
+
slices
|
|
434
|
+
);
|
|
435
|
+
if (parent.ignored) {
|
|
436
|
+
return parent;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return this._rules.test(path9, false, MODE_CHECK_IGNORE);
|
|
440
|
+
}
|
|
441
|
+
_t(path9, cache, checkUnignored, slices) {
|
|
442
|
+
if (path9 in cache) {
|
|
443
|
+
return cache[path9];
|
|
444
|
+
}
|
|
445
|
+
if (!slices) {
|
|
446
|
+
slices = path9.split(SLASH).filter(Boolean);
|
|
447
|
+
}
|
|
448
|
+
slices.pop();
|
|
449
|
+
if (!slices.length) {
|
|
450
|
+
return cache[path9] = this._rules.test(path9, checkUnignored, MODE_IGNORE);
|
|
451
|
+
}
|
|
452
|
+
const parent = this._t(
|
|
453
|
+
slices.join(SLASH) + SLASH,
|
|
454
|
+
cache,
|
|
455
|
+
checkUnignored,
|
|
456
|
+
slices
|
|
457
|
+
);
|
|
458
|
+
return cache[path9] = parent.ignored ? parent : this._rules.test(path9, checkUnignored, MODE_IGNORE);
|
|
459
|
+
}
|
|
460
|
+
ignores(path9) {
|
|
461
|
+
return this._test(path9, this._ignoreCache, false).ignored;
|
|
462
|
+
}
|
|
463
|
+
createFilter() {
|
|
464
|
+
return (path9) => !this.ignores(path9);
|
|
465
|
+
}
|
|
466
|
+
filter(paths) {
|
|
467
|
+
return makeArray(paths).filter(this.createFilter());
|
|
468
|
+
}
|
|
469
|
+
// @returns {TestResult}
|
|
470
|
+
test(path9) {
|
|
471
|
+
return this._test(path9, this._testCache, true);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
var factory = (options) => new Ignore2(options);
|
|
475
|
+
var isPathValid = (path9) => checkPath(path9 && checkPath.convert(path9), path9, RETURN_FALSE);
|
|
476
|
+
var setupWindows = () => {
|
|
477
|
+
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
478
|
+
checkPath.convert = makePosix;
|
|
479
|
+
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
480
|
+
checkPath.isNotRelative = (path9) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path9) || isNotRelative(path9);
|
|
481
|
+
};
|
|
482
|
+
if (
|
|
483
|
+
// Detect `process` so that it can run in browsers.
|
|
484
|
+
typeof process !== "undefined" && process.platform === "win32"
|
|
485
|
+
) {
|
|
486
|
+
setupWindows();
|
|
487
|
+
}
|
|
488
|
+
module.exports = factory;
|
|
489
|
+
factory.default = factory;
|
|
490
|
+
module.exports.isPathValid = isPathValid;
|
|
491
|
+
define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
7
494
|
|
|
8
495
|
// src/index.ts
|
|
9
496
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -487,8 +974,8 @@ function getErrorMap() {
|
|
|
487
974
|
|
|
488
975
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
489
976
|
var makeIssue = (params) => {
|
|
490
|
-
const { data, path:
|
|
491
|
-
const fullPath = [...
|
|
977
|
+
const { data, path: path9, errorMaps, issueData } = params;
|
|
978
|
+
const fullPath = [...path9, ...issueData.path || []];
|
|
492
979
|
const fullIssue = {
|
|
493
980
|
...issueData,
|
|
494
981
|
path: fullPath
|
|
@@ -604,11 +1091,11 @@ var errorUtil;
|
|
|
604
1091
|
|
|
605
1092
|
// node_modules/zod/v3/types.js
|
|
606
1093
|
var ParseInputLazyPath = class {
|
|
607
|
-
constructor(parent, value,
|
|
1094
|
+
constructor(parent, value, path9, key) {
|
|
608
1095
|
this._cachedPath = [];
|
|
609
1096
|
this.parent = parent;
|
|
610
1097
|
this.data = value;
|
|
611
|
-
this._path =
|
|
1098
|
+
this._path = path9;
|
|
612
1099
|
this._key = key;
|
|
613
1100
|
}
|
|
614
1101
|
get path() {
|
|
@@ -4195,7 +4682,8 @@ var configSchema = external_exports.object({
|
|
|
4195
4682
|
defaultProjectId: external_exports.string().uuid().optional(),
|
|
4196
4683
|
userAgent: external_exports.string().default(`contextstream-mcp/${VERSION}`),
|
|
4197
4684
|
allowHeaderAuth: external_exports.boolean().optional(),
|
|
4198
|
-
contextPackEnabled: external_exports.boolean().default(true)
|
|
4685
|
+
contextPackEnabled: external_exports.boolean().default(true),
|
|
4686
|
+
showTiming: external_exports.boolean().default(false)
|
|
4199
4687
|
});
|
|
4200
4688
|
var MISSING_CREDENTIALS_ERROR = "Set CONTEXTSTREAM_API_KEY or CONTEXTSTREAM_JWT for authentication (or CONTEXTSTREAM_ALLOW_HEADER_AUTH=true for header-based auth).";
|
|
4201
4689
|
function isMissingCredentialsError(err) {
|
|
@@ -4209,6 +4697,7 @@ function loadConfig() {
|
|
|
4209
4697
|
const contextPackEnabled = parseBooleanEnv(
|
|
4210
4698
|
process.env.CONTEXTSTREAM_CONTEXT_PACK ?? process.env.CONTEXTSTREAM_CONTEXT_PACK_ENABLED
|
|
4211
4699
|
);
|
|
4700
|
+
const showTiming = parseBooleanEnv(process.env.CONTEXTSTREAM_SHOW_TIMING);
|
|
4212
4701
|
const parsed = configSchema.safeParse({
|
|
4213
4702
|
apiUrl: process.env.CONTEXTSTREAM_API_URL,
|
|
4214
4703
|
apiKey: process.env.CONTEXTSTREAM_API_KEY,
|
|
@@ -4217,7 +4706,8 @@ function loadConfig() {
|
|
|
4217
4706
|
defaultProjectId: process.env.CONTEXTSTREAM_PROJECT_ID,
|
|
4218
4707
|
userAgent: process.env.CONTEXTSTREAM_USER_AGENT,
|
|
4219
4708
|
allowHeaderAuth,
|
|
4220
|
-
contextPackEnabled
|
|
4709
|
+
contextPackEnabled,
|
|
4710
|
+
showTiming
|
|
4221
4711
|
});
|
|
4222
4712
|
if (!parsed.success) {
|
|
4223
4713
|
const missing = parsed.error.errors.map((e) => e.path.join(".")).join(", ");
|
|
@@ -4233,7 +4723,7 @@ function loadConfig() {
|
|
|
4233
4723
|
|
|
4234
4724
|
// src/client.ts
|
|
4235
4725
|
import { randomUUID } from "node:crypto";
|
|
4236
|
-
import * as
|
|
4726
|
+
import * as path4 from "node:path";
|
|
4237
4727
|
|
|
4238
4728
|
// src/auth-context.ts
|
|
4239
4729
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -4299,12 +4789,12 @@ var BASE_DELAY = 1e3;
|
|
|
4299
4789
|
async function sleep(ms) {
|
|
4300
4790
|
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
4301
4791
|
}
|
|
4302
|
-
async function request(config,
|
|
4792
|
+
async function request(config, path9, options = {}) {
|
|
4303
4793
|
const { apiUrl, userAgent } = config;
|
|
4304
4794
|
const authOverride = getAuthOverride();
|
|
4305
4795
|
const apiKey = authOverride?.apiKey ?? config.apiKey;
|
|
4306
4796
|
const jwt = authOverride?.jwt ?? config.jwt;
|
|
4307
|
-
const apiPath =
|
|
4797
|
+
const apiPath = path9.startsWith("/api/") ? path9 : `/api/v1${path9}`;
|
|
4308
4798
|
const url = `${apiUrl.replace(/\/$/, "")}${apiPath}`;
|
|
4309
4799
|
const maxRetries = options.retries ?? MAX_RETRIES;
|
|
4310
4800
|
const baseDelay = options.retryDelay ?? BASE_DELAY;
|
|
@@ -4450,9 +4940,9 @@ function extractErrorCode(payload) {
|
|
|
4450
4940
|
if (typeof payload.code === "string" && payload.code.trim()) return payload.code.trim();
|
|
4451
4941
|
return null;
|
|
4452
4942
|
}
|
|
4453
|
-
function detectIntegrationProvider(
|
|
4454
|
-
if (/\/github(\/|$)/i.test(
|
|
4455
|
-
if (/\/slack(\/|$)/i.test(
|
|
4943
|
+
function detectIntegrationProvider(path9) {
|
|
4944
|
+
if (/\/github(\/|$)/i.test(path9)) return "github";
|
|
4945
|
+
if (/\/slack(\/|$)/i.test(path9)) return "slack";
|
|
4456
4946
|
return null;
|
|
4457
4947
|
}
|
|
4458
4948
|
function rewriteNotFoundMessage(input) {
|
|
@@ -4465,8 +4955,81 @@ function rewriteNotFoundMessage(input) {
|
|
|
4465
4955
|
}
|
|
4466
4956
|
|
|
4467
4957
|
// src/files.ts
|
|
4958
|
+
import * as fs2 from "fs";
|
|
4959
|
+
import * as path2 from "path";
|
|
4960
|
+
|
|
4961
|
+
// src/ignore.ts
|
|
4962
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
4468
4963
|
import * as fs from "fs";
|
|
4469
4964
|
import * as path from "path";
|
|
4965
|
+
var IGNORE_FILENAME = ".contextstream/ignore";
|
|
4966
|
+
var DEFAULT_IGNORE_PATTERNS = [
|
|
4967
|
+
// Version control
|
|
4968
|
+
".git/",
|
|
4969
|
+
".svn/",
|
|
4970
|
+
".hg/",
|
|
4971
|
+
// Package managers / dependencies
|
|
4972
|
+
"node_modules/",
|
|
4973
|
+
"vendor/",
|
|
4974
|
+
".pnpm/",
|
|
4975
|
+
// Build outputs
|
|
4976
|
+
"target/",
|
|
4977
|
+
"dist/",
|
|
4978
|
+
"build/",
|
|
4979
|
+
"out/",
|
|
4980
|
+
".next/",
|
|
4981
|
+
".nuxt/",
|
|
4982
|
+
// Python
|
|
4983
|
+
"__pycache__/",
|
|
4984
|
+
".pytest_cache/",
|
|
4985
|
+
".mypy_cache/",
|
|
4986
|
+
"venv/",
|
|
4987
|
+
".venv/",
|
|
4988
|
+
"env/",
|
|
4989
|
+
".env/",
|
|
4990
|
+
// IDE
|
|
4991
|
+
".idea/",
|
|
4992
|
+
".vscode/",
|
|
4993
|
+
".vs/",
|
|
4994
|
+
// Coverage
|
|
4995
|
+
"coverage/",
|
|
4996
|
+
".coverage/",
|
|
4997
|
+
// Lock files
|
|
4998
|
+
"package-lock.json",
|
|
4999
|
+
"yarn.lock",
|
|
5000
|
+
"pnpm-lock.yaml",
|
|
5001
|
+
"Cargo.lock",
|
|
5002
|
+
"poetry.lock",
|
|
5003
|
+
"Gemfile.lock",
|
|
5004
|
+
"composer.lock",
|
|
5005
|
+
// OS files
|
|
5006
|
+
".DS_Store",
|
|
5007
|
+
"Thumbs.db"
|
|
5008
|
+
];
|
|
5009
|
+
async function loadIgnorePatterns(projectRoot) {
|
|
5010
|
+
const ig = (0, import_ignore.default)();
|
|
5011
|
+
const patterns = [...DEFAULT_IGNORE_PATTERNS];
|
|
5012
|
+
ig.add(DEFAULT_IGNORE_PATTERNS);
|
|
5013
|
+
const ignoreFilePath = path.join(projectRoot, IGNORE_FILENAME);
|
|
5014
|
+
let hasUserPatterns = false;
|
|
5015
|
+
try {
|
|
5016
|
+
const content = await fs.promises.readFile(ignoreFilePath, "utf-8");
|
|
5017
|
+
const userPatterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
5018
|
+
if (userPatterns.length > 0) {
|
|
5019
|
+
ig.add(userPatterns);
|
|
5020
|
+
patterns.push(...userPatterns);
|
|
5021
|
+
hasUserPatterns = true;
|
|
5022
|
+
}
|
|
5023
|
+
} catch {
|
|
5024
|
+
}
|
|
5025
|
+
return {
|
|
5026
|
+
ignores: (pathname) => ig.ignores(pathname),
|
|
5027
|
+
patterns,
|
|
5028
|
+
hasUserPatterns
|
|
5029
|
+
};
|
|
5030
|
+
}
|
|
5031
|
+
|
|
5032
|
+
// src/files.ts
|
|
4470
5033
|
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4471
5034
|
// Rust
|
|
4472
5035
|
"rs",
|
|
@@ -4577,31 +5140,34 @@ var MAX_FILES_PER_BATCH = 200;
|
|
|
4577
5140
|
async function* readAllFilesInBatches(rootPath, options = {}) {
|
|
4578
5141
|
const maxBatchBytes = options.maxBatchBytes ?? MAX_BATCH_BYTES;
|
|
4579
5142
|
const largeFileThreshold = options.largeFileThreshold ?? LARGE_FILE_THRESHOLD;
|
|
4580
|
-
const maxFilesPerBatch = options.maxFilesPerBatch ?? MAX_FILES_PER_BATCH;
|
|
5143
|
+
const maxFilesPerBatch = options.maxFilesPerBatch ?? options.batchSize ?? MAX_FILES_PER_BATCH;
|
|
4581
5144
|
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
5145
|
+
const ig = options.ignoreInstance ?? await loadIgnorePatterns(rootPath);
|
|
4582
5146
|
let batch = [];
|
|
4583
5147
|
let currentBatchBytes = 0;
|
|
4584
5148
|
async function* walkDir(dir, relativePath = "") {
|
|
4585
5149
|
let entries;
|
|
4586
5150
|
try {
|
|
4587
|
-
entries = await
|
|
5151
|
+
entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
4588
5152
|
} catch {
|
|
4589
5153
|
return;
|
|
4590
5154
|
}
|
|
4591
5155
|
for (const entry of entries) {
|
|
4592
|
-
const fullPath =
|
|
4593
|
-
const relPath =
|
|
5156
|
+
const fullPath = path2.join(dir, entry.name);
|
|
5157
|
+
const relPath = path2.join(relativePath, entry.name);
|
|
4594
5158
|
if (entry.isDirectory()) {
|
|
4595
5159
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
5160
|
+
if (ig.ignores(relPath + "/")) continue;
|
|
4596
5161
|
yield* walkDir(fullPath, relPath);
|
|
4597
5162
|
} else if (entry.isFile()) {
|
|
4598
5163
|
if (IGNORE_FILES.has(entry.name)) continue;
|
|
5164
|
+
if (ig.ignores(relPath)) continue;
|
|
4599
5165
|
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
4600
5166
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
4601
5167
|
try {
|
|
4602
|
-
const stat2 = await
|
|
5168
|
+
const stat2 = await fs2.promises.stat(fullPath);
|
|
4603
5169
|
if (stat2.size > maxFileSize) continue;
|
|
4604
|
-
const content = await
|
|
5170
|
+
const content = await fs2.promises.readFile(fullPath, "utf-8");
|
|
4605
5171
|
yield { path: relPath, content, sizeBytes: stat2.size };
|
|
4606
5172
|
} catch {
|
|
4607
5173
|
}
|
|
@@ -4637,9 +5203,10 @@ async function* readAllFilesInBatches(rootPath, options = {}) {
|
|
|
4637
5203
|
async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}) {
|
|
4638
5204
|
const maxBatchBytes = options.maxBatchBytes ?? MAX_BATCH_BYTES;
|
|
4639
5205
|
const largeFileThreshold = options.largeFileThreshold ?? LARGE_FILE_THRESHOLD;
|
|
4640
|
-
const maxFilesPerBatch = options.maxFilesPerBatch ?? MAX_FILES_PER_BATCH;
|
|
5206
|
+
const maxFilesPerBatch = options.maxFilesPerBatch ?? options.batchSize ?? MAX_FILES_PER_BATCH;
|
|
4641
5207
|
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
4642
5208
|
const sinceMs = sinceTimestamp.getTime();
|
|
5209
|
+
const ig = options.ignoreInstance ?? await loadIgnorePatterns(rootPath);
|
|
4643
5210
|
let batch = [];
|
|
4644
5211
|
let currentBatchBytes = 0;
|
|
4645
5212
|
let filesScanned = 0;
|
|
@@ -4647,26 +5214,28 @@ async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}
|
|
|
4647
5214
|
async function* walkDir(dir, relativePath = "") {
|
|
4648
5215
|
let entries;
|
|
4649
5216
|
try {
|
|
4650
|
-
entries = await
|
|
5217
|
+
entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
4651
5218
|
} catch {
|
|
4652
5219
|
return;
|
|
4653
5220
|
}
|
|
4654
5221
|
for (const entry of entries) {
|
|
4655
|
-
const fullPath =
|
|
4656
|
-
const relPath =
|
|
5222
|
+
const fullPath = path2.join(dir, entry.name);
|
|
5223
|
+
const relPath = path2.join(relativePath, entry.name);
|
|
4657
5224
|
if (entry.isDirectory()) {
|
|
4658
5225
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
5226
|
+
if (ig.ignores(relPath + "/")) continue;
|
|
4659
5227
|
yield* walkDir(fullPath, relPath);
|
|
4660
5228
|
} else if (entry.isFile()) {
|
|
4661
5229
|
if (IGNORE_FILES.has(entry.name)) continue;
|
|
5230
|
+
if (ig.ignores(relPath)) continue;
|
|
4662
5231
|
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
4663
5232
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
4664
5233
|
try {
|
|
4665
|
-
const stat2 = await
|
|
5234
|
+
const stat2 = await fs2.promises.stat(fullPath);
|
|
4666
5235
|
filesScanned++;
|
|
4667
5236
|
if (stat2.mtimeMs <= sinceMs) continue;
|
|
4668
5237
|
if (stat2.size > maxFileSize) continue;
|
|
4669
|
-
const content = await
|
|
5238
|
+
const content = await fs2.promises.readFile(fullPath, "utf-8");
|
|
4670
5239
|
filesChanged++;
|
|
4671
5240
|
yield { path: relPath, content, sizeBytes: stat2.size };
|
|
4672
5241
|
} catch {
|
|
@@ -4706,16 +5275,17 @@ async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}
|
|
|
4706
5275
|
async function countIndexableFiles(rootPath, options = {}) {
|
|
4707
5276
|
const maxFiles = options.maxFiles ?? 1;
|
|
4708
5277
|
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
5278
|
+
const ig = options.ignoreInstance ?? await loadIgnorePatterns(rootPath);
|
|
4709
5279
|
let count = 0;
|
|
4710
5280
|
let stopped = false;
|
|
4711
|
-
async function walkDir(dir) {
|
|
5281
|
+
async function walkDir(dir, relativePath = "") {
|
|
4712
5282
|
if (count >= maxFiles) {
|
|
4713
5283
|
stopped = true;
|
|
4714
5284
|
return;
|
|
4715
5285
|
}
|
|
4716
5286
|
let entries;
|
|
4717
5287
|
try {
|
|
4718
|
-
entries = await
|
|
5288
|
+
entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
4719
5289
|
} catch {
|
|
4720
5290
|
return;
|
|
4721
5291
|
}
|
|
@@ -4724,16 +5294,19 @@ async function countIndexableFiles(rootPath, options = {}) {
|
|
|
4724
5294
|
stopped = true;
|
|
4725
5295
|
return;
|
|
4726
5296
|
}
|
|
4727
|
-
const fullPath =
|
|
5297
|
+
const fullPath = path2.join(dir, entry.name);
|
|
5298
|
+
const relPath = path2.join(relativePath, entry.name);
|
|
4728
5299
|
if (entry.isDirectory()) {
|
|
4729
5300
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
4730
|
-
|
|
5301
|
+
if (ig.ignores(relPath + "/")) continue;
|
|
5302
|
+
await walkDir(fullPath, relPath);
|
|
4731
5303
|
} else if (entry.isFile()) {
|
|
4732
5304
|
if (IGNORE_FILES.has(entry.name)) continue;
|
|
5305
|
+
if (ig.ignores(relPath)) continue;
|
|
4733
5306
|
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
4734
5307
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
4735
5308
|
try {
|
|
4736
|
-
const stat2 = await
|
|
5309
|
+
const stat2 = await fs2.promises.stat(fullPath);
|
|
4737
5310
|
if (stat2.size > maxFileSize) continue;
|
|
4738
5311
|
count++;
|
|
4739
5312
|
if (count >= maxFiles) {
|
|
@@ -4750,16 +5323,16 @@ async function countIndexableFiles(rootPath, options = {}) {
|
|
|
4750
5323
|
}
|
|
4751
5324
|
|
|
4752
5325
|
// src/workspace-config.ts
|
|
4753
|
-
import * as
|
|
4754
|
-
import * as
|
|
5326
|
+
import * as fs3 from "fs";
|
|
5327
|
+
import * as path3 from "path";
|
|
4755
5328
|
var CONFIG_DIR = ".contextstream";
|
|
4756
5329
|
var CONFIG_FILE = "config.json";
|
|
4757
5330
|
var GLOBAL_MAPPINGS_FILE = ".contextstream-mappings.json";
|
|
4758
5331
|
function readLocalConfig(repoPath) {
|
|
4759
|
-
const configPath =
|
|
5332
|
+
const configPath = path3.join(repoPath, CONFIG_DIR, CONFIG_FILE);
|
|
4760
5333
|
try {
|
|
4761
|
-
if (
|
|
4762
|
-
const content =
|
|
5334
|
+
if (fs3.existsSync(configPath)) {
|
|
5335
|
+
const content = fs3.readFileSync(configPath, "utf-8");
|
|
4763
5336
|
return JSON.parse(content);
|
|
4764
5337
|
}
|
|
4765
5338
|
} catch (e) {
|
|
@@ -4768,13 +5341,13 @@ function readLocalConfig(repoPath) {
|
|
|
4768
5341
|
return null;
|
|
4769
5342
|
}
|
|
4770
5343
|
function writeLocalConfig(repoPath, config) {
|
|
4771
|
-
const configDir =
|
|
4772
|
-
const configPath =
|
|
5344
|
+
const configDir = path3.join(repoPath, CONFIG_DIR);
|
|
5345
|
+
const configPath = path3.join(configDir, CONFIG_FILE);
|
|
4773
5346
|
try {
|
|
4774
|
-
if (!
|
|
4775
|
-
|
|
5347
|
+
if (!fs3.existsSync(configDir)) {
|
|
5348
|
+
fs3.mkdirSync(configDir, { recursive: true });
|
|
4776
5349
|
}
|
|
4777
|
-
|
|
5350
|
+
fs3.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
4778
5351
|
return true;
|
|
4779
5352
|
} catch (e) {
|
|
4780
5353
|
console.error(`Failed to write config to ${configPath}:`, e);
|
|
@@ -4783,10 +5356,10 @@ function writeLocalConfig(repoPath, config) {
|
|
|
4783
5356
|
}
|
|
4784
5357
|
function readGlobalMappings() {
|
|
4785
5358
|
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
4786
|
-
const mappingsPath =
|
|
5359
|
+
const mappingsPath = path3.join(homeDir, GLOBAL_MAPPINGS_FILE);
|
|
4787
5360
|
try {
|
|
4788
|
-
if (
|
|
4789
|
-
const content =
|
|
5361
|
+
if (fs3.existsSync(mappingsPath)) {
|
|
5362
|
+
const content = fs3.readFileSync(mappingsPath, "utf-8");
|
|
4790
5363
|
return JSON.parse(content);
|
|
4791
5364
|
}
|
|
4792
5365
|
} catch (e) {
|
|
@@ -4796,9 +5369,9 @@ function readGlobalMappings() {
|
|
|
4796
5369
|
}
|
|
4797
5370
|
function writeGlobalMappings(mappings) {
|
|
4798
5371
|
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
4799
|
-
const mappingsPath =
|
|
5372
|
+
const mappingsPath = path3.join(homeDir, GLOBAL_MAPPINGS_FILE);
|
|
4800
5373
|
try {
|
|
4801
|
-
|
|
5374
|
+
fs3.writeFileSync(mappingsPath, JSON.stringify(mappings, null, 2));
|
|
4802
5375
|
return true;
|
|
4803
5376
|
} catch (e) {
|
|
4804
5377
|
console.error(`Failed to write global mappings:`, e);
|
|
@@ -4806,20 +5379,20 @@ function writeGlobalMappings(mappings) {
|
|
|
4806
5379
|
}
|
|
4807
5380
|
}
|
|
4808
5381
|
function addGlobalMapping(mapping) {
|
|
4809
|
-
const normalizedPattern =
|
|
5382
|
+
const normalizedPattern = path3.normalize(mapping.pattern);
|
|
4810
5383
|
const mappings = readGlobalMappings();
|
|
4811
|
-
const filtered = mappings.filter((m) =>
|
|
5384
|
+
const filtered = mappings.filter((m) => path3.normalize(m.pattern) !== normalizedPattern);
|
|
4812
5385
|
filtered.push({ ...mapping, pattern: normalizedPattern });
|
|
4813
5386
|
return writeGlobalMappings(filtered);
|
|
4814
5387
|
}
|
|
4815
5388
|
function findMatchingMapping(repoPath) {
|
|
4816
5389
|
const mappings = readGlobalMappings();
|
|
4817
|
-
const normalizedRepo =
|
|
5390
|
+
const normalizedRepo = path3.normalize(repoPath);
|
|
4818
5391
|
for (const mapping of mappings) {
|
|
4819
|
-
const normalizedPattern =
|
|
4820
|
-
if (normalizedPattern.endsWith(`${
|
|
5392
|
+
const normalizedPattern = path3.normalize(mapping.pattern);
|
|
5393
|
+
if (normalizedPattern.endsWith(`${path3.sep}*`)) {
|
|
4821
5394
|
const parentDir = normalizedPattern.slice(0, -2);
|
|
4822
|
-
if (normalizedRepo.startsWith(parentDir +
|
|
5395
|
+
if (normalizedRepo.startsWith(parentDir + path3.sep)) {
|
|
4823
5396
|
return mapping;
|
|
4824
5397
|
}
|
|
4825
5398
|
} else if (normalizedRepo === normalizedPattern) {
|
|
@@ -5008,6 +5581,47 @@ var INGEST_BENEFITS = [
|
|
|
5008
5581
|
"Allow the AI assistant to find relevant code without manual file navigation",
|
|
5009
5582
|
"Build a searchable knowledge base of your codebase structure"
|
|
5010
5583
|
];
|
|
5584
|
+
var PROJECT_MARKERS = [
|
|
5585
|
+
".git",
|
|
5586
|
+
"package.json",
|
|
5587
|
+
"Cargo.toml",
|
|
5588
|
+
"pyproject.toml",
|
|
5589
|
+
"go.mod",
|
|
5590
|
+
"pom.xml",
|
|
5591
|
+
"build.gradle",
|
|
5592
|
+
"Gemfile",
|
|
5593
|
+
"composer.json",
|
|
5594
|
+
".contextstream"
|
|
5595
|
+
];
|
|
5596
|
+
function isMultiProjectFolder(folderPath) {
|
|
5597
|
+
try {
|
|
5598
|
+
const fs8 = __require("fs");
|
|
5599
|
+
const pathModule = __require("path");
|
|
5600
|
+
const rootHasGit = fs8.existsSync(pathModule.join(folderPath, ".git"));
|
|
5601
|
+
const entries = fs8.readdirSync(folderPath, { withFileTypes: true });
|
|
5602
|
+
const subdirs = entries.filter(
|
|
5603
|
+
(e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules"
|
|
5604
|
+
);
|
|
5605
|
+
const projectSubdirs = [];
|
|
5606
|
+
for (const subdir of subdirs) {
|
|
5607
|
+
const subdirPath = pathModule.join(folderPath, subdir.name);
|
|
5608
|
+
for (const marker of PROJECT_MARKERS) {
|
|
5609
|
+
if (fs8.existsSync(pathModule.join(subdirPath, marker))) {
|
|
5610
|
+
projectSubdirs.push(subdir.name);
|
|
5611
|
+
break;
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5615
|
+
const isMultiProject = projectSubdirs.length >= 1 && (!rootHasGit || projectSubdirs.length >= 2);
|
|
5616
|
+
return {
|
|
5617
|
+
isMultiProject,
|
|
5618
|
+
projectCount: projectSubdirs.length,
|
|
5619
|
+
projectNames: projectSubdirs
|
|
5620
|
+
};
|
|
5621
|
+
} catch {
|
|
5622
|
+
return { isMultiProject: false, projectCount: 0, projectNames: [] };
|
|
5623
|
+
}
|
|
5624
|
+
}
|
|
5011
5625
|
var ContextStreamClient = class {
|
|
5012
5626
|
constructor(config) {
|
|
5013
5627
|
this.config = config;
|
|
@@ -5922,7 +6536,7 @@ var ContextStreamClient = class {
|
|
|
5922
6536
|
context.workspace_source = resolved.source;
|
|
5923
6537
|
context.workspace_resolved_from = resolved.source === "local_config" ? `${rootPath}/.contextstream/config.json` : "parent_folder_mapping";
|
|
5924
6538
|
} else {
|
|
5925
|
-
const folderName = rootPath ?
|
|
6539
|
+
const folderName = rootPath ? path4.basename(rootPath).toLowerCase() : "";
|
|
5926
6540
|
try {
|
|
5927
6541
|
const workspaces = await this.listWorkspaces({ page_size: 50 });
|
|
5928
6542
|
if (workspaces.items && workspaces.items.length > 0) {
|
|
@@ -5978,13 +6592,13 @@ var ContextStreamClient = class {
|
|
|
5978
6592
|
name: w.name,
|
|
5979
6593
|
description: w.description
|
|
5980
6594
|
}));
|
|
5981
|
-
context.message = `New folder detected: "${rootPath ?
|
|
6595
|
+
context.message = `New folder detected: "${rootPath ? path4.basename(rootPath) : "this folder"}". Please select which workspace this belongs to, or create a new one.`;
|
|
5982
6596
|
context.ide_roots = ideRoots;
|
|
5983
|
-
context.folder_name = rootPath ?
|
|
6597
|
+
context.folder_name = rootPath ? path4.basename(rootPath) : void 0;
|
|
5984
6598
|
return context;
|
|
5985
6599
|
}
|
|
5986
6600
|
} else {
|
|
5987
|
-
const folderDisplayName = rootPath ?
|
|
6601
|
+
const folderDisplayName = rootPath ? path4.basename(rootPath) || "this folder" : "this folder";
|
|
5988
6602
|
context.status = "requires_workspace_name";
|
|
5989
6603
|
context.workspace_source = "none_found";
|
|
5990
6604
|
context.ide_roots = ideRoots;
|
|
@@ -6012,7 +6626,7 @@ var ContextStreamClient = class {
|
|
|
6012
6626
|
}
|
|
6013
6627
|
}
|
|
6014
6628
|
if (!workspaceId && !params.allow_no_workspace) {
|
|
6015
|
-
const folderDisplayName = rootPath ?
|
|
6629
|
+
const folderDisplayName = rootPath ? path4.basename(rootPath) || "this folder" : "this folder";
|
|
6016
6630
|
context.ide_roots = ideRoots;
|
|
6017
6631
|
context.folder_name = folderDisplayName;
|
|
6018
6632
|
if (rootPath) {
|
|
@@ -6043,8 +6657,26 @@ var ContextStreamClient = class {
|
|
|
6043
6657
|
return context;
|
|
6044
6658
|
}
|
|
6045
6659
|
}
|
|
6046
|
-
|
|
6047
|
-
|
|
6660
|
+
let autoDetectedMultiProject = false;
|
|
6661
|
+
if (!params.skip_project_creation && !projectId && rootPath) {
|
|
6662
|
+
const detection = isMultiProjectFolder(rootPath);
|
|
6663
|
+
if (detection.isMultiProject) {
|
|
6664
|
+
autoDetectedMultiProject = true;
|
|
6665
|
+
context.workspace_only_mode = true;
|
|
6666
|
+
context.auto_detected_multi_project = true;
|
|
6667
|
+
context.detected_projects = detection.projectNames;
|
|
6668
|
+
context.project_skipped_reason = `Auto-detected ${detection.projectCount} projects in folder: ${detection.projectNames.slice(0, 5).join(", ")}${detection.projectCount > 5 ? "..." : ""}. Working at workspace level.`;
|
|
6669
|
+
console.error(
|
|
6670
|
+
`[ContextStream] Auto-detected multi-project folder with ${detection.projectCount} projects: ${detection.projectNames.slice(0, 5).join(", ")}`
|
|
6671
|
+
);
|
|
6672
|
+
}
|
|
6673
|
+
}
|
|
6674
|
+
if (params.skip_project_creation) {
|
|
6675
|
+
context.workspace_only_mode = true;
|
|
6676
|
+
context.project_skipped_reason = "skip_project_creation=true - working at workspace level for multi-project folder";
|
|
6677
|
+
} else if (autoDetectedMultiProject) {
|
|
6678
|
+
} else if (!projectId && workspaceId && rootPath && params.auto_index !== false) {
|
|
6679
|
+
const projectName = path4.basename(rootPath) || "My Project";
|
|
6048
6680
|
try {
|
|
6049
6681
|
const projects = await this.listProjects({ workspace_id: workspaceId });
|
|
6050
6682
|
const projectNameLower = projectName.toLowerCase();
|
|
@@ -6354,16 +6986,21 @@ var ContextStreamClient = class {
|
|
|
6354
6986
|
* Persists the selection to .contextstream/config.json for future sessions.
|
|
6355
6987
|
*/
|
|
6356
6988
|
async associateWorkspace(params) {
|
|
6357
|
-
const { folder_path, workspace_id, workspace_name, create_parent_mapping } = params;
|
|
6989
|
+
const { folder_path, workspace_id, workspace_name, create_parent_mapping, version, configured_editors, context_pack, api_url } = params;
|
|
6358
6990
|
const saved = writeLocalConfig(folder_path, {
|
|
6359
6991
|
workspace_id,
|
|
6360
6992
|
workspace_name,
|
|
6361
|
-
associated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6993
|
+
associated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6994
|
+
version,
|
|
6995
|
+
configured_editors,
|
|
6996
|
+
context_pack,
|
|
6997
|
+
api_url,
|
|
6998
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6362
6999
|
});
|
|
6363
7000
|
if (create_parent_mapping) {
|
|
6364
|
-
const parentDir =
|
|
7001
|
+
const parentDir = path4.dirname(folder_path);
|
|
6365
7002
|
addGlobalMapping({
|
|
6366
|
-
pattern:
|
|
7003
|
+
pattern: path4.join(parentDir, "*"),
|
|
6367
7004
|
workspace_id,
|
|
6368
7005
|
workspace_name: workspace_name || "Unknown"
|
|
6369
7006
|
});
|
|
@@ -6869,9 +7506,9 @@ var ContextStreamClient = class {
|
|
|
6869
7506
|
candidateParts.push("## Relevant Code\n");
|
|
6870
7507
|
currentChars += 18;
|
|
6871
7508
|
const codeEntries = code.results.map((c) => {
|
|
6872
|
-
const
|
|
7509
|
+
const path9 = c.file_path || "file";
|
|
6873
7510
|
const content = c.content?.slice(0, 150) || "";
|
|
6874
|
-
return { path:
|
|
7511
|
+
return { path: path9, entry: `\u2022 ${path9}: ${content}...
|
|
6875
7512
|
` };
|
|
6876
7513
|
});
|
|
6877
7514
|
for (const c of codeEntries) {
|
|
@@ -7029,7 +7666,10 @@ var ContextStreamClient = class {
|
|
|
7029
7666
|
distill: params.distill,
|
|
7030
7667
|
client_version: VERSION,
|
|
7031
7668
|
rules_version: VERSION,
|
|
7032
|
-
notice_inline: false
|
|
7669
|
+
notice_inline: false,
|
|
7670
|
+
// Session token tracking for context pressure
|
|
7671
|
+
...params.session_tokens !== void 0 && { session_tokens: params.session_tokens },
|
|
7672
|
+
...params.context_threshold !== void 0 && { context_threshold: params.context_threshold }
|
|
7033
7673
|
}
|
|
7034
7674
|
});
|
|
7035
7675
|
const data = unwrapApiResponse(apiResult);
|
|
@@ -7049,7 +7689,8 @@ var ContextStreamClient = class {
|
|
|
7049
7689
|
project_id: withDefaults.project_id,
|
|
7050
7690
|
...versionNotice2 ? { version_notice: versionNotice2 } : {},
|
|
7051
7691
|
...Array.isArray(data?.errors) ? { errors: data.errors } : {},
|
|
7052
|
-
...this.indexRefreshInProgress ? { index_status: "refreshing" } : {}
|
|
7692
|
+
...this.indexRefreshInProgress ? { index_status: "refreshing" } : {},
|
|
7693
|
+
...data?.context_pressure ? { context_pressure: data.context_pressure } : {}
|
|
7053
7694
|
};
|
|
7054
7695
|
} catch (err) {
|
|
7055
7696
|
const message2 = err instanceof Error ? err.message : String(err);
|
|
@@ -8192,8 +8833,8 @@ W:${wsHint}
|
|
|
8192
8833
|
};
|
|
8193
8834
|
|
|
8194
8835
|
// src/tools.ts
|
|
8195
|
-
import * as
|
|
8196
|
-
import * as
|
|
8836
|
+
import * as fs5 from "node:fs";
|
|
8837
|
+
import * as path6 from "node:path";
|
|
8197
8838
|
import { homedir as homedir3 } from "node:os";
|
|
8198
8839
|
|
|
8199
8840
|
// src/rules-templates.ts
|
|
@@ -8417,6 +9058,39 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
|
|
|
8417
9058
|
|
|
8418
9059
|
---
|
|
8419
9060
|
|
|
9061
|
+
### Context Pressure & Compaction Awareness
|
|
9062
|
+
|
|
9063
|
+
ContextStream tracks context pressure to help you stay ahead of conversation compaction:
|
|
9064
|
+
|
|
9065
|
+
**Automatic tracking:** Token usage is tracked automatically. \`context_smart\` returns \`context_pressure\` when usage is high.
|
|
9066
|
+
|
|
9067
|
+
**When \`context_smart\` returns \`context_pressure\` with high/critical level:**
|
|
9068
|
+
1. Review the \`suggested_action\` field:
|
|
9069
|
+
- \`prepare_save\`: Start thinking about saving important state
|
|
9070
|
+
- \`save_now\`: Immediately call \`session(action="capture", event_type="session_snapshot")\` to preserve state
|
|
9071
|
+
|
|
9072
|
+
**PreCompact Hook (Optional):** If enabled, Claude Code will inject a reminder to save state before compaction.
|
|
9073
|
+
Enable with: \`generate_rules(install_hooks=true, include_pre_compact=true)\`
|
|
9074
|
+
|
|
9075
|
+
**Before compaction happens (when warned):**
|
|
9076
|
+
\`\`\`
|
|
9077
|
+
session(action="capture", event_type="session_snapshot", title="Pre-compaction snapshot", content="{
|
|
9078
|
+
\\"conversation_summary\\": \\"<summarize what we've been doing>\\",
|
|
9079
|
+
\\"current_goal\\": \\"<the main task>\\",
|
|
9080
|
+
\\"active_files\\": [\\"file1.ts\\", \\"file2.ts\\"],
|
|
9081
|
+
\\"recent_decisions\\": [{title: \\"...\\", rationale: \\"...\\"}],
|
|
9082
|
+
\\"unfinished_work\\": [{task: \\"...\\", status: \\"...\\", next_steps: \\"...\\"}]
|
|
9083
|
+
}")
|
|
9084
|
+
\`\`\`
|
|
9085
|
+
|
|
9086
|
+
**After compaction (when context seems lost):**
|
|
9087
|
+
1. Call \`session_init(folder_path="...", is_post_compact=true)\` - this auto-restores the most recent snapshot
|
|
9088
|
+
2. Or call \`session_restore_context()\` directly to get the saved state
|
|
9089
|
+
3. Review the \`restored_context\` to understand prior work
|
|
9090
|
+
4. Acknowledge to the user what was restored and continue
|
|
9091
|
+
|
|
9092
|
+
---
|
|
9093
|
+
|
|
8420
9094
|
### Index Status (Auto-Managed)
|
|
8421
9095
|
|
|
8422
9096
|
**Indexing is automatic.** After \`session_init\`, the project is auto-indexed in the background.
|
|
@@ -8650,12 +9324,27 @@ ContextStream search is **indexed** and returns semantic matches + context in ON
|
|
|
8650
9324
|
- Before risky work: \`session(action="get_lessons", query="<topic>")\`
|
|
8651
9325
|
- On mistakes: \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\`
|
|
8652
9326
|
|
|
9327
|
+
### Context Pressure & Compaction
|
|
9328
|
+
|
|
9329
|
+
- If \`context_smart\` returns high/critical \`context_pressure\`: call \`session_capture_smart(...)\` to save state
|
|
9330
|
+
- After compaction (context lost): call \`session_init(..., is_post_compact=true)\` or \`session_restore_context()\`
|
|
9331
|
+
|
|
8653
9332
|
### Plans & Tasks
|
|
8654
9333
|
|
|
8655
9334
|
When user asks for a plan, use ContextStream (not EnterPlanMode):
|
|
8656
9335
|
1. \`session(action="capture_plan", title="...", steps=[...])\`
|
|
8657
9336
|
2. \`memory(action="create_task", title="...", plan_id="<id>")\`
|
|
8658
9337
|
|
|
9338
|
+
### Workspace-Only Mode (Multi-Project Folders)
|
|
9339
|
+
|
|
9340
|
+
If working in a parent folder containing multiple projects:
|
|
9341
|
+
\`\`\`
|
|
9342
|
+
session_init(folder_path="...", skip_project_creation=true)
|
|
9343
|
+
\`\`\`
|
|
9344
|
+
|
|
9345
|
+
This enables workspace-level memory and context without project-specific indexing.
|
|
9346
|
+
Use for monorepos or folders with multiple independent projects.
|
|
9347
|
+
|
|
8659
9348
|
Full docs: https://contextstream.io/docs/mcp/tools
|
|
8660
9349
|
`.trim();
|
|
8661
9350
|
var TEMPLATES = {
|
|
@@ -8937,8 +9626,8 @@ function getCoreToolsHint() {
|
|
|
8937
9626
|
}
|
|
8938
9627
|
|
|
8939
9628
|
// src/hooks-config.ts
|
|
8940
|
-
import * as
|
|
8941
|
-
import * as
|
|
9629
|
+
import * as fs4 from "node:fs/promises";
|
|
9630
|
+
import * as path5 from "node:path";
|
|
8942
9631
|
import { homedir as homedir2 } from "node:os";
|
|
8943
9632
|
var PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
|
|
8944
9633
|
"""
|
|
@@ -9108,26 +9797,102 @@ def main():
|
|
|
9108
9797
|
print(json.dumps({"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": REMINDER}}))
|
|
9109
9798
|
sys.exit(0)
|
|
9110
9799
|
|
|
9800
|
+
if __name__ == "__main__":
|
|
9801
|
+
main()
|
|
9802
|
+
`;
|
|
9803
|
+
var PRECOMPACT_HOOK_SCRIPT = `#!/usr/bin/env python3
|
|
9804
|
+
"""
|
|
9805
|
+
ContextStream PreCompact Hook for Claude Code
|
|
9806
|
+
|
|
9807
|
+
Runs BEFORE conversation context is compacted (manual via /compact or automatic).
|
|
9808
|
+
Injects a reminder for the AI to save conversation state using session_capture_smart.
|
|
9809
|
+
|
|
9810
|
+
Input (via stdin):
|
|
9811
|
+
{
|
|
9812
|
+
"session_id": "...",
|
|
9813
|
+
"transcript_path": "/path/to/transcript.jsonl",
|
|
9814
|
+
"permission_mode": "default",
|
|
9815
|
+
"hook_event_name": "PreCompact",
|
|
9816
|
+
"trigger": "manual" | "auto",
|
|
9817
|
+
"custom_instructions": "..."
|
|
9818
|
+
}
|
|
9819
|
+
|
|
9820
|
+
Output (to stdout):
|
|
9821
|
+
{
|
|
9822
|
+
"hookSpecificOutput": {
|
|
9823
|
+
"hookEventName": "PreCompact",
|
|
9824
|
+
"additionalContext": "... instructions for AI ..."
|
|
9825
|
+
}
|
|
9826
|
+
}
|
|
9827
|
+
"""
|
|
9828
|
+
|
|
9829
|
+
import json
|
|
9830
|
+
import sys
|
|
9831
|
+
import os
|
|
9832
|
+
|
|
9833
|
+
ENABLED = os.environ.get("CONTEXTSTREAM_PRECOMPACT_ENABLED", "true").lower() == "true"
|
|
9834
|
+
|
|
9835
|
+
def main():
|
|
9836
|
+
if not ENABLED:
|
|
9837
|
+
sys.exit(0)
|
|
9838
|
+
|
|
9839
|
+
try:
|
|
9840
|
+
data = json.load(sys.stdin)
|
|
9841
|
+
except:
|
|
9842
|
+
sys.exit(0)
|
|
9843
|
+
|
|
9844
|
+
trigger = data.get("trigger", "unknown")
|
|
9845
|
+
custom_instructions = data.get("custom_instructions", "")
|
|
9846
|
+
|
|
9847
|
+
# Build context injection for the AI
|
|
9848
|
+
context = f"""[CONTEXT COMPACTION IMMINENT - {trigger.upper()}]
|
|
9849
|
+
CRITICAL: Before context is compacted, you MUST save conversation state:
|
|
9850
|
+
|
|
9851
|
+
1. IMMEDIATELY call: mcp__contextstream__session(action="capture", event_type="session_snapshot", title="Pre-compaction snapshot", content="<JSON with: conversation_summary, active_goals, recent_decisions, active_files, unfinished_work>")
|
|
9852
|
+
|
|
9853
|
+
2. Include in the snapshot:
|
|
9854
|
+
- conversation_summary: Brief summary of what was discussed
|
|
9855
|
+
- active_goals: List of goals/tasks in progress
|
|
9856
|
+
- recent_decisions: Key decisions made in this session
|
|
9857
|
+
- active_files: Files currently being worked on
|
|
9858
|
+
- unfinished_work: Any incomplete tasks
|
|
9859
|
+
|
|
9860
|
+
3. After compaction, call session_init(is_post_compact=true) to restore context.
|
|
9861
|
+
|
|
9862
|
+
{f"User instructions: {custom_instructions}" if custom_instructions else ""}
|
|
9863
|
+
[END COMPACTION WARNING]"""
|
|
9864
|
+
|
|
9865
|
+
output = {
|
|
9866
|
+
"hookSpecificOutput": {
|
|
9867
|
+
"hookEventName": "PreCompact",
|
|
9868
|
+
"additionalContext": context
|
|
9869
|
+
}
|
|
9870
|
+
}
|
|
9871
|
+
|
|
9872
|
+
print(json.dumps(output))
|
|
9873
|
+
sys.exit(0)
|
|
9874
|
+
|
|
9111
9875
|
if __name__ == "__main__":
|
|
9112
9876
|
main()
|
|
9113
9877
|
`;
|
|
9114
9878
|
function getClaudeSettingsPath(scope, projectPath) {
|
|
9115
9879
|
if (scope === "user") {
|
|
9116
|
-
return
|
|
9880
|
+
return path5.join(homedir2(), ".claude", "settings.json");
|
|
9117
9881
|
}
|
|
9118
9882
|
if (!projectPath) {
|
|
9119
9883
|
throw new Error("projectPath required for project scope");
|
|
9120
9884
|
}
|
|
9121
|
-
return
|
|
9885
|
+
return path5.join(projectPath, ".claude", "settings.json");
|
|
9122
9886
|
}
|
|
9123
9887
|
function getHooksDir() {
|
|
9124
|
-
return
|
|
9888
|
+
return path5.join(homedir2(), ".claude", "hooks");
|
|
9125
9889
|
}
|
|
9126
|
-
function buildHooksConfig() {
|
|
9890
|
+
function buildHooksConfig(options) {
|
|
9127
9891
|
const hooksDir = getHooksDir();
|
|
9128
|
-
const preToolUsePath =
|
|
9129
|
-
const userPromptPath =
|
|
9130
|
-
|
|
9892
|
+
const preToolUsePath = path5.join(hooksDir, "contextstream-redirect.py");
|
|
9893
|
+
const userPromptPath = path5.join(hooksDir, "contextstream-reminder.py");
|
|
9894
|
+
const preCompactPath = path5.join(hooksDir, "contextstream-precompact.py");
|
|
9895
|
+
const config = {
|
|
9131
9896
|
PreToolUse: [
|
|
9132
9897
|
{
|
|
9133
9898
|
matcher: "Glob|Grep|Search|Task|EnterPlanMode",
|
|
@@ -9153,20 +9918,45 @@ function buildHooksConfig() {
|
|
|
9153
9918
|
}
|
|
9154
9919
|
]
|
|
9155
9920
|
};
|
|
9921
|
+
if (options?.includePreCompact) {
|
|
9922
|
+
config.PreCompact = [
|
|
9923
|
+
{
|
|
9924
|
+
// Match both manual (/compact) and automatic compaction
|
|
9925
|
+
matcher: "*",
|
|
9926
|
+
hooks: [
|
|
9927
|
+
{
|
|
9928
|
+
type: "command",
|
|
9929
|
+
command: `python3 "${preCompactPath}"`,
|
|
9930
|
+
timeout: 10
|
|
9931
|
+
}
|
|
9932
|
+
]
|
|
9933
|
+
}
|
|
9934
|
+
];
|
|
9935
|
+
}
|
|
9936
|
+
return config;
|
|
9156
9937
|
}
|
|
9157
|
-
async function installHookScripts() {
|
|
9938
|
+
async function installHookScripts(options) {
|
|
9158
9939
|
const hooksDir = getHooksDir();
|
|
9159
|
-
await
|
|
9160
|
-
const preToolUsePath =
|
|
9161
|
-
const userPromptPath =
|
|
9162
|
-
|
|
9163
|
-
await
|
|
9164
|
-
|
|
9940
|
+
await fs4.mkdir(hooksDir, { recursive: true });
|
|
9941
|
+
const preToolUsePath = path5.join(hooksDir, "contextstream-redirect.py");
|
|
9942
|
+
const userPromptPath = path5.join(hooksDir, "contextstream-reminder.py");
|
|
9943
|
+
const preCompactPath = path5.join(hooksDir, "contextstream-precompact.py");
|
|
9944
|
+
await fs4.writeFile(preToolUsePath, PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
|
|
9945
|
+
await fs4.writeFile(userPromptPath, USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
|
|
9946
|
+
const result = {
|
|
9947
|
+
preToolUse: preToolUsePath,
|
|
9948
|
+
userPrompt: userPromptPath
|
|
9949
|
+
};
|
|
9950
|
+
if (options?.includePreCompact) {
|
|
9951
|
+
await fs4.writeFile(preCompactPath, PRECOMPACT_HOOK_SCRIPT, { mode: 493 });
|
|
9952
|
+
result.preCompact = preCompactPath;
|
|
9953
|
+
}
|
|
9954
|
+
return result;
|
|
9165
9955
|
}
|
|
9166
9956
|
async function readClaudeSettings(scope, projectPath) {
|
|
9167
9957
|
const settingsPath = getClaudeSettingsPath(scope, projectPath);
|
|
9168
9958
|
try {
|
|
9169
|
-
const content = await
|
|
9959
|
+
const content = await fs4.readFile(settingsPath, "utf-8");
|
|
9170
9960
|
return JSON.parse(content);
|
|
9171
9961
|
} catch {
|
|
9172
9962
|
return {};
|
|
@@ -9174,9 +9964,9 @@ async function readClaudeSettings(scope, projectPath) {
|
|
|
9174
9964
|
}
|
|
9175
9965
|
async function writeClaudeSettings(settings, scope, projectPath) {
|
|
9176
9966
|
const settingsPath = getClaudeSettingsPath(scope, projectPath);
|
|
9177
|
-
const dir =
|
|
9178
|
-
await
|
|
9179
|
-
await
|
|
9967
|
+
const dir = path5.dirname(settingsPath);
|
|
9968
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
9969
|
+
await fs4.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
9180
9970
|
}
|
|
9181
9971
|
function mergeHooksIntoSettings(existingSettings, newHooks) {
|
|
9182
9972
|
const settings = { ...existingSettings };
|
|
@@ -9195,16 +9985,22 @@ function mergeHooksIntoSettings(existingSettings, newHooks) {
|
|
|
9195
9985
|
async function installClaudeCodeHooks(options) {
|
|
9196
9986
|
const result = { scripts: [], settings: [] };
|
|
9197
9987
|
if (!options.dryRun) {
|
|
9198
|
-
const scripts = await installHookScripts();
|
|
9988
|
+
const scripts = await installHookScripts({ includePreCompact: options.includePreCompact });
|
|
9199
9989
|
result.scripts.push(scripts.preToolUse, scripts.userPrompt);
|
|
9990
|
+
if (scripts.preCompact) {
|
|
9991
|
+
result.scripts.push(scripts.preCompact);
|
|
9992
|
+
}
|
|
9200
9993
|
} else {
|
|
9201
9994
|
const hooksDir = getHooksDir();
|
|
9202
9995
|
result.scripts.push(
|
|
9203
|
-
|
|
9204
|
-
|
|
9996
|
+
path5.join(hooksDir, "contextstream-redirect.py"),
|
|
9997
|
+
path5.join(hooksDir, "contextstream-reminder.py")
|
|
9205
9998
|
);
|
|
9999
|
+
if (options.includePreCompact) {
|
|
10000
|
+
result.scripts.push(path5.join(hooksDir, "contextstream-precompact.py"));
|
|
10001
|
+
}
|
|
9206
10002
|
}
|
|
9207
|
-
const hooksConfig = buildHooksConfig();
|
|
10003
|
+
const hooksConfig = buildHooksConfig({ includePreCompact: options.includePreCompact });
|
|
9208
10004
|
if (options.scope === "user" || options.scope === "both") {
|
|
9209
10005
|
const settingsPath = getClaudeSettingsPath("user");
|
|
9210
10006
|
if (!options.dryRun) {
|
|
@@ -9226,12 +10022,12 @@ async function installClaudeCodeHooks(options) {
|
|
|
9226
10022
|
return result;
|
|
9227
10023
|
}
|
|
9228
10024
|
function getIndexStatusPath() {
|
|
9229
|
-
return
|
|
10025
|
+
return path5.join(homedir2(), ".contextstream", "indexed-projects.json");
|
|
9230
10026
|
}
|
|
9231
10027
|
async function readIndexStatus() {
|
|
9232
10028
|
const statusPath = getIndexStatusPath();
|
|
9233
10029
|
try {
|
|
9234
|
-
const content = await
|
|
10030
|
+
const content = await fs4.readFile(statusPath, "utf-8");
|
|
9235
10031
|
return JSON.parse(content);
|
|
9236
10032
|
} catch {
|
|
9237
10033
|
return { version: 1, projects: {} };
|
|
@@ -9239,13 +10035,13 @@ async function readIndexStatus() {
|
|
|
9239
10035
|
}
|
|
9240
10036
|
async function writeIndexStatus(status) {
|
|
9241
10037
|
const statusPath = getIndexStatusPath();
|
|
9242
|
-
const dir =
|
|
9243
|
-
await
|
|
9244
|
-
await
|
|
10038
|
+
const dir = path5.dirname(statusPath);
|
|
10039
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
10040
|
+
await fs4.writeFile(statusPath, JSON.stringify(status, null, 2));
|
|
9245
10041
|
}
|
|
9246
10042
|
async function markProjectIndexed(projectPath, options) {
|
|
9247
10043
|
const status = await readIndexStatus();
|
|
9248
|
-
const resolvedPath =
|
|
10044
|
+
const resolvedPath = path5.resolve(projectPath);
|
|
9249
10045
|
status.projects[resolvedPath] = {
|
|
9250
10046
|
indexed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9251
10047
|
project_id: options?.project_id,
|
|
@@ -9431,15 +10227,15 @@ var RULES_PROJECT_FILES = {
|
|
|
9431
10227
|
cursor: ".cursorrules",
|
|
9432
10228
|
windsurf: ".windsurfrules",
|
|
9433
10229
|
cline: ".clinerules",
|
|
9434
|
-
kilo:
|
|
9435
|
-
roo:
|
|
10230
|
+
kilo: path6.join(".kilocode", "rules", "contextstream.md"),
|
|
10231
|
+
roo: path6.join(".roo", "rules", "contextstream.md"),
|
|
9436
10232
|
aider: ".aider.conf.yml"
|
|
9437
10233
|
};
|
|
9438
10234
|
var RULES_GLOBAL_FILES = {
|
|
9439
|
-
codex: [
|
|
9440
|
-
windsurf: [
|
|
9441
|
-
kilo: [
|
|
9442
|
-
roo: [
|
|
10235
|
+
codex: [path6.join(homedir3(), ".codex", "AGENTS.md")],
|
|
10236
|
+
windsurf: [path6.join(homedir3(), ".codeium", "windsurf", "memories", "global_rules.md")],
|
|
10237
|
+
kilo: [path6.join(homedir3(), ".kilocode", "rules", "contextstream.md")],
|
|
10238
|
+
roo: [path6.join(homedir3(), ".roo", "rules", "contextstream.md")]
|
|
9443
10239
|
};
|
|
9444
10240
|
var rulesNoticeCache = /* @__PURE__ */ new Map();
|
|
9445
10241
|
function compareVersions2(v1, v2) {
|
|
@@ -9492,7 +10288,7 @@ function resolveRulesCandidatePaths(folderPath, editorKey) {
|
|
|
9492
10288
|
if (!folderPath) return;
|
|
9493
10289
|
const rel = RULES_PROJECT_FILES[key];
|
|
9494
10290
|
if (rel) {
|
|
9495
|
-
candidates.add(
|
|
10291
|
+
candidates.add(path6.join(folderPath, rel));
|
|
9496
10292
|
}
|
|
9497
10293
|
};
|
|
9498
10294
|
const addGlobal = (key) => {
|
|
@@ -9524,7 +10320,7 @@ function resolveFolderPath(inputPath, sessionManager) {
|
|
|
9524
10320
|
const indicators = [".git", "package.json", "Cargo.toml", "pyproject.toml", ".contextstream"];
|
|
9525
10321
|
const hasIndicator = indicators.some((entry) => {
|
|
9526
10322
|
try {
|
|
9527
|
-
return
|
|
10323
|
+
return fs5.existsSync(path6.join(cwd, entry));
|
|
9528
10324
|
} catch {
|
|
9529
10325
|
return false;
|
|
9530
10326
|
}
|
|
@@ -9543,7 +10339,7 @@ function getRulesNotice(folderPath, clientName) {
|
|
|
9543
10339
|
return cached.notice;
|
|
9544
10340
|
}
|
|
9545
10341
|
const candidates = resolveRulesCandidatePaths(folderPath, editorKey);
|
|
9546
|
-
const existing = candidates.filter((filePath) =>
|
|
10342
|
+
const existing = candidates.filter((filePath) => fs5.existsSync(filePath));
|
|
9547
10343
|
if (existing.length === 0) {
|
|
9548
10344
|
const updateCommand2 = "generate_rules()";
|
|
9549
10345
|
const notice2 = {
|
|
@@ -9565,7 +10361,7 @@ function getRulesNotice(folderPath, clientName) {
|
|
|
9565
10361
|
const versions = [];
|
|
9566
10362
|
for (const filePath of existing) {
|
|
9567
10363
|
try {
|
|
9568
|
-
const content =
|
|
10364
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
9569
10365
|
const version = extractRulesVersion(content);
|
|
9570
10366
|
if (!version) {
|
|
9571
10367
|
filesMissingVersion.push(filePath);
|
|
@@ -9758,23 +10554,23 @@ function replaceContextStreamBlock(existing, content) {
|
|
|
9758
10554
|
return { content: appended, status: "appended" };
|
|
9759
10555
|
}
|
|
9760
10556
|
async function upsertRuleFile(filePath, content) {
|
|
9761
|
-
await
|
|
10557
|
+
await fs5.promises.mkdir(path6.dirname(filePath), { recursive: true });
|
|
9762
10558
|
const wrappedContent = wrapWithMarkers(content);
|
|
9763
10559
|
let existing = "";
|
|
9764
10560
|
try {
|
|
9765
|
-
existing = await
|
|
10561
|
+
existing = await fs5.promises.readFile(filePath, "utf8");
|
|
9766
10562
|
} catch {
|
|
9767
10563
|
}
|
|
9768
10564
|
if (!existing) {
|
|
9769
|
-
await
|
|
10565
|
+
await fs5.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
9770
10566
|
return "created";
|
|
9771
10567
|
}
|
|
9772
10568
|
if (!existing.trim()) {
|
|
9773
|
-
await
|
|
10569
|
+
await fs5.promises.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
9774
10570
|
return "updated";
|
|
9775
10571
|
}
|
|
9776
10572
|
const replaced = replaceContextStreamBlock(existing, content);
|
|
9777
|
-
await
|
|
10573
|
+
await fs5.promises.writeFile(filePath, replaced.content, "utf8");
|
|
9778
10574
|
return replaced.status;
|
|
9779
10575
|
}
|
|
9780
10576
|
async function writeEditorRules(options) {
|
|
@@ -9792,8 +10588,8 @@ async function writeEditorRules(options) {
|
|
|
9792
10588
|
results.push({ editor, filename: "", status: "unknown editor" });
|
|
9793
10589
|
continue;
|
|
9794
10590
|
}
|
|
9795
|
-
const filePath =
|
|
9796
|
-
if (
|
|
10591
|
+
const filePath = path6.join(options.folderPath, rule.filename);
|
|
10592
|
+
if (fs5.existsSync(filePath) && !options.overwriteExisting) {
|
|
9797
10593
|
results.push({ editor, filename: rule.filename, status: "skipped (exists)" });
|
|
9798
10594
|
continue;
|
|
9799
10595
|
}
|
|
@@ -9849,7 +10645,7 @@ async function writeGlobalRules(options) {
|
|
|
9849
10645
|
continue;
|
|
9850
10646
|
}
|
|
9851
10647
|
for (const filePath of globalPaths) {
|
|
9852
|
-
if (
|
|
10648
|
+
if (fs5.existsSync(filePath) && !options.overwriteExisting) {
|
|
9853
10649
|
results.push({ editor, filename: filePath, status: "skipped (exists)", scope: "global" });
|
|
9854
10650
|
continue;
|
|
9855
10651
|
}
|
|
@@ -9942,9 +10738,9 @@ function humanizeKey(raw) {
|
|
|
9942
10738
|
const withSpaces = raw.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ");
|
|
9943
10739
|
return withSpaces.toLowerCase();
|
|
9944
10740
|
}
|
|
9945
|
-
function buildParamDescription(key,
|
|
10741
|
+
function buildParamDescription(key, path9) {
|
|
9946
10742
|
const normalized = key in DEFAULT_PARAM_DESCRIPTIONS ? key : key.toLowerCase();
|
|
9947
|
-
const parent =
|
|
10743
|
+
const parent = path9[path9.length - 1];
|
|
9948
10744
|
if (parent === "target") {
|
|
9949
10745
|
if (key === "id") return "Target identifier (module path, function id, etc.).";
|
|
9950
10746
|
if (key === "type") return "Target type (module, file, function, type, variable).";
|
|
@@ -9975,7 +10771,7 @@ function getDescription(schema) {
|
|
|
9975
10771
|
if (def?.description && def.description.trim()) return def.description;
|
|
9976
10772
|
return void 0;
|
|
9977
10773
|
}
|
|
9978
|
-
function applyParamDescriptions(schema,
|
|
10774
|
+
function applyParamDescriptions(schema, path9 = []) {
|
|
9979
10775
|
if (!(schema instanceof external_exports.ZodObject)) {
|
|
9980
10776
|
return schema;
|
|
9981
10777
|
}
|
|
@@ -9986,7 +10782,7 @@ function applyParamDescriptions(schema, path8 = []) {
|
|
|
9986
10782
|
let nextField = field;
|
|
9987
10783
|
const existingDescription = getDescription(field);
|
|
9988
10784
|
if (field instanceof external_exports.ZodObject) {
|
|
9989
|
-
const nested = applyParamDescriptions(field, [...
|
|
10785
|
+
const nested = applyParamDescriptions(field, [...path9, key]);
|
|
9990
10786
|
if (nested !== field) {
|
|
9991
10787
|
nextField = nested;
|
|
9992
10788
|
changed = true;
|
|
@@ -9998,7 +10794,7 @@ function applyParamDescriptions(schema, path8 = []) {
|
|
|
9998
10794
|
changed = true;
|
|
9999
10795
|
}
|
|
10000
10796
|
} else {
|
|
10001
|
-
nextField = nextField.describe(buildParamDescription(key,
|
|
10797
|
+
nextField = nextField.describe(buildParamDescription(key, path9));
|
|
10002
10798
|
changed = true;
|
|
10003
10799
|
}
|
|
10004
10800
|
nextShape[key] = nextField;
|
|
@@ -10054,13 +10850,17 @@ function resolveAuthOverride(extra) {
|
|
|
10054
10850
|
return { workspaceId, projectId };
|
|
10055
10851
|
}
|
|
10056
10852
|
var LIGHT_TOOLSET = /* @__PURE__ */ new Set([
|
|
10057
|
-
// Core session tools (
|
|
10853
|
+
// Core session tools (15)
|
|
10058
10854
|
"session_init",
|
|
10059
10855
|
"session_tools",
|
|
10060
10856
|
"context_smart",
|
|
10061
10857
|
"context_feedback",
|
|
10062
10858
|
"session_summary",
|
|
10063
10859
|
"session_capture",
|
|
10860
|
+
"session_capture_smart",
|
|
10861
|
+
// Pre-compaction state capture
|
|
10862
|
+
"session_restore_context",
|
|
10863
|
+
// Post-compaction context restore
|
|
10064
10864
|
"session_capture_lesson",
|
|
10065
10865
|
"session_get_lessons",
|
|
10066
10866
|
"session_recall",
|
|
@@ -10100,13 +10900,17 @@ var LIGHT_TOOLSET = /* @__PURE__ */ new Set([
|
|
|
10100
10900
|
"mcp_server_version"
|
|
10101
10901
|
]);
|
|
10102
10902
|
var STANDARD_TOOLSET = /* @__PURE__ */ new Set([
|
|
10103
|
-
// Core session tools (
|
|
10903
|
+
// Core session tools (16)
|
|
10104
10904
|
"session_init",
|
|
10105
10905
|
"session_tools",
|
|
10106
10906
|
"context_smart",
|
|
10107
10907
|
"context_feedback",
|
|
10108
10908
|
"session_summary",
|
|
10109
10909
|
"session_capture",
|
|
10910
|
+
"session_capture_smart",
|
|
10911
|
+
// Pre-compaction state capture
|
|
10912
|
+
"session_restore_context",
|
|
10913
|
+
// Post-compaction context restore
|
|
10110
10914
|
"session_capture_lesson",
|
|
10111
10915
|
"session_get_lessons",
|
|
10112
10916
|
"session_recall",
|
|
@@ -10557,6 +11361,7 @@ function parsePositiveInt(raw, fallback) {
|
|
|
10557
11361
|
}
|
|
10558
11362
|
var OUTPUT_FORMAT = process.env.CONTEXTSTREAM_OUTPUT_FORMAT || "compact";
|
|
10559
11363
|
var COMPACT_OUTPUT = OUTPUT_FORMAT === "compact";
|
|
11364
|
+
var SHOW_TIMING = process.env.CONTEXTSTREAM_SHOW_TIMING === "true" || process.env.CONTEXTSTREAM_SHOW_TIMING === "1";
|
|
10560
11365
|
var DEFAULT_SEARCH_LIMIT = parsePositiveInt(process.env.CONTEXTSTREAM_SEARCH_LIMIT, 3);
|
|
10561
11366
|
var DEFAULT_SEARCH_CONTENT_MAX_CHARS = parsePositiveInt(
|
|
10562
11367
|
process.env.CONTEXTSTREAM_SEARCH_MAX_CHARS,
|
|
@@ -10669,6 +11474,31 @@ function toStructured(data) {
|
|
|
10669
11474
|
}
|
|
10670
11475
|
return void 0;
|
|
10671
11476
|
}
|
|
11477
|
+
function formatTimingSummary(roundTripMs, resultCount) {
|
|
11478
|
+
if (!SHOW_TIMING) return "";
|
|
11479
|
+
const countStr = resultCount !== void 0 ? `${resultCount} results` : "done";
|
|
11480
|
+
return `\u2713 ${countStr} in ${roundTripMs}ms
|
|
11481
|
+
|
|
11482
|
+
`;
|
|
11483
|
+
}
|
|
11484
|
+
function getResultCount(data) {
|
|
11485
|
+
if (!data || typeof data !== "object") return void 0;
|
|
11486
|
+
const response = data;
|
|
11487
|
+
const dataObj = response.data;
|
|
11488
|
+
if (dataObj?.results && Array.isArray(dataObj.results)) {
|
|
11489
|
+
return dataObj.results.length;
|
|
11490
|
+
}
|
|
11491
|
+
if (typeof dataObj?.total === "number") {
|
|
11492
|
+
return dataObj.total;
|
|
11493
|
+
}
|
|
11494
|
+
if (typeof dataObj?.count === "number") {
|
|
11495
|
+
return dataObj.count;
|
|
11496
|
+
}
|
|
11497
|
+
if (dataObj?.paths && Array.isArray(dataObj.paths)) {
|
|
11498
|
+
return dataObj.paths.length;
|
|
11499
|
+
}
|
|
11500
|
+
return void 0;
|
|
11501
|
+
}
|
|
10672
11502
|
function readStatNumber(payload, key) {
|
|
10673
11503
|
if (!payload || typeof payload !== "object") return void 0;
|
|
10674
11504
|
const direct = payload[key];
|
|
@@ -11334,10 +12164,10 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
|
|
|
11334
12164
|
);
|
|
11335
12165
|
}
|
|
11336
12166
|
async function validateReadableDirectory(inputPath) {
|
|
11337
|
-
const resolvedPath =
|
|
12167
|
+
const resolvedPath = path6.resolve(inputPath);
|
|
11338
12168
|
let stats;
|
|
11339
12169
|
try {
|
|
11340
|
-
stats = await
|
|
12170
|
+
stats = await fs5.promises.stat(resolvedPath);
|
|
11341
12171
|
} catch (error) {
|
|
11342
12172
|
if (error?.code === "ENOENT") {
|
|
11343
12173
|
return { ok: false, error: `Error: path does not exist: ${inputPath}` };
|
|
@@ -11351,7 +12181,7 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
|
|
|
11351
12181
|
return { ok: false, error: `Error: path is not a directory: ${inputPath}` };
|
|
11352
12182
|
}
|
|
11353
12183
|
try {
|
|
11354
|
-
await
|
|
12184
|
+
await fs5.promises.access(resolvedPath, fs5.constants.R_OK | fs5.constants.X_OK);
|
|
11355
12185
|
} catch (error) {
|
|
11356
12186
|
return {
|
|
11357
12187
|
ok: false,
|
|
@@ -11774,17 +12604,17 @@ Access: Free`,
|
|
|
11774
12604
|
let rulesSkipped = [];
|
|
11775
12605
|
if (input.folder_path && projectData.id) {
|
|
11776
12606
|
try {
|
|
11777
|
-
const configDir =
|
|
11778
|
-
const configPath =
|
|
11779
|
-
if (!
|
|
11780
|
-
|
|
12607
|
+
const configDir = path6.join(input.folder_path, ".contextstream");
|
|
12608
|
+
const configPath = path6.join(configDir, "config.json");
|
|
12609
|
+
if (!fs5.existsSync(configDir)) {
|
|
12610
|
+
fs5.mkdirSync(configDir, { recursive: true });
|
|
11781
12611
|
}
|
|
11782
12612
|
const config = {
|
|
11783
12613
|
workspace_id: workspaceId,
|
|
11784
12614
|
project_id: projectData.id,
|
|
11785
12615
|
project_name: input.name
|
|
11786
12616
|
};
|
|
11787
|
-
|
|
12617
|
+
fs5.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
11788
12618
|
if (input.generate_editor_rules) {
|
|
11789
12619
|
const ruleResults = await writeEditorRules({
|
|
11790
12620
|
folderPath: input.folder_path,
|
|
@@ -12989,10 +13819,17 @@ This does semantic search on the first message. You only need context_smart on s
|
|
|
12989
13819
|
auto_index: external_exports.boolean().optional().describe("Automatically create and index project from IDE workspace (default: true)"),
|
|
12990
13820
|
allow_no_workspace: external_exports.boolean().optional().describe(
|
|
12991
13821
|
"If true, allow session_init to return connected even if no workspace is resolved (workspace-level tools may not work)."
|
|
13822
|
+
),
|
|
13823
|
+
skip_project_creation: external_exports.boolean().optional().describe(
|
|
13824
|
+
"If true, skip automatic project creation/matching. Use for parent folders containing multiple projects where you want workspace-level context but no project-specific context."
|
|
13825
|
+
),
|
|
13826
|
+
is_post_compact: external_exports.boolean().optional().describe(
|
|
13827
|
+
"Set to true when resuming after conversation compaction. This prioritizes session_snapshot restoration and recent decisions."
|
|
12992
13828
|
)
|
|
12993
13829
|
})
|
|
12994
13830
|
},
|
|
12995
13831
|
async (input) => {
|
|
13832
|
+
const startTime = Date.now();
|
|
12996
13833
|
let ideRoots = [];
|
|
12997
13834
|
try {
|
|
12998
13835
|
const rootsResponse = await server.server.listRoots();
|
|
@@ -13008,8 +13845,48 @@ This does semantic search on the first message. You only need context_smart on s
|
|
|
13008
13845
|
}
|
|
13009
13846
|
const result = await client.initSession(input, ideRoots);
|
|
13010
13847
|
result.tools_hint = getCoreToolsHint();
|
|
13848
|
+
if (input.is_post_compact) {
|
|
13849
|
+
const workspaceIdForRestore = typeof result.workspace_id === "string" ? result.workspace_id : void 0;
|
|
13850
|
+
const projectIdForRestore = typeof result.project_id === "string" ? result.project_id : void 0;
|
|
13851
|
+
if (workspaceIdForRestore) {
|
|
13852
|
+
try {
|
|
13853
|
+
const snapshotSearch = await client.searchEvents({
|
|
13854
|
+
workspace_id: workspaceIdForRestore,
|
|
13855
|
+
project_id: projectIdForRestore,
|
|
13856
|
+
query: "session_snapshot",
|
|
13857
|
+
event_types: ["session_snapshot"],
|
|
13858
|
+
limit: 1
|
|
13859
|
+
});
|
|
13860
|
+
const snapshots = snapshotSearch?.data?.results || snapshotSearch?.results || snapshotSearch?.data || [];
|
|
13861
|
+
if (snapshots && snapshots.length > 0) {
|
|
13862
|
+
const latestSnapshot = snapshots[0];
|
|
13863
|
+
let snapshotData;
|
|
13864
|
+
try {
|
|
13865
|
+
snapshotData = JSON.parse(latestSnapshot.content);
|
|
13866
|
+
} catch {
|
|
13867
|
+
snapshotData = { conversation_summary: latestSnapshot.content };
|
|
13868
|
+
}
|
|
13869
|
+
result.restored_context = {
|
|
13870
|
+
snapshot_id: latestSnapshot.id,
|
|
13871
|
+
captured_at: snapshotData.captured_at || latestSnapshot.created_at,
|
|
13872
|
+
...snapshotData
|
|
13873
|
+
};
|
|
13874
|
+
result.is_post_compact = true;
|
|
13875
|
+
result.post_compact_hint = "Session restored from pre-compaction snapshot. Review the 'restored_context' to continue where you left off.";
|
|
13876
|
+
} else {
|
|
13877
|
+
result.is_post_compact = true;
|
|
13878
|
+
result.post_compact_hint = "Post-compaction session started, but no snapshots found. Use context_smart to retrieve relevant context.";
|
|
13879
|
+
}
|
|
13880
|
+
} catch (err) {
|
|
13881
|
+
console.error("[ContextStream] Failed to restore post-compact context:", err);
|
|
13882
|
+
result.is_post_compact = true;
|
|
13883
|
+
result.post_compact_hint = "Post-compaction session started. Snapshot restoration failed, use context_smart for context.";
|
|
13884
|
+
}
|
|
13885
|
+
}
|
|
13886
|
+
}
|
|
13011
13887
|
if (sessionManager) {
|
|
13012
13888
|
sessionManager.markInitialized(result);
|
|
13889
|
+
sessionManager.resetTokenCount();
|
|
13013
13890
|
}
|
|
13014
13891
|
const folderPathForRules = input.folder_path || ideRoots[0] || resolveFolderPath(void 0, sessionManager);
|
|
13015
13892
|
if (sessionManager && folderPathForRules) {
|
|
@@ -13073,7 +13950,7 @@ This does semantic search on the first message. You only need context_smart on s
|
|
|
13073
13950
|
formatContent(result)
|
|
13074
13951
|
].join("\n");
|
|
13075
13952
|
} else if (status === "requires_workspace_selection") {
|
|
13076
|
-
const folderName = typeof result.folder_name === "string" ? result.folder_name : typeof input.folder_path === "string" ?
|
|
13953
|
+
const folderName = typeof result.folder_name === "string" ? result.folder_name : typeof input.folder_path === "string" ? path6.basename(input.folder_path) || "this folder" : "this folder";
|
|
13077
13954
|
const candidates = Array.isArray(result.workspace_candidates) ? result.workspace_candidates : [];
|
|
13078
13955
|
const lines = [];
|
|
13079
13956
|
lines.push(
|
|
@@ -13156,6 +14033,12 @@ ${noticeLines.filter(Boolean).join("\n")}`;
|
|
|
13156
14033
|
text = `${text}
|
|
13157
14034
|
|
|
13158
14035
|
${SEARCH_RULES_REMINDER}`;
|
|
14036
|
+
}
|
|
14037
|
+
const roundTripMs = Date.now() - startTime;
|
|
14038
|
+
if (SHOW_TIMING) {
|
|
14039
|
+
text = `\u2713 session initialized in ${roundTripMs}ms
|
|
14040
|
+
|
|
14041
|
+
${text}`;
|
|
13159
14042
|
}
|
|
13160
14043
|
return {
|
|
13161
14044
|
content: [{ type: "text", text }],
|
|
@@ -13325,7 +14208,7 @@ Behavior:
|
|
|
13325
14208
|
"Error: folder_path is required. Provide folder_path or run from a project directory."
|
|
13326
14209
|
);
|
|
13327
14210
|
}
|
|
13328
|
-
const folderName =
|
|
14211
|
+
const folderName = path6.basename(folderPath) || "My Project";
|
|
13329
14212
|
let newWorkspace;
|
|
13330
14213
|
try {
|
|
13331
14214
|
newWorkspace = await client.createWorkspace({
|
|
@@ -13433,8 +14316,11 @@ Use this to persist decisions, insights, preferences, or important information.`
|
|
|
13433
14316
|
// Extracted lesson from correction
|
|
13434
14317
|
"warning",
|
|
13435
14318
|
// Proactive reminder
|
|
13436
|
-
"frustration"
|
|
14319
|
+
"frustration",
|
|
13437
14320
|
// User expressed frustration
|
|
14321
|
+
// Compaction awareness
|
|
14322
|
+
"session_snapshot"
|
|
14323
|
+
// Pre-compaction state capture
|
|
13438
14324
|
]).describe("Type of context being captured"),
|
|
13439
14325
|
title: external_exports.string().describe("Brief title for the captured context"),
|
|
13440
14326
|
content: external_exports.string().describe("Full content/details to capture"),
|
|
@@ -13489,6 +14375,224 @@ Use this to persist decisions, insights, preferences, or important information.`
|
|
|
13489
14375
|
};
|
|
13490
14376
|
}
|
|
13491
14377
|
);
|
|
14378
|
+
registerTool(
|
|
14379
|
+
"session_capture_smart",
|
|
14380
|
+
{
|
|
14381
|
+
title: "Smart capture for conversation compaction",
|
|
14382
|
+
description: `Intelligently capture conversation state before compaction or context loss.
|
|
14383
|
+
This creates a session_snapshot that can be restored after compaction.
|
|
14384
|
+
|
|
14385
|
+
Use when:
|
|
14386
|
+
- Context pressure is high/critical (context_smart returns threshold_warning)
|
|
14387
|
+
- Before manual /compact commands
|
|
14388
|
+
- When significant work progress needs preservation
|
|
14389
|
+
|
|
14390
|
+
Captures:
|
|
14391
|
+
- Conversation summary and current goals
|
|
14392
|
+
- Active files being worked on
|
|
14393
|
+
- Recent decisions with rationale
|
|
14394
|
+
- Unfinished work items
|
|
14395
|
+
- User preferences expressed in session
|
|
14396
|
+
|
|
14397
|
+
The snapshot is automatically prioritized during post-compaction session_init.`,
|
|
14398
|
+
inputSchema: external_exports.object({
|
|
14399
|
+
workspace_id: external_exports.string().uuid().optional(),
|
|
14400
|
+
project_id: external_exports.string().uuid().optional(),
|
|
14401
|
+
conversation_summary: external_exports.string().describe("AI's summary of the conversation so far - what was discussed and accomplished"),
|
|
14402
|
+
current_goal: external_exports.string().optional().describe("The primary goal or task being worked on"),
|
|
14403
|
+
active_files: external_exports.array(external_exports.string()).optional().describe("List of files currently being worked on"),
|
|
14404
|
+
recent_decisions: external_exports.array(
|
|
14405
|
+
external_exports.object({
|
|
14406
|
+
title: external_exports.string(),
|
|
14407
|
+
rationale: external_exports.string().optional()
|
|
14408
|
+
})
|
|
14409
|
+
).optional().describe("Key decisions made in this session with their rationale"),
|
|
14410
|
+
unfinished_work: external_exports.array(
|
|
14411
|
+
external_exports.object({
|
|
14412
|
+
task: external_exports.string(),
|
|
14413
|
+
status: external_exports.string().optional(),
|
|
14414
|
+
next_steps: external_exports.string().optional()
|
|
14415
|
+
})
|
|
14416
|
+
).optional().describe("Work items that are in progress or pending"),
|
|
14417
|
+
user_preferences: external_exports.array(external_exports.string()).optional().describe("Preferences expressed by user during this session"),
|
|
14418
|
+
priority_items: external_exports.array(external_exports.string()).optional().describe("User-flagged important items to remember"),
|
|
14419
|
+
metadata: external_exports.record(external_exports.unknown()).optional().describe("Additional context to preserve")
|
|
14420
|
+
})
|
|
14421
|
+
},
|
|
14422
|
+
async (input) => {
|
|
14423
|
+
let workspaceId = input.workspace_id;
|
|
14424
|
+
let projectId = input.project_id;
|
|
14425
|
+
if (!workspaceId && sessionManager) {
|
|
14426
|
+
const ctx = sessionManager.getContext();
|
|
14427
|
+
if (ctx) {
|
|
14428
|
+
workspaceId = ctx.workspace_id;
|
|
14429
|
+
projectId = projectId || ctx.project_id;
|
|
14430
|
+
}
|
|
14431
|
+
}
|
|
14432
|
+
if (!workspaceId) {
|
|
14433
|
+
return errorResult(
|
|
14434
|
+
"Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
|
|
14435
|
+
);
|
|
14436
|
+
}
|
|
14437
|
+
const snapshotContent = {
|
|
14438
|
+
conversation_summary: input.conversation_summary,
|
|
14439
|
+
captured_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
14440
|
+
};
|
|
14441
|
+
if (input.current_goal) {
|
|
14442
|
+
snapshotContent.current_goal = input.current_goal;
|
|
14443
|
+
}
|
|
14444
|
+
if (input.active_files?.length) {
|
|
14445
|
+
snapshotContent.active_files = input.active_files;
|
|
14446
|
+
}
|
|
14447
|
+
if (input.recent_decisions?.length) {
|
|
14448
|
+
snapshotContent.recent_decisions = input.recent_decisions;
|
|
14449
|
+
}
|
|
14450
|
+
if (input.unfinished_work?.length) {
|
|
14451
|
+
snapshotContent.unfinished_work = input.unfinished_work;
|
|
14452
|
+
}
|
|
14453
|
+
if (input.user_preferences?.length) {
|
|
14454
|
+
snapshotContent.user_preferences = input.user_preferences;
|
|
14455
|
+
}
|
|
14456
|
+
if (input.priority_items?.length) {
|
|
14457
|
+
snapshotContent.priority_items = input.priority_items;
|
|
14458
|
+
}
|
|
14459
|
+
if (input.metadata) {
|
|
14460
|
+
snapshotContent.metadata = input.metadata;
|
|
14461
|
+
}
|
|
14462
|
+
const result = await client.captureContext({
|
|
14463
|
+
workspace_id: workspaceId,
|
|
14464
|
+
project_id: projectId,
|
|
14465
|
+
event_type: "session_snapshot",
|
|
14466
|
+
title: `Session Snapshot: ${input.current_goal || "Conversation State"}`,
|
|
14467
|
+
content: JSON.stringify(snapshotContent, null, 2),
|
|
14468
|
+
importance: "high",
|
|
14469
|
+
tags: ["session_snapshot", "pre_compaction"]
|
|
14470
|
+
});
|
|
14471
|
+
const response = {
|
|
14472
|
+
...result,
|
|
14473
|
+
snapshot_id: result?.data?.id || result?.id,
|
|
14474
|
+
message: "Session state captured successfully. This snapshot will be prioritized after compaction.",
|
|
14475
|
+
hint: "After compaction, call session_init with is_post_compact=true to restore this context."
|
|
14476
|
+
};
|
|
14477
|
+
return {
|
|
14478
|
+
content: [{ type: "text", text: formatContent(response) }],
|
|
14479
|
+
structuredContent: toStructured(response)
|
|
14480
|
+
};
|
|
14481
|
+
}
|
|
14482
|
+
);
|
|
14483
|
+
registerTool(
|
|
14484
|
+
"session_restore_context",
|
|
14485
|
+
{
|
|
14486
|
+
title: "Restore context after compaction",
|
|
14487
|
+
description: `Restore conversation context after compaction or context loss.
|
|
14488
|
+
Call this after conversation compaction to retrieve saved session state.
|
|
14489
|
+
|
|
14490
|
+
Returns structured context including:
|
|
14491
|
+
- conversation_summary: What was being discussed
|
|
14492
|
+
- current_goal: The primary task being worked on
|
|
14493
|
+
- active_files: Files that were being modified
|
|
14494
|
+
- recent_decisions: Key decisions made in the session
|
|
14495
|
+
- unfinished_work: Tasks that are still in progress
|
|
14496
|
+
- user_preferences: Preferences expressed during the session
|
|
14497
|
+
|
|
14498
|
+
Use this in combination with session_init(is_post_compact=true) for seamless continuation.`,
|
|
14499
|
+
inputSchema: external_exports.object({
|
|
14500
|
+
workspace_id: external_exports.string().uuid().optional(),
|
|
14501
|
+
project_id: external_exports.string().uuid().optional(),
|
|
14502
|
+
snapshot_id: external_exports.string().uuid().optional().describe("Specific snapshot ID to restore (defaults to most recent)"),
|
|
14503
|
+
max_snapshots: external_exports.number().optional().default(1).describe("Number of recent snapshots to consider (default: 1)")
|
|
14504
|
+
})
|
|
14505
|
+
},
|
|
14506
|
+
async (input) => {
|
|
14507
|
+
let workspaceId = input.workspace_id;
|
|
14508
|
+
let projectId = input.project_id;
|
|
14509
|
+
if (!workspaceId && sessionManager) {
|
|
14510
|
+
const ctx = sessionManager.getContext();
|
|
14511
|
+
if (ctx) {
|
|
14512
|
+
workspaceId = ctx.workspace_id;
|
|
14513
|
+
projectId = projectId || ctx.project_id;
|
|
14514
|
+
}
|
|
14515
|
+
}
|
|
14516
|
+
if (!workspaceId) {
|
|
14517
|
+
return errorResult(
|
|
14518
|
+
"Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
|
|
14519
|
+
);
|
|
14520
|
+
}
|
|
14521
|
+
try {
|
|
14522
|
+
if (input.snapshot_id) {
|
|
14523
|
+
const eventResult = await client.getEvent(input.snapshot_id);
|
|
14524
|
+
const event = eventResult?.data || eventResult;
|
|
14525
|
+
if (!event || !event.content) {
|
|
14526
|
+
return errorResult(
|
|
14527
|
+
`Snapshot not found: ${input.snapshot_id}. The snapshot may have been deleted or does not exist.`
|
|
14528
|
+
);
|
|
14529
|
+
}
|
|
14530
|
+
let snapshotData2;
|
|
14531
|
+
try {
|
|
14532
|
+
snapshotData2 = JSON.parse(event.content);
|
|
14533
|
+
} catch {
|
|
14534
|
+
snapshotData2 = { conversation_summary: event.content };
|
|
14535
|
+
}
|
|
14536
|
+
const response2 = {
|
|
14537
|
+
restored: true,
|
|
14538
|
+
snapshot_id: event.id,
|
|
14539
|
+
captured_at: snapshotData2.captured_at || event.created_at,
|
|
14540
|
+
...snapshotData2,
|
|
14541
|
+
hint: "Context restored. Continue the conversation with awareness of the above state."
|
|
14542
|
+
};
|
|
14543
|
+
return {
|
|
14544
|
+
content: [{ type: "text", text: formatContent(response2) }],
|
|
14545
|
+
structuredContent: toStructured(response2)
|
|
14546
|
+
};
|
|
14547
|
+
}
|
|
14548
|
+
const listResult = await client.listMemoryEvents({
|
|
14549
|
+
workspace_id: workspaceId,
|
|
14550
|
+
project_id: projectId,
|
|
14551
|
+
limit: 50
|
|
14552
|
+
// Fetch more to filter
|
|
14553
|
+
});
|
|
14554
|
+
const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
|
|
14555
|
+
const events = allEvents.filter(
|
|
14556
|
+
(e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
|
|
14557
|
+
).slice(0, input.max_snapshots || 1);
|
|
14558
|
+
if (!events || events.length === 0) {
|
|
14559
|
+
return {
|
|
14560
|
+
content: [
|
|
14561
|
+
{
|
|
14562
|
+
type: "text",
|
|
14563
|
+
text: formatContent({
|
|
14564
|
+
restored: false,
|
|
14565
|
+
message: "No session snapshots found. This may be a new session or snapshots have not been captured.",
|
|
14566
|
+
hint: "Use session_capture_smart to save session state before compaction."
|
|
14567
|
+
})
|
|
14568
|
+
}
|
|
14569
|
+
]
|
|
14570
|
+
};
|
|
14571
|
+
}
|
|
14572
|
+
const latestEvent = events[0];
|
|
14573
|
+
let snapshotData;
|
|
14574
|
+
try {
|
|
14575
|
+
snapshotData = JSON.parse(latestEvent.content);
|
|
14576
|
+
} catch {
|
|
14577
|
+
snapshotData = { conversation_summary: latestEvent.content };
|
|
14578
|
+
}
|
|
14579
|
+
const response = {
|
|
14580
|
+
restored: true,
|
|
14581
|
+
snapshot_id: latestEvent.id,
|
|
14582
|
+
captured_at: snapshotData.captured_at || latestEvent.created_at,
|
|
14583
|
+
...snapshotData,
|
|
14584
|
+
hint: "Context restored. Continue the conversation with awareness of the above state."
|
|
14585
|
+
};
|
|
14586
|
+
return {
|
|
14587
|
+
content: [{ type: "text", text: formatContent(response) }],
|
|
14588
|
+
structuredContent: toStructured(response)
|
|
14589
|
+
};
|
|
14590
|
+
} catch (error) {
|
|
14591
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14592
|
+
return errorResult(`Failed to restore context: ${message}`);
|
|
14593
|
+
}
|
|
14594
|
+
}
|
|
14595
|
+
);
|
|
13492
14596
|
registerTool(
|
|
13493
14597
|
"session_capture_lesson",
|
|
13494
14598
|
{
|
|
@@ -13847,6 +14951,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
13847
14951
|
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files (ContextStream block only)"),
|
|
13848
14952
|
apply_global: external_exports.boolean().optional().describe("Also write global rule files for supported editors"),
|
|
13849
14953
|
install_hooks: external_exports.boolean().optional().describe("Install Claude Code hooks to enforce ContextStream-first search. Defaults to true for Claude users. Set to false to skip."),
|
|
14954
|
+
include_pre_compact: external_exports.boolean().optional().describe("Include PreCompact hook for automatic state saving before context compaction. Defaults to false."),
|
|
13850
14955
|
dry_run: external_exports.boolean().optional().describe("If true, return content without writing files")
|
|
13851
14956
|
})
|
|
13852
14957
|
},
|
|
@@ -13927,8 +15032,14 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
13927
15032
|
{ file: "~/.claude/hooks/contextstream-reminder.py", status: "dry run - would create" },
|
|
13928
15033
|
{ file: "~/.claude/settings.json", status: "dry run - would update" }
|
|
13929
15034
|
];
|
|
15035
|
+
if (input.include_pre_compact) {
|
|
15036
|
+
hooksResults.push({ file: "~/.claude/hooks/contextstream-precompact.py", status: "dry run - would create" });
|
|
15037
|
+
}
|
|
13930
15038
|
} else {
|
|
13931
|
-
const hookResult = await installClaudeCodeHooks({
|
|
15039
|
+
const hookResult = await installClaudeCodeHooks({
|
|
15040
|
+
scope: "user",
|
|
15041
|
+
includePreCompact: input.include_pre_compact
|
|
15042
|
+
});
|
|
13932
15043
|
hooksResults = [
|
|
13933
15044
|
...hookResult.scripts.map((f) => ({ file: f, status: "created" })),
|
|
13934
15045
|
...hookResult.settings.map((f) => ({ file: f, status: "updated" }))
|
|
@@ -14287,10 +15398,13 @@ This saves ~80% tokens compared to including full chat history.`,
|
|
|
14287
15398
|
max_tokens: external_exports.number().optional().describe("Maximum tokens for context (default: 800)"),
|
|
14288
15399
|
format: external_exports.enum(["minified", "readable", "structured"]).optional().describe("Context format (default: minified)"),
|
|
14289
15400
|
mode: external_exports.enum(["standard", "pack"]).optional().describe("Context pack mode (default: pack when enabled)"),
|
|
14290
|
-
distill: external_exports.boolean().optional().describe("Use distillation for context pack (default: true)")
|
|
15401
|
+
distill: external_exports.boolean().optional().describe("Use distillation for context pack (default: true)"),
|
|
15402
|
+
session_tokens: external_exports.number().optional().describe("Cumulative session token count for context pressure calculation"),
|
|
15403
|
+
context_threshold: external_exports.number().optional().describe("Custom context window threshold (defaults to 70k)")
|
|
14291
15404
|
})
|
|
14292
15405
|
},
|
|
14293
15406
|
async (input) => {
|
|
15407
|
+
const startTime = Date.now();
|
|
14294
15408
|
if (sessionManager) {
|
|
14295
15409
|
sessionManager.markContextSmartCalled();
|
|
14296
15410
|
}
|
|
@@ -14303,6 +15417,17 @@ This saves ~80% tokens compared to including full chat history.`,
|
|
|
14303
15417
|
projectId = projectId || ctx.project_id;
|
|
14304
15418
|
}
|
|
14305
15419
|
}
|
|
15420
|
+
let sessionTokens = input.session_tokens;
|
|
15421
|
+
let contextThreshold = input.context_threshold;
|
|
15422
|
+
if (sessionManager) {
|
|
15423
|
+
if (sessionTokens === void 0) {
|
|
15424
|
+
sessionTokens = sessionManager.getSessionTokens();
|
|
15425
|
+
}
|
|
15426
|
+
if (contextThreshold === void 0) {
|
|
15427
|
+
contextThreshold = sessionManager.getContextThreshold();
|
|
15428
|
+
}
|
|
15429
|
+
sessionManager.addTokens(input.user_message);
|
|
15430
|
+
}
|
|
14306
15431
|
const result = await client.getSmartContext({
|
|
14307
15432
|
user_message: input.user_message,
|
|
14308
15433
|
workspace_id: workspaceId,
|
|
@@ -14310,11 +15435,18 @@ This saves ~80% tokens compared to including full chat history.`,
|
|
|
14310
15435
|
max_tokens: input.max_tokens,
|
|
14311
15436
|
format: input.format,
|
|
14312
15437
|
mode: input.mode,
|
|
14313
|
-
distill: input.distill
|
|
15438
|
+
distill: input.distill,
|
|
15439
|
+
session_tokens: sessionTokens,
|
|
15440
|
+
context_threshold: contextThreshold
|
|
14314
15441
|
});
|
|
15442
|
+
if (sessionManager && result.token_estimate) {
|
|
15443
|
+
sessionManager.addTokens(result.token_estimate);
|
|
15444
|
+
}
|
|
15445
|
+
const roundTripMs = Date.now() - startTime;
|
|
15446
|
+
const timingStr = SHOW_TIMING ? ` | ${roundTripMs}ms` : "";
|
|
14315
15447
|
const footer = `
|
|
14316
15448
|
---
|
|
14317
|
-
\u{1F3AF} ${result.sources_used} sources | ~${result.token_estimate} tokens | format: ${result.format}`;
|
|
15449
|
+
\u{1F3AF} ${result.sources_used} sources | ~${result.token_estimate} tokens | format: ${result.format}${timingStr}`;
|
|
14318
15450
|
const folderPathForRules = resolveFolderPath(void 0, sessionManager);
|
|
14319
15451
|
const rulesNotice = getRulesNotice(folderPathForRules, detectedClientInfo?.name);
|
|
14320
15452
|
let versionNotice = result.version_notice;
|
|
@@ -14341,6 +15473,22 @@ This saves ~80% tokens compared to including full chat history.`,
|
|
|
14341
15473
|
const searchRulesLine = SEARCH_RULES_REMINDER_ENABLED ? `
|
|
14342
15474
|
|
|
14343
15475
|
${SEARCH_RULES_REMINDER}` : "";
|
|
15476
|
+
let contextPressureWarning = "";
|
|
15477
|
+
if (result.context_pressure) {
|
|
15478
|
+
const cp = result.context_pressure;
|
|
15479
|
+
if (cp.level === "critical") {
|
|
15480
|
+
contextPressureWarning = `
|
|
15481
|
+
|
|
15482
|
+
\u{1F6A8} [CONTEXT PRESSURE: CRITICAL] ${cp.usage_percent}% of context used (${cp.session_tokens}/${cp.threshold} tokens)
|
|
15483
|
+
Action: ${cp.suggested_action === "save_now" ? 'SAVE STATE NOW - Call session(action="capture") to preserve conversation state before compaction.' : cp.suggested_action}
|
|
15484
|
+
The conversation may compact soon. Save important decisions, insights, and progress immediately.`;
|
|
15485
|
+
} else if (cp.level === "high") {
|
|
15486
|
+
contextPressureWarning = `
|
|
15487
|
+
|
|
15488
|
+
\u26A0\uFE0F [CONTEXT PRESSURE: HIGH] ${cp.usage_percent}% of context used (${cp.session_tokens}/${cp.threshold} tokens)
|
|
15489
|
+
Action: ${cp.suggested_action === "prepare_save" ? "Consider saving important decisions and conversation state soon." : cp.suggested_action}`;
|
|
15490
|
+
}
|
|
15491
|
+
}
|
|
14344
15492
|
const allWarnings = [
|
|
14345
15493
|
lessonsWarningLine,
|
|
14346
15494
|
rulesWarningLine ? `
|
|
@@ -14349,6 +15497,7 @@ ${rulesWarningLine}` : "",
|
|
|
14349
15497
|
versionWarningLine ? `
|
|
14350
15498
|
|
|
14351
15499
|
${versionWarningLine}` : "",
|
|
15500
|
+
contextPressureWarning,
|
|
14352
15501
|
searchRulesLine
|
|
14353
15502
|
].filter(Boolean).join("");
|
|
14354
15503
|
return {
|
|
@@ -15379,6 +16528,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15379
16528
|
},
|
|
15380
16529
|
async (input) => {
|
|
15381
16530
|
const params = normalizeSearchParams(input);
|
|
16531
|
+
const startTime = Date.now();
|
|
15382
16532
|
let result;
|
|
15383
16533
|
let toolType;
|
|
15384
16534
|
switch (input.mode) {
|
|
@@ -15409,7 +16559,9 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15409
16559
|
default:
|
|
15410
16560
|
toolType = "search_hybrid";
|
|
15411
16561
|
}
|
|
15412
|
-
const
|
|
16562
|
+
const roundTripMs = Date.now() - startTime;
|
|
16563
|
+
const timingSummary = formatTimingSummary(roundTripMs, getResultCount(result));
|
|
16564
|
+
const outputText = timingSummary + formatContent(result);
|
|
15413
16565
|
trackToolTokenSavings(client, toolType, outputText, {
|
|
15414
16566
|
workspace_id: params.workspace_id,
|
|
15415
16567
|
project_id: params.project_id
|
|
@@ -15424,7 +16576,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15424
16576
|
"session",
|
|
15425
16577
|
{
|
|
15426
16578
|
title: "Session",
|
|
15427
|
-
description: `Session management operations. Actions: capture (save decision/insight), capture_lesson (save lesson from mistake), get_lessons (retrieve lessons), recall (natural language recall), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (context-enriched search), decision_trace (trace decision provenance). Plan actions: capture_plan (save implementation plan), get_plan (retrieve plan with tasks), update_plan (modify plan), list_plans (list all plans).`,
|
|
16579
|
+
description: `Session management operations. Actions: capture (save decision/insight), capture_lesson (save lesson from mistake), get_lessons (retrieve lessons), recall (natural language recall), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (context-enriched search), decision_trace (trace decision provenance), restore_context (restore state after compaction). Plan actions: capture_plan (save implementation plan), get_plan (retrieve plan with tasks), update_plan (modify plan), list_plans (list all plans).`,
|
|
15428
16580
|
inputSchema: external_exports.object({
|
|
15429
16581
|
action: external_exports.enum([
|
|
15430
16582
|
"capture",
|
|
@@ -15442,7 +16594,9 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15442
16594
|
"capture_plan",
|
|
15443
16595
|
"get_plan",
|
|
15444
16596
|
"update_plan",
|
|
15445
|
-
"list_plans"
|
|
16597
|
+
"list_plans",
|
|
16598
|
+
// Context restore
|
|
16599
|
+
"restore_context"
|
|
15446
16600
|
]).describe("Action to perform"),
|
|
15447
16601
|
workspace_id: external_exports.string().uuid().optional(),
|
|
15448
16602
|
project_id: external_exports.string().uuid().optional(),
|
|
@@ -15464,7 +16618,8 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15464
16618
|
"lesson",
|
|
15465
16619
|
"warning",
|
|
15466
16620
|
"frustration",
|
|
15467
|
-
"conversation"
|
|
16621
|
+
"conversation",
|
|
16622
|
+
"session_snapshot"
|
|
15468
16623
|
]).optional().describe("Event type for capture"),
|
|
15469
16624
|
importance: external_exports.enum(["low", "medium", "high", "critical"]).optional(),
|
|
15470
16625
|
tags: external_exports.array(external_exports.string()).optional(),
|
|
@@ -15514,7 +16669,10 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15514
16669
|
status: external_exports.enum(["draft", "active", "completed", "archived", "abandoned"]).optional().describe("Plan status"),
|
|
15515
16670
|
due_at: external_exports.string().optional().describe("Due date for plan (ISO timestamp)"),
|
|
15516
16671
|
source_tool: external_exports.string().optional().describe("Tool that generated this plan"),
|
|
15517
|
-
include_tasks: external_exports.boolean().optional().describe("Include tasks when getting plan")
|
|
16672
|
+
include_tasks: external_exports.boolean().optional().describe("Include tasks when getting plan"),
|
|
16673
|
+
// Restore context params
|
|
16674
|
+
snapshot_id: external_exports.string().uuid().optional().describe("Specific snapshot ID to restore (defaults to most recent)"),
|
|
16675
|
+
max_snapshots: external_exports.number().optional().default(1).describe("Number of recent snapshots to consider (default: 1)")
|
|
15518
16676
|
})
|
|
15519
16677
|
},
|
|
15520
16678
|
async (input) => {
|
|
@@ -15820,6 +16978,87 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15820
16978
|
structuredContent: toStructured(result)
|
|
15821
16979
|
};
|
|
15822
16980
|
}
|
|
16981
|
+
case "restore_context": {
|
|
16982
|
+
if (!workspaceId) {
|
|
16983
|
+
return errorResult(
|
|
16984
|
+
"restore_context requires workspace_id. Call session_init first."
|
|
16985
|
+
);
|
|
16986
|
+
}
|
|
16987
|
+
if (input.snapshot_id) {
|
|
16988
|
+
const eventResult = await client.getEvent(input.snapshot_id);
|
|
16989
|
+
const event = eventResult?.data || eventResult;
|
|
16990
|
+
if (!event || !event.content) {
|
|
16991
|
+
return errorResult(
|
|
16992
|
+
`Snapshot not found: ${input.snapshot_id}. The snapshot may have been deleted or does not exist.`
|
|
16993
|
+
);
|
|
16994
|
+
}
|
|
16995
|
+
let snapshotData;
|
|
16996
|
+
try {
|
|
16997
|
+
snapshotData = JSON.parse(event.content);
|
|
16998
|
+
} catch {
|
|
16999
|
+
snapshotData = { conversation_summary: event.content };
|
|
17000
|
+
}
|
|
17001
|
+
const response2 = {
|
|
17002
|
+
restored: true,
|
|
17003
|
+
snapshot_id: event.id,
|
|
17004
|
+
captured_at: snapshotData.captured_at || event.created_at,
|
|
17005
|
+
...snapshotData,
|
|
17006
|
+
hint: "Context restored. Continue the conversation with awareness of the above state."
|
|
17007
|
+
};
|
|
17008
|
+
return {
|
|
17009
|
+
content: [{ type: "text", text: formatContent(response2) }],
|
|
17010
|
+
structuredContent: toStructured(response2)
|
|
17011
|
+
};
|
|
17012
|
+
}
|
|
17013
|
+
const listResult = await client.listMemoryEvents({
|
|
17014
|
+
workspace_id: workspaceId,
|
|
17015
|
+
project_id: projectId,
|
|
17016
|
+
limit: 50
|
|
17017
|
+
// Fetch more to filter
|
|
17018
|
+
});
|
|
17019
|
+
const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
|
|
17020
|
+
const snapshotEvents = allEvents.filter(
|
|
17021
|
+
(e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
|
|
17022
|
+
).slice(0, input.max_snapshots || 1);
|
|
17023
|
+
if (!snapshotEvents || snapshotEvents.length === 0) {
|
|
17024
|
+
return {
|
|
17025
|
+
content: [
|
|
17026
|
+
{
|
|
17027
|
+
type: "text",
|
|
17028
|
+
text: formatContent({
|
|
17029
|
+
restored: false,
|
|
17030
|
+
message: "No session snapshots found. Use session_capture_smart to save state before compaction.",
|
|
17031
|
+
hint: "Start fresh or use session_init to get recent context."
|
|
17032
|
+
})
|
|
17033
|
+
}
|
|
17034
|
+
]
|
|
17035
|
+
};
|
|
17036
|
+
}
|
|
17037
|
+
const snapshots = snapshotEvents.map((event) => {
|
|
17038
|
+
let snapshotData;
|
|
17039
|
+
try {
|
|
17040
|
+
snapshotData = JSON.parse(event.content || "{}");
|
|
17041
|
+
} catch {
|
|
17042
|
+
snapshotData = { conversation_summary: event.content };
|
|
17043
|
+
}
|
|
17044
|
+
return {
|
|
17045
|
+
snapshot_id: event.id,
|
|
17046
|
+
captured_at: snapshotData.captured_at || event.created_at,
|
|
17047
|
+
...snapshotData
|
|
17048
|
+
};
|
|
17049
|
+
});
|
|
17050
|
+
const response = {
|
|
17051
|
+
restored: true,
|
|
17052
|
+
snapshots_found: snapshots.length,
|
|
17053
|
+
latest: snapshots[0],
|
|
17054
|
+
all_snapshots: snapshots.length > 1 ? snapshots : void 0,
|
|
17055
|
+
hint: "Context restored. Continue the conversation with awareness of the above state."
|
|
17056
|
+
};
|
|
17057
|
+
return {
|
|
17058
|
+
content: [{ type: "text", text: formatContent(response) }],
|
|
17059
|
+
structuredContent: toStructured(response)
|
|
17060
|
+
};
|
|
17061
|
+
}
|
|
15823
17062
|
default:
|
|
15824
17063
|
return errorResult(`Unknown action: ${input.action}`);
|
|
15825
17064
|
}
|
|
@@ -15829,7 +17068,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15829
17068
|
"memory",
|
|
15830
17069
|
{
|
|
15831
17070
|
title: "Memory",
|
|
15832
|
-
description: `Memory operations for events and nodes. Event actions: create_event, get_event, update_event, delete_event, list_events, distill_event. Node actions: create_node, get_node, update_node, delete_node, list_nodes, supersede_node. Query actions: search, decisions, timeline, summary. Task actions: create_task (create task, optionally linked to plan), get_task, update_task (can link/unlink task to plan via plan_id), delete_task, list_tasks, reorder_tasks.`,
|
|
17071
|
+
description: `Memory operations for events and nodes. Event actions: create_event, get_event, update_event, delete_event, list_events, distill_event, import_batch (bulk import array of events). Node actions: create_node, get_node, update_node, delete_node, list_nodes, supersede_node. Query actions: search, decisions, timeline, summary. Task actions: create_task (create task, optionally linked to plan), get_task, update_task (can link/unlink task to plan via plan_id), delete_task, list_tasks, reorder_tasks.`,
|
|
15833
17072
|
inputSchema: external_exports.object({
|
|
15834
17073
|
action: external_exports.enum([
|
|
15835
17074
|
"create_event",
|
|
@@ -15848,6 +17087,8 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15848
17087
|
"decisions",
|
|
15849
17088
|
"timeline",
|
|
15850
17089
|
"summary",
|
|
17090
|
+
// Batch actions
|
|
17091
|
+
"import_batch",
|
|
15851
17092
|
// Task actions
|
|
15852
17093
|
"create_task",
|
|
15853
17094
|
"get_task",
|
|
@@ -15908,7 +17149,33 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15908
17149
|
order: external_exports.number().optional().describe("Task order within plan"),
|
|
15909
17150
|
task_ids: external_exports.array(external_exports.string().uuid()).optional().describe("Task IDs for reorder_tasks"),
|
|
15910
17151
|
blocked_reason: external_exports.string().optional().describe("Reason when task is blocked"),
|
|
15911
|
-
tags: external_exports.array(external_exports.string()).optional().describe("Tags for task")
|
|
17152
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Tags for task"),
|
|
17153
|
+
// Batch import params
|
|
17154
|
+
events: external_exports.array(
|
|
17155
|
+
external_exports.object({
|
|
17156
|
+
event_type: external_exports.string(),
|
|
17157
|
+
title: external_exports.string(),
|
|
17158
|
+
content: external_exports.string(),
|
|
17159
|
+
metadata: external_exports.record(external_exports.any()).optional(),
|
|
17160
|
+
provenance: external_exports.object({
|
|
17161
|
+
repo: external_exports.string().optional(),
|
|
17162
|
+
branch: external_exports.string().optional(),
|
|
17163
|
+
commit_sha: external_exports.string().optional(),
|
|
17164
|
+
pr_url: external_exports.string().url().optional(),
|
|
17165
|
+
issue_url: external_exports.string().url().optional(),
|
|
17166
|
+
slack_thread_url: external_exports.string().url().optional()
|
|
17167
|
+
}).optional(),
|
|
17168
|
+
code_refs: external_exports.array(
|
|
17169
|
+
external_exports.object({
|
|
17170
|
+
file_path: external_exports.string(),
|
|
17171
|
+
symbol_id: external_exports.string().optional(),
|
|
17172
|
+
symbol_name: external_exports.string().optional()
|
|
17173
|
+
})
|
|
17174
|
+
).optional(),
|
|
17175
|
+
tags: external_exports.array(external_exports.string()).optional(),
|
|
17176
|
+
occurred_at: external_exports.string().optional().describe("ISO timestamp for when the event occurred")
|
|
17177
|
+
})
|
|
17178
|
+
).optional().describe("Array of events for import_batch action")
|
|
15912
17179
|
})
|
|
15913
17180
|
},
|
|
15914
17181
|
async (input) => {
|
|
@@ -15979,6 +17246,36 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15979
17246
|
structuredContent: toStructured(result)
|
|
15980
17247
|
};
|
|
15981
17248
|
}
|
|
17249
|
+
case "import_batch": {
|
|
17250
|
+
if (!input.events || !Array.isArray(input.events) || input.events.length === 0) {
|
|
17251
|
+
return errorResult("import_batch requires: events (non-empty array of event objects)");
|
|
17252
|
+
}
|
|
17253
|
+
if (!workspaceId) {
|
|
17254
|
+
return errorResult("import_batch requires workspace_id. Call session_init first.");
|
|
17255
|
+
}
|
|
17256
|
+
const eventsWithContext = input.events.map((event) => ({
|
|
17257
|
+
...event,
|
|
17258
|
+
workspace_id: workspaceId,
|
|
17259
|
+
project_id: projectId || event.project_id
|
|
17260
|
+
}));
|
|
17261
|
+
const result = await client.bulkIngestEvents({
|
|
17262
|
+
workspace_id: workspaceId,
|
|
17263
|
+
project_id: projectId,
|
|
17264
|
+
events: eventsWithContext
|
|
17265
|
+
});
|
|
17266
|
+
const count = Array.isArray(result) ? result.length : result?.data?.length ?? 0;
|
|
17267
|
+
return {
|
|
17268
|
+
content: [
|
|
17269
|
+
{
|
|
17270
|
+
type: "text",
|
|
17271
|
+
text: `\u2705 Imported ${count} event(s) successfully.
|
|
17272
|
+
|
|
17273
|
+
${formatContent(result)}`
|
|
17274
|
+
}
|
|
17275
|
+
],
|
|
17276
|
+
structuredContent: toStructured(result)
|
|
17277
|
+
};
|
|
17278
|
+
}
|
|
15982
17279
|
case "distill_event": {
|
|
15983
17280
|
if (!input.event_id) {
|
|
15984
17281
|
return errorResult("distill_event requires: event_id");
|
|
@@ -18327,6 +19624,7 @@ function registerPrompts(server) {
|
|
|
18327
19624
|
|
|
18328
19625
|
// src/session-manager.ts
|
|
18329
19626
|
var SessionManager = class {
|
|
19627
|
+
// Conservative default for 100k context window
|
|
18330
19628
|
constructor(server, client) {
|
|
18331
19629
|
this.server = server;
|
|
18332
19630
|
this.client = client;
|
|
@@ -18337,6 +19635,9 @@ var SessionManager = class {
|
|
|
18337
19635
|
this.folderPath = null;
|
|
18338
19636
|
this.contextSmartCalled = false;
|
|
18339
19637
|
this.warningShown = false;
|
|
19638
|
+
// Token tracking for context pressure calculation
|
|
19639
|
+
this.sessionTokens = 0;
|
|
19640
|
+
this.contextThreshold = 7e4;
|
|
18340
19641
|
}
|
|
18341
19642
|
/**
|
|
18342
19643
|
* Check if session has been auto-initialized
|
|
@@ -18375,8 +19676,8 @@ var SessionManager = class {
|
|
|
18375
19676
|
/**
|
|
18376
19677
|
* Set the folder path hint (can be passed from tools that know the workspace path)
|
|
18377
19678
|
*/
|
|
18378
|
-
setFolderPath(
|
|
18379
|
-
this.folderPath =
|
|
19679
|
+
setFolderPath(path9) {
|
|
19680
|
+
this.folderPath = path9;
|
|
18380
19681
|
}
|
|
18381
19682
|
/**
|
|
18382
19683
|
* Mark that context_smart has been called in this session
|
|
@@ -18384,6 +19685,51 @@ var SessionManager = class {
|
|
|
18384
19685
|
markContextSmartCalled() {
|
|
18385
19686
|
this.contextSmartCalled = true;
|
|
18386
19687
|
}
|
|
19688
|
+
/**
|
|
19689
|
+
* Get current session token count for context pressure calculation.
|
|
19690
|
+
*/
|
|
19691
|
+
getSessionTokens() {
|
|
19692
|
+
return this.sessionTokens;
|
|
19693
|
+
}
|
|
19694
|
+
/**
|
|
19695
|
+
* Get the context threshold (max tokens before compaction warning).
|
|
19696
|
+
*/
|
|
19697
|
+
getContextThreshold() {
|
|
19698
|
+
return this.contextThreshold;
|
|
19699
|
+
}
|
|
19700
|
+
/**
|
|
19701
|
+
* Set a custom context threshold (useful if client provides model info).
|
|
19702
|
+
*/
|
|
19703
|
+
setContextThreshold(threshold) {
|
|
19704
|
+
this.contextThreshold = threshold;
|
|
19705
|
+
}
|
|
19706
|
+
/**
|
|
19707
|
+
* Add tokens to the session count.
|
|
19708
|
+
* Call this after each tool response to track token accumulation.
|
|
19709
|
+
*
|
|
19710
|
+
* @param tokens - Exact token count or text to estimate
|
|
19711
|
+
*/
|
|
19712
|
+
addTokens(tokens) {
|
|
19713
|
+
if (typeof tokens === "number") {
|
|
19714
|
+
this.sessionTokens += tokens;
|
|
19715
|
+
} else {
|
|
19716
|
+
this.sessionTokens += Math.ceil(tokens.length / 4);
|
|
19717
|
+
}
|
|
19718
|
+
}
|
|
19719
|
+
/**
|
|
19720
|
+
* Estimate tokens from a tool response.
|
|
19721
|
+
* Uses a simple heuristic: ~4 characters per token.
|
|
19722
|
+
*/
|
|
19723
|
+
estimateTokens(content) {
|
|
19724
|
+
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
19725
|
+
return Math.ceil(text.length / 4);
|
|
19726
|
+
}
|
|
19727
|
+
/**
|
|
19728
|
+
* Reset token count (e.g., after compaction or new session).
|
|
19729
|
+
*/
|
|
19730
|
+
resetTokenCount() {
|
|
19731
|
+
this.sessionTokens = 0;
|
|
19732
|
+
}
|
|
18387
19733
|
/**
|
|
18388
19734
|
* Check if context_smart has been called and warn if not.
|
|
18389
19735
|
* Returns true if a warning was shown, false otherwise.
|
|
@@ -18452,7 +19798,7 @@ var SessionManager = class {
|
|
|
18452
19798
|
}
|
|
18453
19799
|
if (this.ideRoots.length === 0) {
|
|
18454
19800
|
const cwd = process.cwd();
|
|
18455
|
-
const
|
|
19801
|
+
const fs8 = await import("fs");
|
|
18456
19802
|
const projectIndicators = [
|
|
18457
19803
|
".git",
|
|
18458
19804
|
"package.json",
|
|
@@ -18462,7 +19808,7 @@ var SessionManager = class {
|
|
|
18462
19808
|
];
|
|
18463
19809
|
const hasProjectIndicator = projectIndicators.some((f) => {
|
|
18464
19810
|
try {
|
|
18465
|
-
return
|
|
19811
|
+
return fs8.existsSync(`${cwd}/${f}`);
|
|
18466
19812
|
} catch {
|
|
18467
19813
|
return false;
|
|
18468
19814
|
}
|
|
@@ -18935,24 +20281,24 @@ async function runHttpGateway() {
|
|
|
18935
20281
|
// src/index.ts
|
|
18936
20282
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
18937
20283
|
import { homedir as homedir6 } from "os";
|
|
18938
|
-
import { join as
|
|
20284
|
+
import { join as join10 } from "path";
|
|
18939
20285
|
|
|
18940
20286
|
// src/setup.ts
|
|
18941
|
-
import * as
|
|
18942
|
-
import * as
|
|
20287
|
+
import * as fs7 from "node:fs/promises";
|
|
20288
|
+
import * as path8 from "node:path";
|
|
18943
20289
|
import { homedir as homedir5 } from "node:os";
|
|
18944
20290
|
import { stdin, stdout } from "node:process";
|
|
18945
20291
|
import { createInterface } from "node:readline/promises";
|
|
18946
20292
|
|
|
18947
20293
|
// src/credentials.ts
|
|
18948
|
-
import * as
|
|
18949
|
-
import * as
|
|
20294
|
+
import * as fs6 from "node:fs/promises";
|
|
20295
|
+
import * as path7 from "node:path";
|
|
18950
20296
|
import { homedir as homedir4 } from "node:os";
|
|
18951
20297
|
function normalizeApiUrl(input) {
|
|
18952
20298
|
return String(input ?? "").trim().replace(/\/+$/, "");
|
|
18953
20299
|
}
|
|
18954
20300
|
function credentialsFilePath() {
|
|
18955
|
-
return
|
|
20301
|
+
return path7.join(homedir4(), ".contextstream", "credentials.json");
|
|
18956
20302
|
}
|
|
18957
20303
|
function isRecord(value) {
|
|
18958
20304
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -18960,7 +20306,7 @@ function isRecord(value) {
|
|
|
18960
20306
|
async function readSavedCredentials() {
|
|
18961
20307
|
const filePath = credentialsFilePath();
|
|
18962
20308
|
try {
|
|
18963
|
-
const raw = await
|
|
20309
|
+
const raw = await fs6.readFile(filePath, "utf8");
|
|
18964
20310
|
const parsed = JSON.parse(raw);
|
|
18965
20311
|
if (!isRecord(parsed)) return null;
|
|
18966
20312
|
const version = parsed.version;
|
|
@@ -18986,7 +20332,7 @@ async function readSavedCredentials() {
|
|
|
18986
20332
|
}
|
|
18987
20333
|
async function writeSavedCredentials(input) {
|
|
18988
20334
|
const filePath = credentialsFilePath();
|
|
18989
|
-
await
|
|
20335
|
+
await fs6.mkdir(path7.dirname(filePath), { recursive: true });
|
|
18990
20336
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18991
20337
|
const existing = await readSavedCredentials();
|
|
18992
20338
|
const value = {
|
|
@@ -18998,9 +20344,9 @@ async function writeSavedCredentials(input) {
|
|
|
18998
20344
|
updated_at: now
|
|
18999
20345
|
};
|
|
19000
20346
|
const body = JSON.stringify(value, null, 2) + "\n";
|
|
19001
|
-
await
|
|
20347
|
+
await fs6.writeFile(filePath, body, { encoding: "utf8", mode: 384 });
|
|
19002
20348
|
try {
|
|
19003
|
-
await
|
|
20349
|
+
await fs6.chmod(filePath, 384);
|
|
19004
20350
|
} catch {
|
|
19005
20351
|
}
|
|
19006
20352
|
return { path: filePath, value };
|
|
@@ -19045,7 +20391,7 @@ function parseNumberList(input, max) {
|
|
|
19045
20391
|
}
|
|
19046
20392
|
async function fileExists(filePath) {
|
|
19047
20393
|
try {
|
|
19048
|
-
await
|
|
20394
|
+
await fs7.stat(filePath);
|
|
19049
20395
|
return true;
|
|
19050
20396
|
} catch {
|
|
19051
20397
|
return false;
|
|
@@ -19209,41 +20555,41 @@ function replaceContextStreamBlock2(existing, content) {
|
|
|
19209
20555
|
return { content: appended, status: "appended" };
|
|
19210
20556
|
}
|
|
19211
20557
|
async function upsertTextFile(filePath, content, _marker) {
|
|
19212
|
-
await
|
|
20558
|
+
await fs7.mkdir(path8.dirname(filePath), { recursive: true });
|
|
19213
20559
|
const exists = await fileExists(filePath);
|
|
19214
20560
|
const wrappedContent = wrapWithMarkers2(content);
|
|
19215
20561
|
if (!exists) {
|
|
19216
|
-
await
|
|
20562
|
+
await fs7.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
19217
20563
|
return "created";
|
|
19218
20564
|
}
|
|
19219
|
-
const existing = await
|
|
20565
|
+
const existing = await fs7.readFile(filePath, "utf8").catch(() => "");
|
|
19220
20566
|
if (!existing.trim()) {
|
|
19221
|
-
await
|
|
20567
|
+
await fs7.writeFile(filePath, wrappedContent + "\n", "utf8");
|
|
19222
20568
|
return "updated";
|
|
19223
20569
|
}
|
|
19224
20570
|
const replaced = replaceContextStreamBlock2(existing, content);
|
|
19225
|
-
await
|
|
20571
|
+
await fs7.writeFile(filePath, replaced.content, "utf8");
|
|
19226
20572
|
return replaced.status;
|
|
19227
20573
|
}
|
|
19228
20574
|
function globalRulesPathForEditor(editor) {
|
|
19229
20575
|
const home = homedir5();
|
|
19230
20576
|
switch (editor) {
|
|
19231
20577
|
case "codex":
|
|
19232
|
-
return
|
|
20578
|
+
return path8.join(home, ".codex", "AGENTS.md");
|
|
19233
20579
|
case "claude":
|
|
19234
|
-
return
|
|
20580
|
+
return path8.join(home, ".claude", "CLAUDE.md");
|
|
19235
20581
|
case "windsurf":
|
|
19236
|
-
return
|
|
20582
|
+
return path8.join(home, ".codeium", "windsurf", "memories", "global_rules.md");
|
|
19237
20583
|
case "cline":
|
|
19238
|
-
return
|
|
20584
|
+
return path8.join(home, "Documents", "Cline", "Rules", "contextstream.md");
|
|
19239
20585
|
case "kilo":
|
|
19240
|
-
return
|
|
20586
|
+
return path8.join(home, ".kilocode", "rules", "contextstream.md");
|
|
19241
20587
|
case "roo":
|
|
19242
|
-
return
|
|
20588
|
+
return path8.join(home, ".roo", "rules", "contextstream.md");
|
|
19243
20589
|
case "aider":
|
|
19244
|
-
return
|
|
20590
|
+
return path8.join(home, ".aider.conf.yml");
|
|
19245
20591
|
case "antigravity":
|
|
19246
|
-
return
|
|
20592
|
+
return path8.join(home, ".gemini", "GEMINI.md");
|
|
19247
20593
|
case "cursor":
|
|
19248
20594
|
return null;
|
|
19249
20595
|
default:
|
|
@@ -19261,40 +20607,40 @@ async function isCodexInstalled() {
|
|
|
19261
20607
|
const envHome = process.env.CODEX_HOME;
|
|
19262
20608
|
const candidates = [
|
|
19263
20609
|
envHome,
|
|
19264
|
-
|
|
19265
|
-
|
|
19266
|
-
|
|
20610
|
+
path8.join(home, ".codex"),
|
|
20611
|
+
path8.join(home, ".codex", "config.toml"),
|
|
20612
|
+
path8.join(home, ".config", "codex")
|
|
19267
20613
|
].filter((candidate) => Boolean(candidate));
|
|
19268
20614
|
return anyPathExists(candidates);
|
|
19269
20615
|
}
|
|
19270
20616
|
async function isClaudeInstalled() {
|
|
19271
20617
|
const home = homedir5();
|
|
19272
|
-
const candidates = [
|
|
20618
|
+
const candidates = [path8.join(home, ".claude"), path8.join(home, ".config", "claude")];
|
|
19273
20619
|
const desktopConfig = claudeDesktopConfigPath();
|
|
19274
20620
|
if (desktopConfig) candidates.push(desktopConfig);
|
|
19275
20621
|
if (process.platform === "darwin") {
|
|
19276
|
-
candidates.push(
|
|
20622
|
+
candidates.push(path8.join(home, "Library", "Application Support", "Claude"));
|
|
19277
20623
|
} else if (process.platform === "win32") {
|
|
19278
20624
|
const appData = process.env.APPDATA;
|
|
19279
|
-
if (appData) candidates.push(
|
|
20625
|
+
if (appData) candidates.push(path8.join(appData, "Claude"));
|
|
19280
20626
|
}
|
|
19281
20627
|
return anyPathExists(candidates);
|
|
19282
20628
|
}
|
|
19283
20629
|
async function isWindsurfInstalled() {
|
|
19284
20630
|
const home = homedir5();
|
|
19285
20631
|
const candidates = [
|
|
19286
|
-
|
|
19287
|
-
|
|
19288
|
-
|
|
20632
|
+
path8.join(home, ".codeium"),
|
|
20633
|
+
path8.join(home, ".codeium", "windsurf"),
|
|
20634
|
+
path8.join(home, ".config", "codeium")
|
|
19289
20635
|
];
|
|
19290
20636
|
if (process.platform === "darwin") {
|
|
19291
|
-
candidates.push(
|
|
19292
|
-
candidates.push(
|
|
20637
|
+
candidates.push(path8.join(home, "Library", "Application Support", "Windsurf"));
|
|
20638
|
+
candidates.push(path8.join(home, "Library", "Application Support", "Codeium"));
|
|
19293
20639
|
} else if (process.platform === "win32") {
|
|
19294
20640
|
const appData = process.env.APPDATA;
|
|
19295
20641
|
if (appData) {
|
|
19296
|
-
candidates.push(
|
|
19297
|
-
candidates.push(
|
|
20642
|
+
candidates.push(path8.join(appData, "Windsurf"));
|
|
20643
|
+
candidates.push(path8.join(appData, "Codeium"));
|
|
19298
20644
|
}
|
|
19299
20645
|
}
|
|
19300
20646
|
return anyPathExists(candidates);
|
|
@@ -19302,42 +20648,42 @@ async function isWindsurfInstalled() {
|
|
|
19302
20648
|
async function isClineInstalled() {
|
|
19303
20649
|
const home = homedir5();
|
|
19304
20650
|
const candidates = [
|
|
19305
|
-
|
|
19306
|
-
|
|
19307
|
-
|
|
20651
|
+
path8.join(home, "Documents", "Cline"),
|
|
20652
|
+
path8.join(home, ".cline"),
|
|
20653
|
+
path8.join(home, ".config", "cline")
|
|
19308
20654
|
];
|
|
19309
20655
|
return anyPathExists(candidates);
|
|
19310
20656
|
}
|
|
19311
20657
|
async function isKiloInstalled() {
|
|
19312
20658
|
const home = homedir5();
|
|
19313
|
-
const candidates = [
|
|
20659
|
+
const candidates = [path8.join(home, ".kilocode"), path8.join(home, ".config", "kilocode")];
|
|
19314
20660
|
return anyPathExists(candidates);
|
|
19315
20661
|
}
|
|
19316
20662
|
async function isRooInstalled() {
|
|
19317
20663
|
const home = homedir5();
|
|
19318
|
-
const candidates = [
|
|
20664
|
+
const candidates = [path8.join(home, ".roo"), path8.join(home, ".config", "roo")];
|
|
19319
20665
|
return anyPathExists(candidates);
|
|
19320
20666
|
}
|
|
19321
20667
|
async function isAiderInstalled() {
|
|
19322
20668
|
const home = homedir5();
|
|
19323
|
-
const candidates = [
|
|
20669
|
+
const candidates = [path8.join(home, ".aider.conf.yml"), path8.join(home, ".config", "aider")];
|
|
19324
20670
|
return anyPathExists(candidates);
|
|
19325
20671
|
}
|
|
19326
20672
|
async function isCursorInstalled() {
|
|
19327
20673
|
const home = homedir5();
|
|
19328
|
-
const candidates = [
|
|
20674
|
+
const candidates = [path8.join(home, ".cursor")];
|
|
19329
20675
|
if (process.platform === "darwin") {
|
|
19330
20676
|
candidates.push("/Applications/Cursor.app");
|
|
19331
|
-
candidates.push(
|
|
19332
|
-
candidates.push(
|
|
20677
|
+
candidates.push(path8.join(home, "Applications", "Cursor.app"));
|
|
20678
|
+
candidates.push(path8.join(home, "Library", "Application Support", "Cursor"));
|
|
19333
20679
|
} else if (process.platform === "win32") {
|
|
19334
20680
|
const localApp = process.env.LOCALAPPDATA;
|
|
19335
20681
|
const programFiles = process.env.ProgramFiles;
|
|
19336
20682
|
const programFilesX86 = process.env["ProgramFiles(x86)"];
|
|
19337
|
-
if (localApp) candidates.push(
|
|
19338
|
-
if (localApp) candidates.push(
|
|
19339
|
-
if (programFiles) candidates.push(
|
|
19340
|
-
if (programFilesX86) candidates.push(
|
|
20683
|
+
if (localApp) candidates.push(path8.join(localApp, "Programs", "Cursor", "Cursor.exe"));
|
|
20684
|
+
if (localApp) candidates.push(path8.join(localApp, "Cursor", "Cursor.exe"));
|
|
20685
|
+
if (programFiles) candidates.push(path8.join(programFiles, "Cursor", "Cursor.exe"));
|
|
20686
|
+
if (programFilesX86) candidates.push(path8.join(programFilesX86, "Cursor", "Cursor.exe"));
|
|
19341
20687
|
} else {
|
|
19342
20688
|
candidates.push("/usr/bin/cursor");
|
|
19343
20689
|
candidates.push("/usr/local/bin/cursor");
|
|
@@ -19348,19 +20694,19 @@ async function isCursorInstalled() {
|
|
|
19348
20694
|
}
|
|
19349
20695
|
async function isAntigravityInstalled() {
|
|
19350
20696
|
const home = homedir5();
|
|
19351
|
-
const candidates = [
|
|
20697
|
+
const candidates = [path8.join(home, ".gemini")];
|
|
19352
20698
|
if (process.platform === "darwin") {
|
|
19353
20699
|
candidates.push("/Applications/Antigravity.app");
|
|
19354
|
-
candidates.push(
|
|
19355
|
-
candidates.push(
|
|
20700
|
+
candidates.push(path8.join(home, "Applications", "Antigravity.app"));
|
|
20701
|
+
candidates.push(path8.join(home, "Library", "Application Support", "Antigravity"));
|
|
19356
20702
|
} else if (process.platform === "win32") {
|
|
19357
20703
|
const localApp = process.env.LOCALAPPDATA;
|
|
19358
20704
|
const programFiles = process.env.ProgramFiles;
|
|
19359
20705
|
const programFilesX86 = process.env["ProgramFiles(x86)"];
|
|
19360
|
-
if (localApp) candidates.push(
|
|
19361
|
-
if (localApp) candidates.push(
|
|
19362
|
-
if (programFiles) candidates.push(
|
|
19363
|
-
if (programFilesX86) candidates.push(
|
|
20706
|
+
if (localApp) candidates.push(path8.join(localApp, "Programs", "Antigravity", "Antigravity.exe"));
|
|
20707
|
+
if (localApp) candidates.push(path8.join(localApp, "Antigravity", "Antigravity.exe"));
|
|
20708
|
+
if (programFiles) candidates.push(path8.join(programFiles, "Antigravity", "Antigravity.exe"));
|
|
20709
|
+
if (programFilesX86) candidates.push(path8.join(programFilesX86, "Antigravity", "Antigravity.exe"));
|
|
19364
20710
|
} else {
|
|
19365
20711
|
candidates.push("/usr/bin/antigravity");
|
|
19366
20712
|
candidates.push("/usr/local/bin/antigravity");
|
|
@@ -19403,6 +20749,9 @@ function buildContextStreamMcpServer(params) {
|
|
|
19403
20749
|
env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
|
|
19404
20750
|
}
|
|
19405
20751
|
env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
|
|
20752
|
+
if (params.showTiming) {
|
|
20753
|
+
env.CONTEXTSTREAM_SHOW_TIMING = "true";
|
|
20754
|
+
}
|
|
19406
20755
|
if (IS_WINDOWS) {
|
|
19407
20756
|
return {
|
|
19408
20757
|
command: "cmd",
|
|
@@ -19425,6 +20774,9 @@ function buildContextStreamVsCodeServer(params) {
|
|
|
19425
20774
|
env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
|
|
19426
20775
|
}
|
|
19427
20776
|
env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
|
|
20777
|
+
if (params.showTiming) {
|
|
20778
|
+
env.CONTEXTSTREAM_SHOW_TIMING = "true";
|
|
20779
|
+
}
|
|
19428
20780
|
if (IS_WINDOWS) {
|
|
19429
20781
|
return {
|
|
19430
20782
|
type: "stdio",
|
|
@@ -19459,11 +20811,11 @@ function tryParseJsonLike(raw) {
|
|
|
19459
20811
|
}
|
|
19460
20812
|
}
|
|
19461
20813
|
async function upsertJsonMcpConfig(filePath, server) {
|
|
19462
|
-
await
|
|
20814
|
+
await fs7.mkdir(path8.dirname(filePath), { recursive: true });
|
|
19463
20815
|
const exists = await fileExists(filePath);
|
|
19464
20816
|
let root = {};
|
|
19465
20817
|
if (exists) {
|
|
19466
|
-
const raw = await
|
|
20818
|
+
const raw = await fs7.readFile(filePath, "utf8").catch(() => "");
|
|
19467
20819
|
const parsed = tryParseJsonLike(raw);
|
|
19468
20820
|
if (!parsed.ok) throw new Error(`Invalid JSON in ${filePath}: ${parsed.error}`);
|
|
19469
20821
|
root = parsed.value;
|
|
@@ -19474,16 +20826,16 @@ async function upsertJsonMcpConfig(filePath, server) {
|
|
|
19474
20826
|
const before = JSON.stringify(root.mcpServers.contextstream ?? null);
|
|
19475
20827
|
root.mcpServers.contextstream = server;
|
|
19476
20828
|
const after = JSON.stringify(root.mcpServers.contextstream ?? null);
|
|
19477
|
-
await
|
|
20829
|
+
await fs7.writeFile(filePath, JSON.stringify(root, null, 2) + "\n", "utf8");
|
|
19478
20830
|
if (!exists) return "created";
|
|
19479
20831
|
return before === after ? "skipped" : "updated";
|
|
19480
20832
|
}
|
|
19481
20833
|
async function upsertJsonVsCodeMcpConfig(filePath, server) {
|
|
19482
|
-
await
|
|
20834
|
+
await fs7.mkdir(path8.dirname(filePath), { recursive: true });
|
|
19483
20835
|
const exists = await fileExists(filePath);
|
|
19484
20836
|
let root = {};
|
|
19485
20837
|
if (exists) {
|
|
19486
|
-
const raw = await
|
|
20838
|
+
const raw = await fs7.readFile(filePath, "utf8").catch(() => "");
|
|
19487
20839
|
const parsed = tryParseJsonLike(raw);
|
|
19488
20840
|
if (!parsed.ok) throw new Error(`Invalid JSON in ${filePath}: ${parsed.error}`);
|
|
19489
20841
|
root = parsed.value;
|
|
@@ -19494,14 +20846,14 @@ async function upsertJsonVsCodeMcpConfig(filePath, server) {
|
|
|
19494
20846
|
const before = JSON.stringify(root.servers.contextstream ?? null);
|
|
19495
20847
|
root.servers.contextstream = server;
|
|
19496
20848
|
const after = JSON.stringify(root.servers.contextstream ?? null);
|
|
19497
|
-
await
|
|
20849
|
+
await fs7.writeFile(filePath, JSON.stringify(root, null, 2) + "\n", "utf8");
|
|
19498
20850
|
if (!exists) return "created";
|
|
19499
20851
|
return before === after ? "skipped" : "updated";
|
|
19500
20852
|
}
|
|
19501
20853
|
function claudeDesktopConfigPath() {
|
|
19502
20854
|
const home = homedir5();
|
|
19503
20855
|
if (process.platform === "darwin") {
|
|
19504
|
-
return
|
|
20856
|
+
return path8.join(
|
|
19505
20857
|
home,
|
|
19506
20858
|
"Library",
|
|
19507
20859
|
"Application Support",
|
|
@@ -19510,21 +20862,23 @@ function claudeDesktopConfigPath() {
|
|
|
19510
20862
|
);
|
|
19511
20863
|
}
|
|
19512
20864
|
if (process.platform === "win32") {
|
|
19513
|
-
const appData = process.env.APPDATA ||
|
|
19514
|
-
return
|
|
20865
|
+
const appData = process.env.APPDATA || path8.join(home, "AppData", "Roaming");
|
|
20866
|
+
return path8.join(appData, "Claude", "claude_desktop_config.json");
|
|
19515
20867
|
}
|
|
19516
20868
|
return null;
|
|
19517
20869
|
}
|
|
19518
20870
|
async function upsertCodexTomlConfig(filePath, params) {
|
|
19519
|
-
await
|
|
20871
|
+
await fs7.mkdir(path8.dirname(filePath), { recursive: true });
|
|
19520
20872
|
const exists = await fileExists(filePath);
|
|
19521
|
-
const existing = exists ? await
|
|
20873
|
+
const existing = exists ? await fs7.readFile(filePath, "utf8").catch(() => "") : "";
|
|
19522
20874
|
const marker = "[mcp_servers.contextstream]";
|
|
19523
20875
|
const envMarker = "[mcp_servers.contextstream.env]";
|
|
19524
20876
|
const toolsetLine = params.toolset === "router" ? `CONTEXTSTREAM_PROGRESSIVE_MODE = "true"
|
|
19525
20877
|
` : "";
|
|
19526
20878
|
const contextPackLine = `CONTEXTSTREAM_CONTEXT_PACK = "${params.contextPackEnabled === false ? "false" : "true"}"
|
|
19527
20879
|
`;
|
|
20880
|
+
const showTimingLine = params.showTiming ? `CONTEXTSTREAM_SHOW_TIMING = "true"
|
|
20881
|
+
` : "";
|
|
19528
20882
|
const commandLine = IS_WINDOWS ? `command = "cmd"
|
|
19529
20883
|
args = ["/c", "npx", "-y", "@contextstream/mcp-server"]
|
|
19530
20884
|
` : `command = "npx"
|
|
@@ -19538,17 +20892,17 @@ args = ["-y", "@contextstream/mcp-server"]
|
|
|
19538
20892
|
[mcp_servers.contextstream.env]
|
|
19539
20893
|
CONTEXTSTREAM_API_URL = "${params.apiUrl}"
|
|
19540
20894
|
CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
19541
|
-
` + toolsetLine + contextPackLine;
|
|
20895
|
+
` + toolsetLine + contextPackLine + showTimingLine;
|
|
19542
20896
|
if (!exists) {
|
|
19543
|
-
await
|
|
20897
|
+
await fs7.writeFile(filePath, block.trimStart(), "utf8");
|
|
19544
20898
|
return "created";
|
|
19545
20899
|
}
|
|
19546
20900
|
if (!existing.includes(marker)) {
|
|
19547
|
-
await
|
|
20901
|
+
await fs7.writeFile(filePath, existing.trimEnd() + block, "utf8");
|
|
19548
20902
|
return "updated";
|
|
19549
20903
|
}
|
|
19550
20904
|
if (!existing.includes(envMarker)) {
|
|
19551
|
-
await
|
|
20905
|
+
await fs7.writeFile(
|
|
19552
20906
|
filePath,
|
|
19553
20907
|
existing.trimEnd() + "\n\n" + envMarker + `
|
|
19554
20908
|
CONTEXTSTREAM_API_URL = "${params.apiUrl}"
|
|
@@ -19609,18 +20963,18 @@ CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
|
19609
20963
|
}
|
|
19610
20964
|
const updated = out.join("\n");
|
|
19611
20965
|
if (updated === existing) return "skipped";
|
|
19612
|
-
await
|
|
20966
|
+
await fs7.writeFile(filePath, updated, "utf8");
|
|
19613
20967
|
return "updated";
|
|
19614
20968
|
}
|
|
19615
20969
|
async function discoverProjectsUnderFolder(parentFolder) {
|
|
19616
|
-
const entries = await
|
|
19617
|
-
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
20970
|
+
const entries = await fs7.readdir(parentFolder, { withFileTypes: true });
|
|
20971
|
+
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => path8.join(parentFolder, e.name));
|
|
19618
20972
|
const projects = [];
|
|
19619
20973
|
for (const dir of candidates) {
|
|
19620
|
-
const hasGit = await fileExists(
|
|
19621
|
-
const hasPkg = await fileExists(
|
|
19622
|
-
const hasCargo = await fileExists(
|
|
19623
|
-
const hasPyProject = await fileExists(
|
|
20974
|
+
const hasGit = await fileExists(path8.join(dir, ".git"));
|
|
20975
|
+
const hasPkg = await fileExists(path8.join(dir, "package.json"));
|
|
20976
|
+
const hasCargo = await fileExists(path8.join(dir, "Cargo.toml"));
|
|
20977
|
+
const hasPyProject = await fileExists(path8.join(dir, "pyproject.toml"));
|
|
19624
20978
|
if (hasGit || hasPkg || hasCargo || hasPyProject) projects.push(dir);
|
|
19625
20979
|
}
|
|
19626
20980
|
return projects;
|
|
@@ -19895,6 +21249,11 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
19895
21249
|
console.log(" Uses more operations/credits; can be disabled in settings or via env.");
|
|
19896
21250
|
const contextPackChoice = normalizeInput(await rl.question("Enable Context Pack? [Y/n]: "));
|
|
19897
21251
|
const contextPackEnabled = !(contextPackChoice.toLowerCase() === "n" || contextPackChoice.toLowerCase() === "no");
|
|
21252
|
+
console.log("\nResponse Timing:");
|
|
21253
|
+
console.log(" Show response time for tool calls (e.g., '\u2713 3 results in 142ms').");
|
|
21254
|
+
console.log(" Useful for debugging performance; disabled by default.");
|
|
21255
|
+
const showTimingChoice = normalizeInput(await rl.question("Show response timing? [y/N]: "));
|
|
21256
|
+
const showTiming = showTimingChoice.toLowerCase() === "y" || showTimingChoice.toLowerCase() === "yes";
|
|
19898
21257
|
const editors = [
|
|
19899
21258
|
"codex",
|
|
19900
21259
|
"claude",
|
|
@@ -19969,18 +21328,20 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
19969
21328
|
)
|
|
19970
21329
|
) || mcpChoiceDefault;
|
|
19971
21330
|
const mcpScope = mcpChoice === "2" && hasCodex && !hasProjectMcpEditors ? "skip" : mcpChoice === "4" ? "skip" : mcpChoice === "1" ? "global" : mcpChoice === "2" ? "project" : "both";
|
|
19972
|
-
const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, toolset, contextPackEnabled });
|
|
21331
|
+
const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, toolset, contextPackEnabled, showTiming });
|
|
19973
21332
|
const mcpServerClaude = buildContextStreamMcpServer({
|
|
19974
21333
|
apiUrl,
|
|
19975
21334
|
apiKey,
|
|
19976
21335
|
toolset,
|
|
19977
|
-
contextPackEnabled
|
|
21336
|
+
contextPackEnabled,
|
|
21337
|
+
showTiming
|
|
19978
21338
|
});
|
|
19979
21339
|
const vsCodeServer = buildContextStreamVsCodeServer({
|
|
19980
21340
|
apiUrl,
|
|
19981
21341
|
apiKey,
|
|
19982
21342
|
toolset,
|
|
19983
|
-
contextPackEnabled
|
|
21343
|
+
contextPackEnabled,
|
|
21344
|
+
showTiming
|
|
19984
21345
|
});
|
|
19985
21346
|
const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex;
|
|
19986
21347
|
if (needsGlobalMcpConfig) {
|
|
@@ -19989,7 +21350,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
19989
21350
|
if (mcpScope === "project" && editor !== "codex") continue;
|
|
19990
21351
|
try {
|
|
19991
21352
|
if (editor === "codex") {
|
|
19992
|
-
const filePath =
|
|
21353
|
+
const filePath = path8.join(homedir5(), ".codex", "config.toml");
|
|
19993
21354
|
if (dryRun) {
|
|
19994
21355
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
19995
21356
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -19999,14 +21360,15 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
19999
21360
|
apiUrl,
|
|
20000
21361
|
apiKey,
|
|
20001
21362
|
toolset,
|
|
20002
|
-
contextPackEnabled
|
|
21363
|
+
contextPackEnabled,
|
|
21364
|
+
showTiming
|
|
20003
21365
|
});
|
|
20004
21366
|
writeActions.push({ kind: "mcp-config", target: filePath, status });
|
|
20005
21367
|
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
20006
21368
|
continue;
|
|
20007
21369
|
}
|
|
20008
21370
|
if (editor === "windsurf") {
|
|
20009
|
-
const filePath =
|
|
21371
|
+
const filePath = path8.join(homedir5(), ".codeium", "windsurf", "mcp_config.json");
|
|
20010
21372
|
if (dryRun) {
|
|
20011
21373
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
20012
21374
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -20048,7 +21410,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
20048
21410
|
continue;
|
|
20049
21411
|
}
|
|
20050
21412
|
if (editor === "cursor") {
|
|
20051
|
-
const filePath =
|
|
21413
|
+
const filePath = path8.join(homedir5(), ".cursor", "mcp.json");
|
|
20052
21414
|
if (dryRun) {
|
|
20053
21415
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
20054
21416
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -20106,9 +21468,9 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
20106
21468
|
if (dryRun) {
|
|
20107
21469
|
console.log("- Would install hooks to ~/.claude/hooks/");
|
|
20108
21470
|
console.log("- Would update ~/.claude/settings.json");
|
|
20109
|
-
writeActions.push({ kind: "mcp-config", target:
|
|
20110
|
-
writeActions.push({ kind: "mcp-config", target:
|
|
20111
|
-
writeActions.push({ kind: "mcp-config", target:
|
|
21471
|
+
writeActions.push({ kind: "mcp-config", target: path8.join(homedir5(), ".claude", "hooks", "contextstream-redirect.py"), status: "dry-run" });
|
|
21472
|
+
writeActions.push({ kind: "mcp-config", target: path8.join(homedir5(), ".claude", "hooks", "contextstream-reminder.py"), status: "dry-run" });
|
|
21473
|
+
writeActions.push({ kind: "mcp-config", target: path8.join(homedir5(), ".claude", "settings.json"), status: "dry-run" });
|
|
20112
21474
|
} else {
|
|
20113
21475
|
const result = await installClaudeCodeHooks({ scope: "user" });
|
|
20114
21476
|
result.scripts.forEach((script) => {
|
|
@@ -20130,6 +21492,102 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
20130
21492
|
console.log(" Note: Without hooks, Claude may still use default tools instead of ContextStream.");
|
|
20131
21493
|
}
|
|
20132
21494
|
}
|
|
21495
|
+
console.log("\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
21496
|
+
console.log("\u2502 Code Privacy & Indexing \u2502");
|
|
21497
|
+
console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
21498
|
+
console.log("");
|
|
21499
|
+
console.log(" Your code is protected:");
|
|
21500
|
+
console.log(" \u2713 Encrypted in transit (TLS 1.3) and at rest (AES-256)");
|
|
21501
|
+
console.log(" \u2713 Isolated per workspace \u2014 no cross-tenant access");
|
|
21502
|
+
console.log(" \u2713 You can delete your data anytime");
|
|
21503
|
+
console.log("");
|
|
21504
|
+
console.log(" As an additional measure, you can exclude specific files from indexing");
|
|
21505
|
+
console.log(" using .contextstream/ignore (gitignore syntax). Already excluded:");
|
|
21506
|
+
console.log(" \u2022 node_modules, vendor, .git, build outputs, lock files");
|
|
21507
|
+
console.log("");
|
|
21508
|
+
console.log(" Optional: Add patterns for extra-sensitive files like:");
|
|
21509
|
+
console.log(" \u2022 customer-data/ \u2014 client-specific data");
|
|
21510
|
+
console.log(" \u2022 **/*.pem, **/*.key \u2014 private keys & certificates");
|
|
21511
|
+
console.log(" \u2022 src/legacy/ \u2014 code you don't need indexed");
|
|
21512
|
+
console.log("");
|
|
21513
|
+
const createIgnoreFile = normalizeInput(
|
|
21514
|
+
await rl.question("Create a sample .contextstream/ignore file? [y/N]: ")
|
|
21515
|
+
).toLowerCase();
|
|
21516
|
+
if (createIgnoreFile === "y" || createIgnoreFile === "yes") {
|
|
21517
|
+
const ignoreContent = `# .contextstream/ignore - Additional exclusions from ContextStream indexing
|
|
21518
|
+
# Uses gitignore syntax: https://git-scm.com/docs/gitignore
|
|
21519
|
+
#
|
|
21520
|
+
# Note: Your code is already protected with encryption (TLS 1.3 + AES-256)
|
|
21521
|
+
# and workspace isolation. This file is for extra-sensitive paths you prefer
|
|
21522
|
+
# to keep completely off the index.
|
|
21523
|
+
|
|
21524
|
+
# Customer/sensitive data
|
|
21525
|
+
**/customer-data/
|
|
21526
|
+
**/secrets/
|
|
21527
|
+
**/*.pem
|
|
21528
|
+
**/*.key
|
|
21529
|
+
|
|
21530
|
+
# Large generated files
|
|
21531
|
+
**/generated/
|
|
21532
|
+
**/*.min.js
|
|
21533
|
+
**/*.min.css
|
|
21534
|
+
|
|
21535
|
+
# Test fixtures with sensitive data
|
|
21536
|
+
**/fixtures/production/
|
|
21537
|
+
**/test-data/real/
|
|
21538
|
+
|
|
21539
|
+
# Vendor code you don't want indexed
|
|
21540
|
+
**/third-party/
|
|
21541
|
+
**/external-libs/
|
|
21542
|
+
|
|
21543
|
+
# Specific paths in your project (uncomment as needed)
|
|
21544
|
+
# src/legacy/
|
|
21545
|
+
# docs/internal/
|
|
21546
|
+
`;
|
|
21547
|
+
console.log("\nWhere to create .contextstream/ignore?");
|
|
21548
|
+
console.log(` 1) Current folder (${process.cwd()})`);
|
|
21549
|
+
console.log(" 2) Home folder (applies globally)");
|
|
21550
|
+
console.log(" 3) I'll add it to specific projects later");
|
|
21551
|
+
const ignoreLocation = normalizeInput(await rl.question("Choose [1/2/3] (default 1): ")) || "1";
|
|
21552
|
+
if (ignoreLocation === "1" || ignoreLocation === "2") {
|
|
21553
|
+
const baseDir = ignoreLocation === "1" ? process.cwd() : homedir5();
|
|
21554
|
+
const ignoreDir = path8.join(baseDir, ".contextstream");
|
|
21555
|
+
const ignorePath = path8.join(ignoreDir, "ignore");
|
|
21556
|
+
if (dryRun) {
|
|
21557
|
+
writeActions.push({ kind: "rules", target: ignorePath, status: "dry-run" });
|
|
21558
|
+
console.log(`- Would create ${ignorePath}`);
|
|
21559
|
+
} else {
|
|
21560
|
+
try {
|
|
21561
|
+
await fs7.mkdir(ignoreDir, { recursive: true });
|
|
21562
|
+
const exists = await fileExists(ignorePath);
|
|
21563
|
+
if (exists) {
|
|
21564
|
+
const overwrite = normalizeInput(
|
|
21565
|
+
await rl.question(`${ignorePath} already exists. Overwrite? [y/N]: `)
|
|
21566
|
+
).toLowerCase();
|
|
21567
|
+
if (overwrite !== "y" && overwrite !== "yes") {
|
|
21568
|
+
console.log("- Skipped (file exists)");
|
|
21569
|
+
} else {
|
|
21570
|
+
await fs7.writeFile(ignorePath, ignoreContent, "utf-8");
|
|
21571
|
+
writeActions.push({ kind: "rules", target: ignorePath, status: "updated" });
|
|
21572
|
+
console.log(`- Updated ${ignorePath}`);
|
|
21573
|
+
}
|
|
21574
|
+
} else {
|
|
21575
|
+
await fs7.writeFile(ignorePath, ignoreContent, "utf-8");
|
|
21576
|
+
writeActions.push({ kind: "rules", target: ignorePath, status: "created" });
|
|
21577
|
+
console.log(`- Created ${ignorePath}`);
|
|
21578
|
+
}
|
|
21579
|
+
} catch (err) {
|
|
21580
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
21581
|
+
console.log(`- Failed to create ignore file: ${message}`);
|
|
21582
|
+
}
|
|
21583
|
+
}
|
|
21584
|
+
} else {
|
|
21585
|
+
console.log("- Skipped. Add .contextstream/ignore to your projects as needed.");
|
|
21586
|
+
}
|
|
21587
|
+
} else {
|
|
21588
|
+
console.log("- Using default exclusions only.");
|
|
21589
|
+
console.log(" Tip: Create .contextstream/ignore in any project to customize.");
|
|
21590
|
+
}
|
|
20133
21591
|
if (scope === "global" || scope === "both") {
|
|
20134
21592
|
console.log("\nInstalling global rules...");
|
|
20135
21593
|
for (const editor of configuredEditors) {
|
|
@@ -20170,7 +21628,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
20170
21628
|
await rl.question(`Add current folder as a project? [Y/n] (${process.cwd()}): `)
|
|
20171
21629
|
);
|
|
20172
21630
|
if (addCwd.toLowerCase() !== "n" && addCwd.toLowerCase() !== "no") {
|
|
20173
|
-
projectPaths.add(
|
|
21631
|
+
projectPaths.add(path8.resolve(process.cwd()));
|
|
20174
21632
|
}
|
|
20175
21633
|
while (true) {
|
|
20176
21634
|
console.log("\n 1) Add another project path");
|
|
@@ -20180,13 +21638,13 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
20180
21638
|
if (choice === "3") break;
|
|
20181
21639
|
if (choice === "1") {
|
|
20182
21640
|
const p = normalizeInput(await rl.question("Project folder path: "));
|
|
20183
|
-
if (p) projectPaths.add(
|
|
21641
|
+
if (p) projectPaths.add(path8.resolve(p));
|
|
20184
21642
|
continue;
|
|
20185
21643
|
}
|
|
20186
21644
|
if (choice === "2") {
|
|
20187
21645
|
const parent = normalizeInput(await rl.question("Parent folder path: "));
|
|
20188
21646
|
if (!parent) continue;
|
|
20189
|
-
const parentAbs =
|
|
21647
|
+
const parentAbs = path8.resolve(parent);
|
|
20190
21648
|
const projects2 = await discoverProjectsUnderFolder(parentAbs);
|
|
20191
21649
|
if (projects2.length === 0) {
|
|
20192
21650
|
console.log(
|
|
@@ -20218,11 +21676,16 @@ Applying to ${projects.length} project(s)...`);
|
|
|
20218
21676
|
folder_path: projectPath,
|
|
20219
21677
|
workspace_id: workspaceId,
|
|
20220
21678
|
workspace_name: workspaceName,
|
|
20221
|
-
create_parent_mapping: createParentMapping
|
|
21679
|
+
create_parent_mapping: createParentMapping,
|
|
21680
|
+
// Include version and config info for desktop app compatibility
|
|
21681
|
+
version: VERSION,
|
|
21682
|
+
configured_editors: configuredEditors,
|
|
21683
|
+
context_pack: contextPackEnabled,
|
|
21684
|
+
api_url: apiUrl
|
|
20222
21685
|
});
|
|
20223
21686
|
writeActions.push({
|
|
20224
21687
|
kind: "workspace-config",
|
|
20225
|
-
target:
|
|
21688
|
+
target: path8.join(projectPath, ".contextstream", "config.json"),
|
|
20226
21689
|
status: "created"
|
|
20227
21690
|
});
|
|
20228
21691
|
console.log(`- Linked workspace in ${projectPath}`);
|
|
@@ -20233,7 +21696,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
20233
21696
|
} else if (workspaceId && workspaceId !== "dry-run" && workspaceName && dryRun) {
|
|
20234
21697
|
writeActions.push({
|
|
20235
21698
|
kind: "workspace-config",
|
|
20236
|
-
target:
|
|
21699
|
+
target: path8.join(projectPath, ".contextstream", "config.json"),
|
|
20237
21700
|
status: "dry-run"
|
|
20238
21701
|
});
|
|
20239
21702
|
}
|
|
@@ -20241,8 +21704,8 @@ Applying to ${projects.length} project(s)...`);
|
|
|
20241
21704
|
for (const editor of configuredEditors) {
|
|
20242
21705
|
try {
|
|
20243
21706
|
if (editor === "cursor") {
|
|
20244
|
-
const cursorPath =
|
|
20245
|
-
const vscodePath =
|
|
21707
|
+
const cursorPath = path8.join(projectPath, ".cursor", "mcp.json");
|
|
21708
|
+
const vscodePath = path8.join(projectPath, ".vscode", "mcp.json");
|
|
20246
21709
|
if (dryRun) {
|
|
20247
21710
|
writeActions.push({ kind: "mcp-config", target: cursorPath, status: "dry-run" });
|
|
20248
21711
|
writeActions.push({ kind: "mcp-config", target: vscodePath, status: "dry-run" });
|
|
@@ -20255,7 +21718,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
20255
21718
|
continue;
|
|
20256
21719
|
}
|
|
20257
21720
|
if (editor === "claude") {
|
|
20258
|
-
const mcpPath =
|
|
21721
|
+
const mcpPath = path8.join(projectPath, ".mcp.json");
|
|
20259
21722
|
if (dryRun) {
|
|
20260
21723
|
writeActions.push({ kind: "mcp-config", target: mcpPath, status: "dry-run" });
|
|
20261
21724
|
} else {
|
|
@@ -20265,7 +21728,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
20265
21728
|
continue;
|
|
20266
21729
|
}
|
|
20267
21730
|
if (editor === "kilo") {
|
|
20268
|
-
const kiloPath =
|
|
21731
|
+
const kiloPath = path8.join(projectPath, ".kilocode", "mcp.json");
|
|
20269
21732
|
if (dryRun) {
|
|
20270
21733
|
writeActions.push({ kind: "mcp-config", target: kiloPath, status: "dry-run" });
|
|
20271
21734
|
} else {
|
|
@@ -20275,7 +21738,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
20275
21738
|
continue;
|
|
20276
21739
|
}
|
|
20277
21740
|
if (editor === "roo") {
|
|
20278
|
-
const rooPath =
|
|
21741
|
+
const rooPath = path8.join(projectPath, ".roo", "mcp.json");
|
|
20279
21742
|
if (dryRun) {
|
|
20280
21743
|
writeActions.push({ kind: "mcp-config", target: rooPath, status: "dry-run" });
|
|
20281
21744
|
} else {
|
|
@@ -20298,11 +21761,11 @@ Applying to ${projects.length} project(s)...`);
|
|
|
20298
21761
|
const rule = generateRuleContent(editor, {
|
|
20299
21762
|
workspaceName,
|
|
20300
21763
|
workspaceId: workspaceId && workspaceId !== "dry-run" ? workspaceId : void 0,
|
|
20301
|
-
projectName:
|
|
21764
|
+
projectName: path8.basename(projectPath),
|
|
20302
21765
|
mode
|
|
20303
21766
|
});
|
|
20304
21767
|
if (!rule) continue;
|
|
20305
|
-
const filePath =
|
|
21768
|
+
const filePath = path8.join(projectPath, rule.filename);
|
|
20306
21769
|
if (dryRun) {
|
|
20307
21770
|
writeActions.push({ kind: "rules", target: filePath, status: "dry-run" });
|
|
20308
21771
|
continue;
|
|
@@ -20335,6 +21798,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
20335
21798
|
console.log(`Toolset: ${toolset} (${toolsetDesc})`);
|
|
20336
21799
|
console.log(`Token reduction: ~75% compared to previous versions.`);
|
|
20337
21800
|
console.log(`Context Pack: ${contextPackEnabled ? "enabled" : "disabled"}`);
|
|
21801
|
+
console.log(`Response Timing: ${showTiming ? "enabled" : "disabled"}`);
|
|
20338
21802
|
}
|
|
20339
21803
|
console.log("\nNext steps:");
|
|
20340
21804
|
console.log("- Restart your editor/CLI after changing MCP config or rules.");
|
|
@@ -20350,6 +21814,9 @@ Applying to ${projects.length} project(s)...`);
|
|
|
20350
21814
|
console.log(
|
|
20351
21815
|
"- Toggle Context Pack with CONTEXTSTREAM_CONTEXT_PACK=true|false (and in dashboard settings)."
|
|
20352
21816
|
);
|
|
21817
|
+
console.log(
|
|
21818
|
+
"- Toggle Response Timing with CONTEXTSTREAM_SHOW_TIMING=true|false."
|
|
21819
|
+
);
|
|
20353
21820
|
console.log("");
|
|
20354
21821
|
console.log("You're set up! Now try these prompts in your AI tool:");
|
|
20355
21822
|
console.log(' 1) "session summary"');
|
|
@@ -20365,8 +21832,8 @@ Applying to ${projects.length} project(s)...`);
|
|
|
20365
21832
|
// src/index.ts
|
|
20366
21833
|
var ENABLE_PROMPTS2 = (process.env.CONTEXTSTREAM_ENABLE_PROMPTS || "true").toLowerCase() !== "false";
|
|
20367
21834
|
function showFirstRunMessage() {
|
|
20368
|
-
const configDir =
|
|
20369
|
-
const starShownFile =
|
|
21835
|
+
const configDir = join10(homedir6(), ".contextstream");
|
|
21836
|
+
const starShownFile = join10(configDir, ".star-shown");
|
|
20370
21837
|
if (existsSync4(starShownFile)) {
|
|
20371
21838
|
return;
|
|
20372
21839
|
}
|