@contextstream/mcp-server 0.3.30 → 0.3.32
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/README.md +3 -1
- package/dist/index.js +355 -112
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,9 @@ npx -y @contextstream/mcp-server setup
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
Notes:
|
|
50
|
-
- Uses browser/device login by default and creates an API key for you
|
|
50
|
+
- Uses browser/device login by default and creates an API key for you.
|
|
51
|
+
- To avoid re-auth prompts on subsequent runs, the wizard saves that API key to `~/.contextstream/credentials.json` (and also writes it into the MCP config files it generates). Delete that file to force a fresh login.
|
|
52
|
+
- Codex CLI MCP config is global-only (`~/.codex/config.toml`), so the wizard will always write Codex config globally when selected.
|
|
51
53
|
- Some tools still require UI/CLI-based MCP setup (the wizard will tell you when it can’t write a config).
|
|
52
54
|
- Preview changes without writing files: `npx -y @contextstream/mcp-server setup --dry-run`
|
|
53
55
|
|
package/dist/index.js
CHANGED
|
@@ -487,8 +487,8 @@ function getErrorMap() {
|
|
|
487
487
|
|
|
488
488
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
489
489
|
var makeIssue = (params) => {
|
|
490
|
-
const { data, path:
|
|
491
|
-
const fullPath = [...
|
|
490
|
+
const { data, path: path7, errorMaps, issueData } = params;
|
|
491
|
+
const fullPath = [...path7, ...issueData.path || []];
|
|
492
492
|
const fullIssue = {
|
|
493
493
|
...issueData,
|
|
494
494
|
path: fullPath
|
|
@@ -604,11 +604,11 @@ var errorUtil;
|
|
|
604
604
|
|
|
605
605
|
// node_modules/zod/v3/types.js
|
|
606
606
|
var ParseInputLazyPath = class {
|
|
607
|
-
constructor(parent, value,
|
|
607
|
+
constructor(parent, value, path7, key) {
|
|
608
608
|
this._cachedPath = [];
|
|
609
609
|
this.parent = parent;
|
|
610
610
|
this.data = value;
|
|
611
|
-
this._path =
|
|
611
|
+
this._path = path7;
|
|
612
612
|
this._key = key;
|
|
613
613
|
}
|
|
614
614
|
get path() {
|
|
@@ -4138,9 +4138,9 @@ var BASE_DELAY = 1e3;
|
|
|
4138
4138
|
async function sleep(ms) {
|
|
4139
4139
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4140
4140
|
}
|
|
4141
|
-
async function request(config,
|
|
4141
|
+
async function request(config, path7, options = {}) {
|
|
4142
4142
|
const { apiUrl, apiKey, jwt, userAgent } = config;
|
|
4143
|
-
const apiPath =
|
|
4143
|
+
const apiPath = path7.startsWith("/api/") ? path7 : `/api/v1${path7}`;
|
|
4144
4144
|
const url = `${apiUrl.replace(/\/$/, "")}${apiPath}`;
|
|
4145
4145
|
const maxRetries = options.retries ?? MAX_RETRIES;
|
|
4146
4146
|
const baseDelay = options.retryDelay ?? BASE_DELAY;
|
|
@@ -4641,6 +4641,29 @@ function unwrapApiResponse(result) {
|
|
|
4641
4641
|
}
|
|
4642
4642
|
return result;
|
|
4643
4643
|
}
|
|
4644
|
+
function normalizeNodeType(input) {
|
|
4645
|
+
const t = String(input ?? "").trim().toLowerCase();
|
|
4646
|
+
switch (t) {
|
|
4647
|
+
case "fact":
|
|
4648
|
+
case "insight":
|
|
4649
|
+
case "note":
|
|
4650
|
+
return "Fact";
|
|
4651
|
+
case "decision":
|
|
4652
|
+
return "Decision";
|
|
4653
|
+
case "preference":
|
|
4654
|
+
return "Preference";
|
|
4655
|
+
case "constraint":
|
|
4656
|
+
return "Constraint";
|
|
4657
|
+
case "habit":
|
|
4658
|
+
return "Habit";
|
|
4659
|
+
case "lesson":
|
|
4660
|
+
return "Lesson";
|
|
4661
|
+
default:
|
|
4662
|
+
throw new Error(
|
|
4663
|
+
`Invalid node_type: ${JSON.stringify(input)} (expected one of fact|decision|preference|constraint|habit|lesson)`
|
|
4664
|
+
);
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4644
4667
|
var ContextStreamClient = class {
|
|
4645
4668
|
constructor(config) {
|
|
4646
4669
|
this.config = config;
|
|
@@ -4721,13 +4744,19 @@ var ContextStreamClient = class {
|
|
|
4721
4744
|
const result = await request(this.config, "/workspaces", { body: input });
|
|
4722
4745
|
return unwrapApiResponse(result);
|
|
4723
4746
|
}
|
|
4724
|
-
updateWorkspace(workspaceId, input) {
|
|
4747
|
+
async updateWorkspace(workspaceId, input) {
|
|
4725
4748
|
uuidSchema.parse(workspaceId);
|
|
4726
|
-
|
|
4749
|
+
const result = await request(this.config, `/workspaces/${workspaceId}`, { method: "PUT", body: input });
|
|
4750
|
+
globalCache.delete(CacheKeys.workspace(workspaceId));
|
|
4751
|
+
globalCache.delete(`workspace_overview:${workspaceId}`);
|
|
4752
|
+
return result;
|
|
4727
4753
|
}
|
|
4728
|
-
deleteWorkspace(workspaceId) {
|
|
4754
|
+
async deleteWorkspace(workspaceId) {
|
|
4729
4755
|
uuidSchema.parse(workspaceId);
|
|
4730
|
-
|
|
4756
|
+
const result = await request(this.config, `/workspaces/${workspaceId}`, { method: "DELETE" });
|
|
4757
|
+
globalCache.delete(CacheKeys.workspace(workspaceId));
|
|
4758
|
+
globalCache.delete(`workspace_overview:${workspaceId}`);
|
|
4759
|
+
return result;
|
|
4731
4760
|
}
|
|
4732
4761
|
listProjects(params) {
|
|
4733
4762
|
const withDefaults = this.withDefaults(params || {});
|
|
@@ -4743,13 +4772,19 @@ var ContextStreamClient = class {
|
|
|
4743
4772
|
const result = await request(this.config, "/projects", { body: payload });
|
|
4744
4773
|
return unwrapApiResponse(result);
|
|
4745
4774
|
}
|
|
4746
|
-
updateProject(projectId, input) {
|
|
4775
|
+
async updateProject(projectId, input) {
|
|
4747
4776
|
uuidSchema.parse(projectId);
|
|
4748
|
-
|
|
4777
|
+
const result = await request(this.config, `/projects/${projectId}`, { method: "PUT", body: input });
|
|
4778
|
+
globalCache.delete(CacheKeys.project(projectId));
|
|
4779
|
+
globalCache.delete(`project_overview:${projectId}`);
|
|
4780
|
+
return result;
|
|
4749
4781
|
}
|
|
4750
|
-
deleteProject(projectId) {
|
|
4782
|
+
async deleteProject(projectId) {
|
|
4751
4783
|
uuidSchema.parse(projectId);
|
|
4752
|
-
|
|
4784
|
+
const result = await request(this.config, `/projects/${projectId}`, { method: "DELETE" });
|
|
4785
|
+
globalCache.delete(CacheKeys.project(projectId));
|
|
4786
|
+
globalCache.delete(`project_overview:${projectId}`);
|
|
4787
|
+
return result;
|
|
4753
4788
|
}
|
|
4754
4789
|
indexProject(projectId) {
|
|
4755
4790
|
uuidSchema.parse(projectId);
|
|
@@ -4818,7 +4853,24 @@ var ContextStreamClient = class {
|
|
|
4818
4853
|
return request(this.config, `/memory/events/workspace/${withDefaults.workspace_id}${suffix}`, { method: "GET" });
|
|
4819
4854
|
}
|
|
4820
4855
|
createKnowledgeNode(body) {
|
|
4821
|
-
|
|
4856
|
+
const withDefaults = this.withDefaults(body);
|
|
4857
|
+
if (!withDefaults.workspace_id) {
|
|
4858
|
+
throw new Error("workspace_id is required for creating knowledge nodes");
|
|
4859
|
+
}
|
|
4860
|
+
const summary = String(withDefaults.title ?? "").trim() || String(withDefaults.content ?? "").trim().slice(0, 120) || "Untitled";
|
|
4861
|
+
const details = String(withDefaults.content ?? "").trim();
|
|
4862
|
+
const apiBody = {
|
|
4863
|
+
workspace_id: withDefaults.workspace_id,
|
|
4864
|
+
project_id: withDefaults.project_id,
|
|
4865
|
+
node_type: normalizeNodeType(withDefaults.node_type),
|
|
4866
|
+
summary,
|
|
4867
|
+
details: details || void 0,
|
|
4868
|
+
valid_from: (/* @__PURE__ */ new Date()).toISOString()
|
|
4869
|
+
};
|
|
4870
|
+
if (withDefaults.relations && withDefaults.relations.length) {
|
|
4871
|
+
apiBody.context = { relations: withDefaults.relations };
|
|
4872
|
+
}
|
|
4873
|
+
return request(this.config, "/memory/nodes", { body: apiBody });
|
|
4822
4874
|
}
|
|
4823
4875
|
listKnowledgeNodes(params) {
|
|
4824
4876
|
const withDefaults = this.withDefaults(params || {});
|
|
@@ -4957,7 +5009,7 @@ var ContextStreamClient = class {
|
|
|
4957
5009
|
}
|
|
4958
5010
|
deleteMemoryEvent(eventId) {
|
|
4959
5011
|
uuidSchema.parse(eventId);
|
|
4960
|
-
return request(this.config, `/memory/events/${eventId}`, { method: "DELETE" });
|
|
5012
|
+
return request(this.config, `/memory/events/${eventId}`, { method: "DELETE" }).then((r) => r === "" || r == null ? { success: true } : r);
|
|
4961
5013
|
}
|
|
4962
5014
|
distillMemoryEvent(eventId) {
|
|
4963
5015
|
uuidSchema.parse(eventId);
|
|
@@ -4969,15 +5021,47 @@ var ContextStreamClient = class {
|
|
|
4969
5021
|
}
|
|
4970
5022
|
updateKnowledgeNode(nodeId, body) {
|
|
4971
5023
|
uuidSchema.parse(nodeId);
|
|
4972
|
-
|
|
5024
|
+
const apiBody = {};
|
|
5025
|
+
if (body.title !== void 0) apiBody.summary = body.title;
|
|
5026
|
+
if (body.content !== void 0) apiBody.details = body.content;
|
|
5027
|
+
if (body.relations && body.relations.length) apiBody.context = { relations: body.relations };
|
|
5028
|
+
return request(this.config, `/memory/nodes/${nodeId}`, { method: "PUT", body: apiBody });
|
|
4973
5029
|
}
|
|
4974
5030
|
deleteKnowledgeNode(nodeId) {
|
|
4975
5031
|
uuidSchema.parse(nodeId);
|
|
4976
|
-
return request(this.config, `/memory/nodes/${nodeId}`, { method: "DELETE" });
|
|
5032
|
+
return request(this.config, `/memory/nodes/${nodeId}`, { method: "DELETE" }).then((r) => r === "" || r == null ? { success: true } : r);
|
|
4977
5033
|
}
|
|
4978
5034
|
supersedeKnowledgeNode(nodeId, body) {
|
|
4979
5035
|
uuidSchema.parse(nodeId);
|
|
4980
|
-
return
|
|
5036
|
+
return (async () => {
|
|
5037
|
+
const existingResp = await this.getKnowledgeNode(nodeId);
|
|
5038
|
+
const existing = unwrapApiResponse(existingResp);
|
|
5039
|
+
if (!existing || !existing.workspace_id) {
|
|
5040
|
+
throw new Error("Failed to load existing node before superseding");
|
|
5041
|
+
}
|
|
5042
|
+
const createdResp = await this.createKnowledgeNode({
|
|
5043
|
+
workspace_id: existing.workspace_id,
|
|
5044
|
+
project_id: existing.project_id ?? void 0,
|
|
5045
|
+
node_type: existing.node_type,
|
|
5046
|
+
title: existing.summary ?? "Superseded node",
|
|
5047
|
+
content: body.new_content
|
|
5048
|
+
});
|
|
5049
|
+
const created = unwrapApiResponse(createdResp);
|
|
5050
|
+
if (!created?.id) {
|
|
5051
|
+
throw new Error("Failed to create replacement node for supersede");
|
|
5052
|
+
}
|
|
5053
|
+
await request(this.config, `/memory/nodes/${nodeId}/supersede`, { body: { superseded_by: created.id } });
|
|
5054
|
+
return {
|
|
5055
|
+
success: true,
|
|
5056
|
+
data: {
|
|
5057
|
+
status: "superseded",
|
|
5058
|
+
old_node_id: nodeId,
|
|
5059
|
+
new_node_id: created.id,
|
|
5060
|
+
reason: body.reason ?? null
|
|
5061
|
+
},
|
|
5062
|
+
error: null
|
|
5063
|
+
};
|
|
5064
|
+
})();
|
|
4981
5065
|
}
|
|
4982
5066
|
memoryTimeline(workspaceId) {
|
|
4983
5067
|
uuidSchema.parse(workspaceId);
|
|
@@ -5842,9 +5926,9 @@ var ContextStreamClient = class {
|
|
|
5842
5926
|
candidateParts.push("## Relevant Code\n");
|
|
5843
5927
|
currentChars += 18;
|
|
5844
5928
|
const codeEntries = code.results.map((c) => {
|
|
5845
|
-
const
|
|
5929
|
+
const path7 = c.file_path || "file";
|
|
5846
5930
|
const content = c.content?.slice(0, 150) || "";
|
|
5847
|
-
return { path:
|
|
5931
|
+
return { path: path7, entry: `\u2022 ${path7}: ${content}...
|
|
5848
5932
|
` };
|
|
5849
5933
|
});
|
|
5850
5934
|
for (const c of codeEntries) {
|
|
@@ -6776,6 +6860,8 @@ function getVersion() {
|
|
|
6776
6860
|
var VERSION = getVersion();
|
|
6777
6861
|
|
|
6778
6862
|
// src/tools.ts
|
|
6863
|
+
var LESSON_DEDUP_WINDOW_MS = 2 * 60 * 1e3;
|
|
6864
|
+
var recentLessonCaptures = /* @__PURE__ */ new Map();
|
|
6779
6865
|
function formatContent(data) {
|
|
6780
6866
|
return JSON.stringify(data, null, 2);
|
|
6781
6867
|
}
|
|
@@ -6785,6 +6871,35 @@ function toStructured(data) {
|
|
|
6785
6871
|
}
|
|
6786
6872
|
return void 0;
|
|
6787
6873
|
}
|
|
6874
|
+
function normalizeLessonField(value) {
|
|
6875
|
+
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
6876
|
+
}
|
|
6877
|
+
function buildLessonSignature(input, workspaceId, projectId) {
|
|
6878
|
+
return [
|
|
6879
|
+
workspaceId,
|
|
6880
|
+
projectId || "global",
|
|
6881
|
+
input.category,
|
|
6882
|
+
input.title,
|
|
6883
|
+
input.trigger,
|
|
6884
|
+
input.impact,
|
|
6885
|
+
input.prevention
|
|
6886
|
+
].map(normalizeLessonField).join("|");
|
|
6887
|
+
}
|
|
6888
|
+
function isDuplicateLessonCapture(signature) {
|
|
6889
|
+
const now = Date.now();
|
|
6890
|
+
for (const [key, ts] of recentLessonCaptures) {
|
|
6891
|
+
if (now - ts > LESSON_DEDUP_WINDOW_MS) {
|
|
6892
|
+
recentLessonCaptures.delete(key);
|
|
6893
|
+
}
|
|
6894
|
+
}
|
|
6895
|
+
const last = recentLessonCaptures.get(signature);
|
|
6896
|
+
if (last && now - last < LESSON_DEDUP_WINDOW_MS) {
|
|
6897
|
+
recentLessonCaptures.set(signature, now);
|
|
6898
|
+
return true;
|
|
6899
|
+
}
|
|
6900
|
+
recentLessonCaptures.set(signature, now);
|
|
6901
|
+
return false;
|
|
6902
|
+
}
|
|
6788
6903
|
function registerTools(server, client, sessionManager) {
|
|
6789
6904
|
const upgradeUrl = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
|
|
6790
6905
|
const defaultProTools = /* @__PURE__ */ new Set([
|
|
@@ -7936,26 +8051,26 @@ Optionally generates AI editor rules for automatic ContextStream usage.`,
|
|
|
7936
8051
|
const result = await client.associateWorkspace(input);
|
|
7937
8052
|
let rulesGenerated = [];
|
|
7938
8053
|
if (input.generate_editor_rules) {
|
|
7939
|
-
const
|
|
7940
|
-
const
|
|
8054
|
+
const fs5 = await import("fs");
|
|
8055
|
+
const path7 = await import("path");
|
|
7941
8056
|
for (const editor of getAvailableEditors()) {
|
|
7942
8057
|
const rule = generateRuleContent(editor, {
|
|
7943
8058
|
workspaceName: input.workspace_name,
|
|
7944
8059
|
workspaceId: input.workspace_id
|
|
7945
8060
|
});
|
|
7946
8061
|
if (rule) {
|
|
7947
|
-
const filePath =
|
|
8062
|
+
const filePath = path7.join(input.folder_path, rule.filename);
|
|
7948
8063
|
try {
|
|
7949
8064
|
let existingContent = "";
|
|
7950
8065
|
try {
|
|
7951
|
-
existingContent =
|
|
8066
|
+
existingContent = fs5.readFileSync(filePath, "utf-8");
|
|
7952
8067
|
} catch {
|
|
7953
8068
|
}
|
|
7954
8069
|
if (!existingContent) {
|
|
7955
|
-
|
|
8070
|
+
fs5.writeFileSync(filePath, rule.content);
|
|
7956
8071
|
rulesGenerated.push(rule.filename);
|
|
7957
8072
|
} else if (!existingContent.includes("ContextStream Integration")) {
|
|
7958
|
-
|
|
8073
|
+
fs5.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
|
|
7959
8074
|
rulesGenerated.push(rule.filename + " (appended)");
|
|
7960
8075
|
}
|
|
7961
8076
|
} catch {
|
|
@@ -8041,26 +8156,26 @@ Behavior:
|
|
|
8041
8156
|
});
|
|
8042
8157
|
let rulesGenerated = [];
|
|
8043
8158
|
if (input.generate_editor_rules) {
|
|
8044
|
-
const
|
|
8045
|
-
const
|
|
8159
|
+
const fs5 = await import("fs");
|
|
8160
|
+
const path7 = await import("path");
|
|
8046
8161
|
for (const editor of getAvailableEditors()) {
|
|
8047
8162
|
const rule = generateRuleContent(editor, {
|
|
8048
8163
|
workspaceName: newWorkspace.name || input.workspace_name,
|
|
8049
8164
|
workspaceId: newWorkspace.id
|
|
8050
8165
|
});
|
|
8051
8166
|
if (!rule) continue;
|
|
8052
|
-
const filePath =
|
|
8167
|
+
const filePath = path7.join(folderPath, rule.filename);
|
|
8053
8168
|
try {
|
|
8054
8169
|
let existingContent = "";
|
|
8055
8170
|
try {
|
|
8056
|
-
existingContent =
|
|
8171
|
+
existingContent = fs5.readFileSync(filePath, "utf-8");
|
|
8057
8172
|
} catch {
|
|
8058
8173
|
}
|
|
8059
8174
|
if (!existingContent) {
|
|
8060
|
-
|
|
8175
|
+
fs5.writeFileSync(filePath, rule.content);
|
|
8061
8176
|
rulesGenerated.push(rule.filename);
|
|
8062
8177
|
} else if (!existingContent.includes("ContextStream Integration")) {
|
|
8063
|
-
|
|
8178
|
+
fs5.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
|
|
8064
8179
|
rulesGenerated.push(rule.filename + " (appended)");
|
|
8065
8180
|
}
|
|
8066
8181
|
} catch {
|
|
@@ -8202,6 +8317,25 @@ The lesson will be tagged with 'lesson' and stored with structured metadata for
|
|
|
8202
8317
|
isError: true
|
|
8203
8318
|
};
|
|
8204
8319
|
}
|
|
8320
|
+
const lessonSignature = buildLessonSignature({
|
|
8321
|
+
title: input.title,
|
|
8322
|
+
category: input.category,
|
|
8323
|
+
trigger: input.trigger,
|
|
8324
|
+
impact: input.impact,
|
|
8325
|
+
prevention: input.prevention
|
|
8326
|
+
}, workspaceId, projectId);
|
|
8327
|
+
if (isDuplicateLessonCapture(lessonSignature)) {
|
|
8328
|
+
return {
|
|
8329
|
+
content: [{
|
|
8330
|
+
type: "text",
|
|
8331
|
+
text: `\u2139\uFE0F Duplicate lesson capture ignored: "${input.title}" was already recorded recently.`
|
|
8332
|
+
}],
|
|
8333
|
+
structuredContent: {
|
|
8334
|
+
deduped: true,
|
|
8335
|
+
title: input.title
|
|
8336
|
+
}
|
|
8337
|
+
};
|
|
8338
|
+
}
|
|
8205
8339
|
const lessonContent = [
|
|
8206
8340
|
`## ${input.title}`,
|
|
8207
8341
|
"",
|
|
@@ -8460,8 +8594,8 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
8460
8594
|
})
|
|
8461
8595
|
},
|
|
8462
8596
|
async (input) => {
|
|
8463
|
-
const
|
|
8464
|
-
const
|
|
8597
|
+
const fs5 = await import("fs");
|
|
8598
|
+
const path7 = await import("path");
|
|
8465
8599
|
const editors = input.editors?.includes("all") || !input.editors ? getAvailableEditors() : input.editors.filter((e) => e !== "all");
|
|
8466
8600
|
const results = [];
|
|
8467
8601
|
for (const editor of editors) {
|
|
@@ -8476,7 +8610,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
8476
8610
|
results.push({ editor, filename: "", status: "unknown editor" });
|
|
8477
8611
|
continue;
|
|
8478
8612
|
}
|
|
8479
|
-
const filePath =
|
|
8613
|
+
const filePath = path7.join(input.folder_path, rule.filename);
|
|
8480
8614
|
if (input.dry_run) {
|
|
8481
8615
|
results.push({
|
|
8482
8616
|
editor,
|
|
@@ -8488,15 +8622,15 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
8488
8622
|
try {
|
|
8489
8623
|
let existingContent = "";
|
|
8490
8624
|
try {
|
|
8491
|
-
existingContent =
|
|
8625
|
+
existingContent = fs5.readFileSync(filePath, "utf-8");
|
|
8492
8626
|
} catch {
|
|
8493
8627
|
}
|
|
8494
8628
|
if (existingContent && !existingContent.includes("ContextStream Integration")) {
|
|
8495
8629
|
const updatedContent = existingContent + "\n\n" + rule.content;
|
|
8496
|
-
|
|
8630
|
+
fs5.writeFileSync(filePath, updatedContent);
|
|
8497
8631
|
results.push({ editor, filename: rule.filename, status: "appended to existing" });
|
|
8498
8632
|
} else {
|
|
8499
|
-
|
|
8633
|
+
fs5.writeFileSync(filePath, rule.content);
|
|
8500
8634
|
results.push({ editor, filename: rule.filename, status: "created" });
|
|
8501
8635
|
}
|
|
8502
8636
|
} catch (err) {
|
|
@@ -9166,8 +9300,8 @@ var SessionManager = class {
|
|
|
9166
9300
|
/**
|
|
9167
9301
|
* Set the folder path hint (can be passed from tools that know the workspace path)
|
|
9168
9302
|
*/
|
|
9169
|
-
setFolderPath(
|
|
9170
|
-
this.folderPath =
|
|
9303
|
+
setFolderPath(path7) {
|
|
9304
|
+
this.folderPath = path7;
|
|
9171
9305
|
}
|
|
9172
9306
|
/**
|
|
9173
9307
|
* Mark that context_smart has been called in this session
|
|
@@ -9234,11 +9368,11 @@ var SessionManager = class {
|
|
|
9234
9368
|
}
|
|
9235
9369
|
if (this.ideRoots.length === 0) {
|
|
9236
9370
|
const cwd = process.cwd();
|
|
9237
|
-
const
|
|
9371
|
+
const fs5 = await import("fs");
|
|
9238
9372
|
const projectIndicators = [".git", "package.json", "Cargo.toml", "pyproject.toml", ".contextstream"];
|
|
9239
9373
|
const hasProjectIndicator = projectIndicators.some((f) => {
|
|
9240
9374
|
try {
|
|
9241
|
-
return
|
|
9375
|
+
return fs5.existsSync(`${cwd}/${f}`);
|
|
9242
9376
|
} catch {
|
|
9243
9377
|
return false;
|
|
9244
9378
|
}
|
|
@@ -9416,15 +9550,79 @@ var SessionManager = class {
|
|
|
9416
9550
|
|
|
9417
9551
|
// src/index.ts
|
|
9418
9552
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
9419
|
-
import { homedir as
|
|
9420
|
-
import { join as
|
|
9553
|
+
import { homedir as homedir3 } from "os";
|
|
9554
|
+
import { join as join6 } from "path";
|
|
9421
9555
|
|
|
9422
9556
|
// src/setup.ts
|
|
9557
|
+
import * as fs4 from "node:fs/promises";
|
|
9558
|
+
import * as path6 from "node:path";
|
|
9559
|
+
import { homedir as homedir2 } from "node:os";
|
|
9560
|
+
import { stdin, stdout } from "node:process";
|
|
9561
|
+
import { createInterface } from "node:readline/promises";
|
|
9562
|
+
|
|
9563
|
+
// src/credentials.ts
|
|
9423
9564
|
import * as fs3 from "node:fs/promises";
|
|
9424
9565
|
import * as path5 from "node:path";
|
|
9425
9566
|
import { homedir } from "node:os";
|
|
9426
|
-
|
|
9427
|
-
|
|
9567
|
+
function normalizeApiUrl(input) {
|
|
9568
|
+
return String(input ?? "").trim().replace(/\/+$/, "");
|
|
9569
|
+
}
|
|
9570
|
+
function credentialsFilePath() {
|
|
9571
|
+
return path5.join(homedir(), ".contextstream", "credentials.json");
|
|
9572
|
+
}
|
|
9573
|
+
function isRecord(value) {
|
|
9574
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9575
|
+
}
|
|
9576
|
+
async function readSavedCredentials() {
|
|
9577
|
+
const filePath = credentialsFilePath();
|
|
9578
|
+
try {
|
|
9579
|
+
const raw = await fs3.readFile(filePath, "utf8");
|
|
9580
|
+
const parsed = JSON.parse(raw);
|
|
9581
|
+
if (!isRecord(parsed)) return null;
|
|
9582
|
+
const version = parsed.version;
|
|
9583
|
+
if (version !== 1) return null;
|
|
9584
|
+
const apiUrl = typeof parsed.api_url === "string" ? normalizeApiUrl(parsed.api_url) : "";
|
|
9585
|
+
const apiKey = typeof parsed.api_key === "string" ? parsed.api_key.trim() : "";
|
|
9586
|
+
if (!apiUrl || !apiKey) return null;
|
|
9587
|
+
const email = typeof parsed.email === "string" ? parsed.email.trim() : "";
|
|
9588
|
+
const createdAt = typeof parsed.created_at === "string" ? parsed.created_at : "";
|
|
9589
|
+
const updatedAt = typeof parsed.updated_at === "string" ? parsed.updated_at : "";
|
|
9590
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9591
|
+
return {
|
|
9592
|
+
version: 1,
|
|
9593
|
+
api_url: apiUrl,
|
|
9594
|
+
api_key: apiKey,
|
|
9595
|
+
email: email || void 0,
|
|
9596
|
+
created_at: createdAt || now,
|
|
9597
|
+
updated_at: updatedAt || now
|
|
9598
|
+
};
|
|
9599
|
+
} catch {
|
|
9600
|
+
return null;
|
|
9601
|
+
}
|
|
9602
|
+
}
|
|
9603
|
+
async function writeSavedCredentials(input) {
|
|
9604
|
+
const filePath = credentialsFilePath();
|
|
9605
|
+
await fs3.mkdir(path5.dirname(filePath), { recursive: true });
|
|
9606
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9607
|
+
const existing = await readSavedCredentials();
|
|
9608
|
+
const value = {
|
|
9609
|
+
version: 1,
|
|
9610
|
+
api_url: normalizeApiUrl(input.apiUrl),
|
|
9611
|
+
api_key: input.apiKey.trim(),
|
|
9612
|
+
email: input.email?.trim() || void 0,
|
|
9613
|
+
created_at: existing?.created_at || now,
|
|
9614
|
+
updated_at: now
|
|
9615
|
+
};
|
|
9616
|
+
const body = JSON.stringify(value, null, 2) + "\n";
|
|
9617
|
+
await fs3.writeFile(filePath, body, { encoding: "utf8", mode: 384 });
|
|
9618
|
+
try {
|
|
9619
|
+
await fs3.chmod(filePath, 384);
|
|
9620
|
+
} catch {
|
|
9621
|
+
}
|
|
9622
|
+
return { path: filePath, value };
|
|
9623
|
+
}
|
|
9624
|
+
|
|
9625
|
+
// src/setup.ts
|
|
9428
9626
|
var EDITOR_LABELS = {
|
|
9429
9627
|
codex: "Codex CLI",
|
|
9430
9628
|
claude: "Claude Code",
|
|
@@ -9435,6 +9633,9 @@ var EDITOR_LABELS = {
|
|
|
9435
9633
|
roo: "Roo Code",
|
|
9436
9634
|
aider: "Aider"
|
|
9437
9635
|
};
|
|
9636
|
+
function supportsProjectMcpConfig(editor) {
|
|
9637
|
+
return editor === "cursor" || editor === "claude" || editor === "kilo" || editor === "roo";
|
|
9638
|
+
}
|
|
9438
9639
|
function normalizeInput(value) {
|
|
9439
9640
|
return value.trim();
|
|
9440
9641
|
}
|
|
@@ -9459,42 +9660,42 @@ function parseNumberList(input, max) {
|
|
|
9459
9660
|
}
|
|
9460
9661
|
async function fileExists(filePath) {
|
|
9461
9662
|
try {
|
|
9462
|
-
await
|
|
9663
|
+
await fs4.stat(filePath);
|
|
9463
9664
|
return true;
|
|
9464
9665
|
} catch {
|
|
9465
9666
|
return false;
|
|
9466
9667
|
}
|
|
9467
9668
|
}
|
|
9468
9669
|
async function upsertTextFile(filePath, content, marker) {
|
|
9469
|
-
await
|
|
9670
|
+
await fs4.mkdir(path6.dirname(filePath), { recursive: true });
|
|
9470
9671
|
const exists = await fileExists(filePath);
|
|
9471
9672
|
if (!exists) {
|
|
9472
|
-
await
|
|
9673
|
+
await fs4.writeFile(filePath, content, "utf8");
|
|
9473
9674
|
return "created";
|
|
9474
9675
|
}
|
|
9475
|
-
const existing = await
|
|
9676
|
+
const existing = await fs4.readFile(filePath, "utf8").catch(() => "");
|
|
9476
9677
|
if (existing.includes(marker)) return "skipped";
|
|
9477
9678
|
const joined = existing.trimEnd() + "\n\n" + content.trim() + "\n";
|
|
9478
|
-
await
|
|
9679
|
+
await fs4.writeFile(filePath, joined, "utf8");
|
|
9479
9680
|
return "appended";
|
|
9480
9681
|
}
|
|
9481
9682
|
function globalRulesPathForEditor(editor) {
|
|
9482
|
-
const home =
|
|
9683
|
+
const home = homedir2();
|
|
9483
9684
|
switch (editor) {
|
|
9484
9685
|
case "codex":
|
|
9485
|
-
return
|
|
9686
|
+
return path6.join(home, ".codex", "AGENTS.md");
|
|
9486
9687
|
case "claude":
|
|
9487
|
-
return
|
|
9688
|
+
return path6.join(home, ".claude", "CLAUDE.md");
|
|
9488
9689
|
case "windsurf":
|
|
9489
|
-
return
|
|
9690
|
+
return path6.join(home, ".codeium", "windsurf", "memories", "global_rules.md");
|
|
9490
9691
|
case "cline":
|
|
9491
|
-
return
|
|
9692
|
+
return path6.join(home, "Documents", "Cline", "Rules", "contextstream.md");
|
|
9492
9693
|
case "kilo":
|
|
9493
|
-
return
|
|
9694
|
+
return path6.join(home, ".kilocode", "rules", "contextstream.md");
|
|
9494
9695
|
case "roo":
|
|
9495
|
-
return
|
|
9696
|
+
return path6.join(home, ".roo", "rules", "contextstream.md");
|
|
9496
9697
|
case "aider":
|
|
9497
|
-
return
|
|
9698
|
+
return path6.join(home, ".aider.conf.yml");
|
|
9498
9699
|
case "cursor":
|
|
9499
9700
|
return null;
|
|
9500
9701
|
default:
|
|
@@ -9541,11 +9742,11 @@ function tryParseJsonLike(raw) {
|
|
|
9541
9742
|
}
|
|
9542
9743
|
}
|
|
9543
9744
|
async function upsertJsonMcpConfig(filePath, server) {
|
|
9544
|
-
await
|
|
9745
|
+
await fs4.mkdir(path6.dirname(filePath), { recursive: true });
|
|
9545
9746
|
const exists = await fileExists(filePath);
|
|
9546
9747
|
let root = {};
|
|
9547
9748
|
if (exists) {
|
|
9548
|
-
const raw = await
|
|
9749
|
+
const raw = await fs4.readFile(filePath, "utf8").catch(() => "");
|
|
9549
9750
|
const parsed = tryParseJsonLike(raw);
|
|
9550
9751
|
if (!parsed.ok) throw new Error(`Invalid JSON in ${filePath}: ${parsed.error}`);
|
|
9551
9752
|
root = parsed.value;
|
|
@@ -9555,16 +9756,16 @@ async function upsertJsonMcpConfig(filePath, server) {
|
|
|
9555
9756
|
const before = JSON.stringify(root.mcpServers.contextstream ?? null);
|
|
9556
9757
|
root.mcpServers.contextstream = server;
|
|
9557
9758
|
const after = JSON.stringify(root.mcpServers.contextstream ?? null);
|
|
9558
|
-
await
|
|
9759
|
+
await fs4.writeFile(filePath, JSON.stringify(root, null, 2) + "\n", "utf8");
|
|
9559
9760
|
if (!exists) return "created";
|
|
9560
9761
|
return before === after ? "skipped" : "updated";
|
|
9561
9762
|
}
|
|
9562
9763
|
async function upsertJsonVsCodeMcpConfig(filePath, server) {
|
|
9563
|
-
await
|
|
9764
|
+
await fs4.mkdir(path6.dirname(filePath), { recursive: true });
|
|
9564
9765
|
const exists = await fileExists(filePath);
|
|
9565
9766
|
let root = {};
|
|
9566
9767
|
if (exists) {
|
|
9567
|
-
const raw = await
|
|
9768
|
+
const raw = await fs4.readFile(filePath, "utf8").catch(() => "");
|
|
9568
9769
|
const parsed = tryParseJsonLike(raw);
|
|
9569
9770
|
if (!parsed.ok) throw new Error(`Invalid JSON in ${filePath}: ${parsed.error}`);
|
|
9570
9771
|
root = parsed.value;
|
|
@@ -9574,25 +9775,25 @@ async function upsertJsonVsCodeMcpConfig(filePath, server) {
|
|
|
9574
9775
|
const before = JSON.stringify(root.servers.contextstream ?? null);
|
|
9575
9776
|
root.servers.contextstream = server;
|
|
9576
9777
|
const after = JSON.stringify(root.servers.contextstream ?? null);
|
|
9577
|
-
await
|
|
9778
|
+
await fs4.writeFile(filePath, JSON.stringify(root, null, 2) + "\n", "utf8");
|
|
9578
9779
|
if (!exists) return "created";
|
|
9579
9780
|
return before === after ? "skipped" : "updated";
|
|
9580
9781
|
}
|
|
9581
9782
|
function claudeDesktopConfigPath() {
|
|
9582
|
-
const home =
|
|
9783
|
+
const home = homedir2();
|
|
9583
9784
|
if (process.platform === "darwin") {
|
|
9584
|
-
return
|
|
9785
|
+
return path6.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
9585
9786
|
}
|
|
9586
9787
|
if (process.platform === "win32") {
|
|
9587
|
-
const appData = process.env.APPDATA ||
|
|
9588
|
-
return
|
|
9788
|
+
const appData = process.env.APPDATA || path6.join(home, "AppData", "Roaming");
|
|
9789
|
+
return path6.join(appData, "Claude", "claude_desktop_config.json");
|
|
9589
9790
|
}
|
|
9590
9791
|
return null;
|
|
9591
9792
|
}
|
|
9592
9793
|
async function upsertCodexTomlConfig(filePath, params) {
|
|
9593
|
-
await
|
|
9794
|
+
await fs4.mkdir(path6.dirname(filePath), { recursive: true });
|
|
9594
9795
|
const exists = await fileExists(filePath);
|
|
9595
|
-
const existing = exists ? await
|
|
9796
|
+
const existing = exists ? await fs4.readFile(filePath, "utf8").catch(() => "") : "";
|
|
9596
9797
|
const marker = "[mcp_servers.contextstream]";
|
|
9597
9798
|
const envMarker = "[mcp_servers.contextstream.env]";
|
|
9598
9799
|
const block = `
|
|
@@ -9607,15 +9808,15 @@ CONTEXTSTREAM_API_URL = "${params.apiUrl}"
|
|
|
9607
9808
|
CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
9608
9809
|
`;
|
|
9609
9810
|
if (!exists) {
|
|
9610
|
-
await
|
|
9811
|
+
await fs4.writeFile(filePath, block.trimStart(), "utf8");
|
|
9611
9812
|
return "created";
|
|
9612
9813
|
}
|
|
9613
9814
|
if (!existing.includes(marker)) {
|
|
9614
|
-
await
|
|
9815
|
+
await fs4.writeFile(filePath, existing.trimEnd() + block, "utf8");
|
|
9615
9816
|
return "updated";
|
|
9616
9817
|
}
|
|
9617
9818
|
if (!existing.includes(envMarker)) {
|
|
9618
|
-
await
|
|
9819
|
+
await fs4.writeFile(filePath, existing.trimEnd() + "\n\n" + envMarker + `
|
|
9619
9820
|
CONTEXTSTREAM_API_URL = "${params.apiUrl}"
|
|
9620
9821
|
CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
9621
9822
|
`, "utf8");
|
|
@@ -9656,18 +9857,18 @@ CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
|
9656
9857
|
}
|
|
9657
9858
|
const updated = out.join("\n");
|
|
9658
9859
|
if (updated === existing) return "skipped";
|
|
9659
|
-
await
|
|
9860
|
+
await fs4.writeFile(filePath, updated, "utf8");
|
|
9660
9861
|
return "updated";
|
|
9661
9862
|
}
|
|
9662
9863
|
async function discoverProjectsUnderFolder(parentFolder) {
|
|
9663
|
-
const entries = await
|
|
9664
|
-
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
9864
|
+
const entries = await fs4.readdir(parentFolder, { withFileTypes: true });
|
|
9865
|
+
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => path6.join(parentFolder, e.name));
|
|
9665
9866
|
const projects = [];
|
|
9666
9867
|
for (const dir of candidates) {
|
|
9667
|
-
const hasGit = await fileExists(
|
|
9668
|
-
const hasPkg = await fileExists(
|
|
9669
|
-
const hasCargo = await fileExists(
|
|
9670
|
-
const hasPyProject = await fileExists(
|
|
9868
|
+
const hasGit = await fileExists(path6.join(dir, ".git"));
|
|
9869
|
+
const hasPkg = await fileExists(path6.join(dir, "package.json"));
|
|
9870
|
+
const hasCargo = await fileExists(path6.join(dir, "Cargo.toml"));
|
|
9871
|
+
const hasPyProject = await fileExists(path6.join(dir, "pyproject.toml"));
|
|
9671
9872
|
if (hasGit || hasPkg || hasCargo || hasPyProject) projects.push(dir);
|
|
9672
9873
|
}
|
|
9673
9874
|
return projects;
|
|
@@ -9691,16 +9892,32 @@ async function runSetupWizard(args) {
|
|
|
9691
9892
|
console.log("This configures ContextStream MCP + rules for your AI editor(s).");
|
|
9692
9893
|
if (dryRun) console.log("DRY RUN: no files will be written.\n");
|
|
9693
9894
|
else console.log("");
|
|
9694
|
-
const
|
|
9695
|
-
const
|
|
9696
|
-
|
|
9697
|
-
|
|
9895
|
+
const savedCreds = await readSavedCredentials();
|
|
9896
|
+
const apiUrlDefault = normalizeApiUrl(process.env.CONTEXTSTREAM_API_URL || savedCreds?.api_url || "https://api.contextstream.io");
|
|
9897
|
+
const apiUrl = normalizeApiUrl(
|
|
9898
|
+
normalizeInput(await rl.question(`ContextStream API URL [${apiUrlDefault}]: `)) || apiUrlDefault
|
|
9899
|
+
);
|
|
9698
9900
|
let apiKey = normalizeInput(process.env.CONTEXTSTREAM_API_KEY || "");
|
|
9901
|
+
let apiKeySource = apiKey ? "env" : "unknown";
|
|
9699
9902
|
if (apiKey) {
|
|
9700
9903
|
const confirm = normalizeInput(
|
|
9701
9904
|
await rl.question(`Use CONTEXTSTREAM_API_KEY from environment (${maskApiKey(apiKey)})? [Y/n]: `)
|
|
9702
9905
|
);
|
|
9703
|
-
if (confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no")
|
|
9906
|
+
if (confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no") {
|
|
9907
|
+
apiKey = "";
|
|
9908
|
+
apiKeySource = "unknown";
|
|
9909
|
+
}
|
|
9910
|
+
}
|
|
9911
|
+
if (!apiKey && savedCreds?.api_key && normalizeApiUrl(savedCreds.api_url) === apiUrl) {
|
|
9912
|
+
const confirm = normalizeInput(
|
|
9913
|
+
await rl.question(
|
|
9914
|
+
`Use saved API key from ${credentialsFilePath()} (${maskApiKey(savedCreds.api_key)})? [Y/n]: `
|
|
9915
|
+
)
|
|
9916
|
+
);
|
|
9917
|
+
if (!(confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no")) {
|
|
9918
|
+
apiKey = savedCreds.api_key;
|
|
9919
|
+
apiKeySource = "saved";
|
|
9920
|
+
}
|
|
9704
9921
|
}
|
|
9705
9922
|
if (!apiKey) {
|
|
9706
9923
|
console.log("\nAuthentication:");
|
|
@@ -9711,6 +9928,7 @@ async function runSetupWizard(args) {
|
|
|
9711
9928
|
console.log("\nYou need a ContextStream API key to continue.");
|
|
9712
9929
|
console.log("Create one here (then paste it): https://app.contextstream.io/settings/api-keys\n");
|
|
9713
9930
|
apiKey = normalizeInput(await rl.question("CONTEXTSTREAM_API_KEY: "));
|
|
9931
|
+
apiKeySource = "paste";
|
|
9714
9932
|
} else {
|
|
9715
9933
|
const anonClient = new ContextStreamClient(buildClientConfig({ apiUrl }));
|
|
9716
9934
|
let device;
|
|
@@ -9773,6 +9991,7 @@ Code: ${device.user_code}`);
|
|
|
9773
9991
|
throw new Error("API key creation returned an unexpected response.");
|
|
9774
9992
|
}
|
|
9775
9993
|
apiKey = createdKey.secret_key.trim();
|
|
9994
|
+
apiKeySource = "browser";
|
|
9776
9995
|
console.log(`
|
|
9777
9996
|
Created API key: ${maskApiKey(apiKey)}
|
|
9778
9997
|
`);
|
|
@@ -9789,6 +10008,17 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9789
10008
|
const email = typeof me?.data?.email === "string" ? me.data.email : typeof me?.email === "string" ? me.email : void 0;
|
|
9790
10009
|
console.log(`Authenticated as: ${email || "unknown user"} (${maskApiKey(apiKey)})
|
|
9791
10010
|
`);
|
|
10011
|
+
if (!dryRun && (apiKeySource === "browser" || apiKeySource === "paste")) {
|
|
10012
|
+
try {
|
|
10013
|
+
const saved = await writeSavedCredentials({ apiUrl, apiKey, email });
|
|
10014
|
+
console.log(`Saved API key for future runs: ${saved.path} (${maskApiKey(apiKey)})
|
|
10015
|
+
`);
|
|
10016
|
+
} catch (err) {
|
|
10017
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10018
|
+
console.log(`Warning: failed to save API key for future runs (${credentialsFilePath()}): ${msg}
|
|
10019
|
+
`);
|
|
10020
|
+
}
|
|
10021
|
+
}
|
|
9792
10022
|
let workspaceId;
|
|
9793
10023
|
let workspaceName;
|
|
9794
10024
|
console.log("Workspace setup:");
|
|
@@ -9851,6 +10081,8 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9851
10081
|
const selectedRaw = normalizeInput(await rl.question("Editors [all]: ")) || "all";
|
|
9852
10082
|
const selectedNums = parseNumberList(selectedRaw, editors.length);
|
|
9853
10083
|
const selectedEditors = selectedNums.length ? selectedNums.map((n) => editors[n - 1]) : editors;
|
|
10084
|
+
const hasCodex = selectedEditors.includes("codex");
|
|
10085
|
+
const hasProjectMcpEditors = selectedEditors.some((e) => supportsProjectMcpConfig(e));
|
|
9854
10086
|
console.log("\nInstall rules as:");
|
|
9855
10087
|
console.log(" 1) Global");
|
|
9856
10088
|
console.log(" 2) Project");
|
|
@@ -9858,20 +10090,31 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9858
10090
|
const scopeChoice = normalizeInput(await rl.question("Choose [1/2/3] (default 3): ")) || "3";
|
|
9859
10091
|
const scope = scopeChoice === "1" ? "global" : scopeChoice === "2" ? "project" : "both";
|
|
9860
10092
|
console.log("\nInstall MCP server config as:");
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
10093
|
+
if (hasCodex && !hasProjectMcpEditors) {
|
|
10094
|
+
console.log(" 1) Global (Codex CLI supports global config only)");
|
|
10095
|
+
console.log(" 2) Skip (rules only)");
|
|
10096
|
+
} else {
|
|
10097
|
+
console.log(" 1) Global");
|
|
10098
|
+
console.log(" 2) Project");
|
|
10099
|
+
console.log(" 3) Both");
|
|
10100
|
+
console.log(" 4) Skip (rules only)");
|
|
10101
|
+
if (hasCodex) {
|
|
10102
|
+
console.log(" Note: Codex CLI does not support per-project MCP config; it will be configured globally if selected.");
|
|
10103
|
+
}
|
|
10104
|
+
}
|
|
10105
|
+
const mcpChoiceDefault = hasCodex && !hasProjectMcpEditors ? "1" : "3";
|
|
10106
|
+
const mcpChoice = normalizeInput(await rl.question(`Choose [${hasCodex && !hasProjectMcpEditors ? "1/2" : "1/2/3/4"}] (default ${mcpChoiceDefault}): `)) || mcpChoiceDefault;
|
|
10107
|
+
const mcpScope = mcpChoice === "2" && hasCodex && !hasProjectMcpEditors ? "skip" : mcpChoice === "4" ? "skip" : mcpChoice === "1" ? "global" : mcpChoice === "2" ? "project" : "both";
|
|
9867
10108
|
const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey });
|
|
9868
10109
|
const vsCodeServer = buildContextStreamVsCodeServer({ apiUrl, apiKey });
|
|
9869
|
-
|
|
10110
|
+
const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex;
|
|
10111
|
+
if (needsGlobalMcpConfig) {
|
|
9870
10112
|
console.log("\nInstalling global MCP config...");
|
|
9871
10113
|
for (const editor of selectedEditors) {
|
|
10114
|
+
if (mcpScope === "project" && editor !== "codex") continue;
|
|
9872
10115
|
try {
|
|
9873
10116
|
if (editor === "codex") {
|
|
9874
|
-
const filePath =
|
|
10117
|
+
const filePath = path6.join(homedir2(), ".codex", "config.toml");
|
|
9875
10118
|
if (dryRun) {
|
|
9876
10119
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
9877
10120
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -9883,7 +10126,7 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9883
10126
|
continue;
|
|
9884
10127
|
}
|
|
9885
10128
|
if (editor === "windsurf") {
|
|
9886
|
-
const filePath =
|
|
10129
|
+
const filePath = path6.join(homedir2(), ".codeium", "windsurf", "mcp_config.json");
|
|
9887
10130
|
if (dryRun) {
|
|
9888
10131
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
9889
10132
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -9915,7 +10158,7 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9915
10158
|
continue;
|
|
9916
10159
|
}
|
|
9917
10160
|
if (editor === "cursor") {
|
|
9918
|
-
const filePath =
|
|
10161
|
+
const filePath = path6.join(homedir2(), ".cursor", "mcp.json");
|
|
9919
10162
|
if (dryRun) {
|
|
9920
10163
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
9921
10164
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -9969,12 +10212,12 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9969
10212
|
}
|
|
9970
10213
|
}
|
|
9971
10214
|
const projectPaths = /* @__PURE__ */ new Set();
|
|
9972
|
-
const needsProjects = scope === "project" || scope === "both" || mcpScope === "project" || mcpScope === "both";
|
|
10215
|
+
const needsProjects = scope === "project" || scope === "both" || (mcpScope === "project" || mcpScope === "both") && hasProjectMcpEditors;
|
|
9973
10216
|
if (needsProjects) {
|
|
9974
10217
|
console.log("\nProject setup...");
|
|
9975
10218
|
const addCwd = normalizeInput(await rl.question(`Add current folder as a project? [Y/n] (${process.cwd()}): `));
|
|
9976
10219
|
if (addCwd.toLowerCase() !== "n" && addCwd.toLowerCase() !== "no") {
|
|
9977
|
-
projectPaths.add(
|
|
10220
|
+
projectPaths.add(path6.resolve(process.cwd()));
|
|
9978
10221
|
}
|
|
9979
10222
|
while (true) {
|
|
9980
10223
|
console.log("\n 1) Add another project path");
|
|
@@ -9984,13 +10227,13 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9984
10227
|
if (choice === "3") break;
|
|
9985
10228
|
if (choice === "1") {
|
|
9986
10229
|
const p = normalizeInput(await rl.question("Project folder path: "));
|
|
9987
|
-
if (p) projectPaths.add(
|
|
10230
|
+
if (p) projectPaths.add(path6.resolve(p));
|
|
9988
10231
|
continue;
|
|
9989
10232
|
}
|
|
9990
10233
|
if (choice === "2") {
|
|
9991
10234
|
const parent = normalizeInput(await rl.question("Parent folder path: "));
|
|
9992
10235
|
if (!parent) continue;
|
|
9993
|
-
const parentAbs =
|
|
10236
|
+
const parentAbs = path6.resolve(parent);
|
|
9994
10237
|
const projects2 = await discoverProjectsUnderFolder(parentAbs);
|
|
9995
10238
|
if (projects2.length === 0) {
|
|
9996
10239
|
console.log(`No projects detected under ${parentAbs} (looked for .git/package.json/Cargo.toml/pyproject.toml).`);
|
|
@@ -10020,21 +10263,21 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10020
10263
|
workspace_name: workspaceName,
|
|
10021
10264
|
create_parent_mapping: createParentMapping
|
|
10022
10265
|
});
|
|
10023
|
-
writeActions.push({ kind: "workspace-config", target:
|
|
10266
|
+
writeActions.push({ kind: "workspace-config", target: path6.join(projectPath, ".contextstream", "config.json"), status: "created" });
|
|
10024
10267
|
console.log(`- Linked workspace in ${projectPath}`);
|
|
10025
10268
|
} catch (err) {
|
|
10026
10269
|
const message = err instanceof Error ? err.message : String(err);
|
|
10027
10270
|
console.log(`- Failed to link workspace in ${projectPath}: ${message}`);
|
|
10028
10271
|
}
|
|
10029
10272
|
} else if (workspaceId && workspaceId !== "dry-run" && workspaceName && dryRun) {
|
|
10030
|
-
writeActions.push({ kind: "workspace-config", target:
|
|
10273
|
+
writeActions.push({ kind: "workspace-config", target: path6.join(projectPath, ".contextstream", "config.json"), status: "dry-run" });
|
|
10031
10274
|
}
|
|
10032
10275
|
if (mcpScope === "project" || mcpScope === "both") {
|
|
10033
10276
|
for (const editor of selectedEditors) {
|
|
10034
10277
|
try {
|
|
10035
10278
|
if (editor === "cursor") {
|
|
10036
|
-
const cursorPath =
|
|
10037
|
-
const vscodePath =
|
|
10279
|
+
const cursorPath = path6.join(projectPath, ".cursor", "mcp.json");
|
|
10280
|
+
const vscodePath = path6.join(projectPath, ".vscode", "mcp.json");
|
|
10038
10281
|
if (dryRun) {
|
|
10039
10282
|
writeActions.push({ kind: "mcp-config", target: cursorPath, status: "dry-run" });
|
|
10040
10283
|
writeActions.push({ kind: "mcp-config", target: vscodePath, status: "dry-run" });
|
|
@@ -10047,7 +10290,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10047
10290
|
continue;
|
|
10048
10291
|
}
|
|
10049
10292
|
if (editor === "claude") {
|
|
10050
|
-
const mcpPath =
|
|
10293
|
+
const mcpPath = path6.join(projectPath, ".mcp.json");
|
|
10051
10294
|
if (dryRun) {
|
|
10052
10295
|
writeActions.push({ kind: "mcp-config", target: mcpPath, status: "dry-run" });
|
|
10053
10296
|
} else {
|
|
@@ -10057,7 +10300,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10057
10300
|
continue;
|
|
10058
10301
|
}
|
|
10059
10302
|
if (editor === "kilo") {
|
|
10060
|
-
const kiloPath =
|
|
10303
|
+
const kiloPath = path6.join(projectPath, ".kilocode", "mcp.json");
|
|
10061
10304
|
if (dryRun) {
|
|
10062
10305
|
writeActions.push({ kind: "mcp-config", target: kiloPath, status: "dry-run" });
|
|
10063
10306
|
} else {
|
|
@@ -10067,7 +10310,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10067
10310
|
continue;
|
|
10068
10311
|
}
|
|
10069
10312
|
if (editor === "roo") {
|
|
10070
|
-
const rooPath =
|
|
10313
|
+
const rooPath = path6.join(projectPath, ".roo", "mcp.json");
|
|
10071
10314
|
if (dryRun) {
|
|
10072
10315
|
writeActions.push({ kind: "mcp-config", target: rooPath, status: "dry-run" });
|
|
10073
10316
|
} else {
|
|
@@ -10087,11 +10330,11 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10087
10330
|
const rule = generateRuleContent(editor, {
|
|
10088
10331
|
workspaceName,
|
|
10089
10332
|
workspaceId: workspaceId && workspaceId !== "dry-run" ? workspaceId : void 0,
|
|
10090
|
-
projectName:
|
|
10333
|
+
projectName: path6.basename(projectPath),
|
|
10091
10334
|
mode
|
|
10092
10335
|
});
|
|
10093
10336
|
if (!rule) continue;
|
|
10094
|
-
const filePath =
|
|
10337
|
+
const filePath = path6.join(projectPath, rule.filename);
|
|
10095
10338
|
if (dryRun) {
|
|
10096
10339
|
writeActions.push({ kind: "rules", target: filePath, status: "dry-run" });
|
|
10097
10340
|
continue;
|
|
@@ -10124,8 +10367,8 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10124
10367
|
|
|
10125
10368
|
// src/index.ts
|
|
10126
10369
|
function showFirstRunMessage() {
|
|
10127
|
-
const configDir =
|
|
10128
|
-
const starShownFile =
|
|
10370
|
+
const configDir = join6(homedir3(), ".contextstream");
|
|
10371
|
+
const starShownFile = join6(configDir, ".star-shown");
|
|
10129
10372
|
if (existsSync2(starShownFile)) {
|
|
10130
10373
|
return;
|
|
10131
10374
|
}
|
package/package.json
CHANGED