@contextstream/mcp-server 0.3.29 → 0.3.31
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 +308 -115
- 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) {
|
|
@@ -7936,26 +8020,26 @@ Optionally generates AI editor rules for automatic ContextStream usage.`,
|
|
|
7936
8020
|
const result = await client.associateWorkspace(input);
|
|
7937
8021
|
let rulesGenerated = [];
|
|
7938
8022
|
if (input.generate_editor_rules) {
|
|
7939
|
-
const
|
|
7940
|
-
const
|
|
8023
|
+
const fs5 = await import("fs");
|
|
8024
|
+
const path7 = await import("path");
|
|
7941
8025
|
for (const editor of getAvailableEditors()) {
|
|
7942
8026
|
const rule = generateRuleContent(editor, {
|
|
7943
8027
|
workspaceName: input.workspace_name,
|
|
7944
8028
|
workspaceId: input.workspace_id
|
|
7945
8029
|
});
|
|
7946
8030
|
if (rule) {
|
|
7947
|
-
const filePath =
|
|
8031
|
+
const filePath = path7.join(input.folder_path, rule.filename);
|
|
7948
8032
|
try {
|
|
7949
8033
|
let existingContent = "";
|
|
7950
8034
|
try {
|
|
7951
|
-
existingContent =
|
|
8035
|
+
existingContent = fs5.readFileSync(filePath, "utf-8");
|
|
7952
8036
|
} catch {
|
|
7953
8037
|
}
|
|
7954
8038
|
if (!existingContent) {
|
|
7955
|
-
|
|
8039
|
+
fs5.writeFileSync(filePath, rule.content);
|
|
7956
8040
|
rulesGenerated.push(rule.filename);
|
|
7957
8041
|
} else if (!existingContent.includes("ContextStream Integration")) {
|
|
7958
|
-
|
|
8042
|
+
fs5.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
|
|
7959
8043
|
rulesGenerated.push(rule.filename + " (appended)");
|
|
7960
8044
|
}
|
|
7961
8045
|
} catch {
|
|
@@ -8041,26 +8125,26 @@ Behavior:
|
|
|
8041
8125
|
});
|
|
8042
8126
|
let rulesGenerated = [];
|
|
8043
8127
|
if (input.generate_editor_rules) {
|
|
8044
|
-
const
|
|
8045
|
-
const
|
|
8128
|
+
const fs5 = await import("fs");
|
|
8129
|
+
const path7 = await import("path");
|
|
8046
8130
|
for (const editor of getAvailableEditors()) {
|
|
8047
8131
|
const rule = generateRuleContent(editor, {
|
|
8048
8132
|
workspaceName: newWorkspace.name || input.workspace_name,
|
|
8049
8133
|
workspaceId: newWorkspace.id
|
|
8050
8134
|
});
|
|
8051
8135
|
if (!rule) continue;
|
|
8052
|
-
const filePath =
|
|
8136
|
+
const filePath = path7.join(folderPath, rule.filename);
|
|
8053
8137
|
try {
|
|
8054
8138
|
let existingContent = "";
|
|
8055
8139
|
try {
|
|
8056
|
-
existingContent =
|
|
8140
|
+
existingContent = fs5.readFileSync(filePath, "utf-8");
|
|
8057
8141
|
} catch {
|
|
8058
8142
|
}
|
|
8059
8143
|
if (!existingContent) {
|
|
8060
|
-
|
|
8144
|
+
fs5.writeFileSync(filePath, rule.content);
|
|
8061
8145
|
rulesGenerated.push(rule.filename);
|
|
8062
8146
|
} else if (!existingContent.includes("ContextStream Integration")) {
|
|
8063
|
-
|
|
8147
|
+
fs5.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
|
|
8064
8148
|
rulesGenerated.push(rule.filename + " (appended)");
|
|
8065
8149
|
}
|
|
8066
8150
|
} catch {
|
|
@@ -8460,8 +8544,8 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
8460
8544
|
})
|
|
8461
8545
|
},
|
|
8462
8546
|
async (input) => {
|
|
8463
|
-
const
|
|
8464
|
-
const
|
|
8547
|
+
const fs5 = await import("fs");
|
|
8548
|
+
const path7 = await import("path");
|
|
8465
8549
|
const editors = input.editors?.includes("all") || !input.editors ? getAvailableEditors() : input.editors.filter((e) => e !== "all");
|
|
8466
8550
|
const results = [];
|
|
8467
8551
|
for (const editor of editors) {
|
|
@@ -8476,7 +8560,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
8476
8560
|
results.push({ editor, filename: "", status: "unknown editor" });
|
|
8477
8561
|
continue;
|
|
8478
8562
|
}
|
|
8479
|
-
const filePath =
|
|
8563
|
+
const filePath = path7.join(input.folder_path, rule.filename);
|
|
8480
8564
|
if (input.dry_run) {
|
|
8481
8565
|
results.push({
|
|
8482
8566
|
editor,
|
|
@@ -8488,15 +8572,15 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
8488
8572
|
try {
|
|
8489
8573
|
let existingContent = "";
|
|
8490
8574
|
try {
|
|
8491
|
-
existingContent =
|
|
8575
|
+
existingContent = fs5.readFileSync(filePath, "utf-8");
|
|
8492
8576
|
} catch {
|
|
8493
8577
|
}
|
|
8494
8578
|
if (existingContent && !existingContent.includes("ContextStream Integration")) {
|
|
8495
8579
|
const updatedContent = existingContent + "\n\n" + rule.content;
|
|
8496
|
-
|
|
8580
|
+
fs5.writeFileSync(filePath, updatedContent);
|
|
8497
8581
|
results.push({ editor, filename: rule.filename, status: "appended to existing" });
|
|
8498
8582
|
} else {
|
|
8499
|
-
|
|
8583
|
+
fs5.writeFileSync(filePath, rule.content);
|
|
8500
8584
|
results.push({ editor, filename: rule.filename, status: "created" });
|
|
8501
8585
|
}
|
|
8502
8586
|
} catch (err) {
|
|
@@ -9166,8 +9250,8 @@ var SessionManager = class {
|
|
|
9166
9250
|
/**
|
|
9167
9251
|
* Set the folder path hint (can be passed from tools that know the workspace path)
|
|
9168
9252
|
*/
|
|
9169
|
-
setFolderPath(
|
|
9170
|
-
this.folderPath =
|
|
9253
|
+
setFolderPath(path7) {
|
|
9254
|
+
this.folderPath = path7;
|
|
9171
9255
|
}
|
|
9172
9256
|
/**
|
|
9173
9257
|
* Mark that context_smart has been called in this session
|
|
@@ -9234,11 +9318,11 @@ var SessionManager = class {
|
|
|
9234
9318
|
}
|
|
9235
9319
|
if (this.ideRoots.length === 0) {
|
|
9236
9320
|
const cwd = process.cwd();
|
|
9237
|
-
const
|
|
9321
|
+
const fs5 = await import("fs");
|
|
9238
9322
|
const projectIndicators = [".git", "package.json", "Cargo.toml", "pyproject.toml", ".contextstream"];
|
|
9239
9323
|
const hasProjectIndicator = projectIndicators.some((f) => {
|
|
9240
9324
|
try {
|
|
9241
|
-
return
|
|
9325
|
+
return fs5.existsSync(`${cwd}/${f}`);
|
|
9242
9326
|
} catch {
|
|
9243
9327
|
return false;
|
|
9244
9328
|
}
|
|
@@ -9416,15 +9500,79 @@ var SessionManager = class {
|
|
|
9416
9500
|
|
|
9417
9501
|
// src/index.ts
|
|
9418
9502
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
9419
|
-
import { homedir as
|
|
9420
|
-
import { join as
|
|
9503
|
+
import { homedir as homedir3 } from "os";
|
|
9504
|
+
import { join as join6 } from "path";
|
|
9421
9505
|
|
|
9422
9506
|
// src/setup.ts
|
|
9507
|
+
import * as fs4 from "node:fs/promises";
|
|
9508
|
+
import * as path6 from "node:path";
|
|
9509
|
+
import { homedir as homedir2 } from "node:os";
|
|
9510
|
+
import { stdin, stdout } from "node:process";
|
|
9511
|
+
import { createInterface } from "node:readline/promises";
|
|
9512
|
+
|
|
9513
|
+
// src/credentials.ts
|
|
9423
9514
|
import * as fs3 from "node:fs/promises";
|
|
9424
9515
|
import * as path5 from "node:path";
|
|
9425
9516
|
import { homedir } from "node:os";
|
|
9426
|
-
|
|
9427
|
-
|
|
9517
|
+
function normalizeApiUrl(input) {
|
|
9518
|
+
return String(input ?? "").trim().replace(/\/+$/, "");
|
|
9519
|
+
}
|
|
9520
|
+
function credentialsFilePath() {
|
|
9521
|
+
return path5.join(homedir(), ".contextstream", "credentials.json");
|
|
9522
|
+
}
|
|
9523
|
+
function isRecord(value) {
|
|
9524
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9525
|
+
}
|
|
9526
|
+
async function readSavedCredentials() {
|
|
9527
|
+
const filePath = credentialsFilePath();
|
|
9528
|
+
try {
|
|
9529
|
+
const raw = await fs3.readFile(filePath, "utf8");
|
|
9530
|
+
const parsed = JSON.parse(raw);
|
|
9531
|
+
if (!isRecord(parsed)) return null;
|
|
9532
|
+
const version = parsed.version;
|
|
9533
|
+
if (version !== 1) return null;
|
|
9534
|
+
const apiUrl = typeof parsed.api_url === "string" ? normalizeApiUrl(parsed.api_url) : "";
|
|
9535
|
+
const apiKey = typeof parsed.api_key === "string" ? parsed.api_key.trim() : "";
|
|
9536
|
+
if (!apiUrl || !apiKey) return null;
|
|
9537
|
+
const email = typeof parsed.email === "string" ? parsed.email.trim() : "";
|
|
9538
|
+
const createdAt = typeof parsed.created_at === "string" ? parsed.created_at : "";
|
|
9539
|
+
const updatedAt = typeof parsed.updated_at === "string" ? parsed.updated_at : "";
|
|
9540
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9541
|
+
return {
|
|
9542
|
+
version: 1,
|
|
9543
|
+
api_url: apiUrl,
|
|
9544
|
+
api_key: apiKey,
|
|
9545
|
+
email: email || void 0,
|
|
9546
|
+
created_at: createdAt || now,
|
|
9547
|
+
updated_at: updatedAt || now
|
|
9548
|
+
};
|
|
9549
|
+
} catch {
|
|
9550
|
+
return null;
|
|
9551
|
+
}
|
|
9552
|
+
}
|
|
9553
|
+
async function writeSavedCredentials(input) {
|
|
9554
|
+
const filePath = credentialsFilePath();
|
|
9555
|
+
await fs3.mkdir(path5.dirname(filePath), { recursive: true });
|
|
9556
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9557
|
+
const existing = await readSavedCredentials();
|
|
9558
|
+
const value = {
|
|
9559
|
+
version: 1,
|
|
9560
|
+
api_url: normalizeApiUrl(input.apiUrl),
|
|
9561
|
+
api_key: input.apiKey.trim(),
|
|
9562
|
+
email: input.email?.trim() || void 0,
|
|
9563
|
+
created_at: existing?.created_at || now,
|
|
9564
|
+
updated_at: now
|
|
9565
|
+
};
|
|
9566
|
+
const body = JSON.stringify(value, null, 2) + "\n";
|
|
9567
|
+
await fs3.writeFile(filePath, body, { encoding: "utf8", mode: 384 });
|
|
9568
|
+
try {
|
|
9569
|
+
await fs3.chmod(filePath, 384);
|
|
9570
|
+
} catch {
|
|
9571
|
+
}
|
|
9572
|
+
return { path: filePath, value };
|
|
9573
|
+
}
|
|
9574
|
+
|
|
9575
|
+
// src/setup.ts
|
|
9428
9576
|
var EDITOR_LABELS = {
|
|
9429
9577
|
codex: "Codex CLI",
|
|
9430
9578
|
claude: "Claude Code",
|
|
@@ -9435,6 +9583,9 @@ var EDITOR_LABELS = {
|
|
|
9435
9583
|
roo: "Roo Code",
|
|
9436
9584
|
aider: "Aider"
|
|
9437
9585
|
};
|
|
9586
|
+
function supportsProjectMcpConfig(editor) {
|
|
9587
|
+
return editor === "cursor" || editor === "claude" || editor === "kilo" || editor === "roo";
|
|
9588
|
+
}
|
|
9438
9589
|
function normalizeInput(value) {
|
|
9439
9590
|
return value.trim();
|
|
9440
9591
|
}
|
|
@@ -9459,42 +9610,42 @@ function parseNumberList(input, max) {
|
|
|
9459
9610
|
}
|
|
9460
9611
|
async function fileExists(filePath) {
|
|
9461
9612
|
try {
|
|
9462
|
-
await
|
|
9613
|
+
await fs4.stat(filePath);
|
|
9463
9614
|
return true;
|
|
9464
9615
|
} catch {
|
|
9465
9616
|
return false;
|
|
9466
9617
|
}
|
|
9467
9618
|
}
|
|
9468
9619
|
async function upsertTextFile(filePath, content, marker) {
|
|
9469
|
-
await
|
|
9620
|
+
await fs4.mkdir(path6.dirname(filePath), { recursive: true });
|
|
9470
9621
|
const exists = await fileExists(filePath);
|
|
9471
9622
|
if (!exists) {
|
|
9472
|
-
await
|
|
9623
|
+
await fs4.writeFile(filePath, content, "utf8");
|
|
9473
9624
|
return "created";
|
|
9474
9625
|
}
|
|
9475
|
-
const existing = await
|
|
9626
|
+
const existing = await fs4.readFile(filePath, "utf8").catch(() => "");
|
|
9476
9627
|
if (existing.includes(marker)) return "skipped";
|
|
9477
9628
|
const joined = existing.trimEnd() + "\n\n" + content.trim() + "\n";
|
|
9478
|
-
await
|
|
9629
|
+
await fs4.writeFile(filePath, joined, "utf8");
|
|
9479
9630
|
return "appended";
|
|
9480
9631
|
}
|
|
9481
9632
|
function globalRulesPathForEditor(editor) {
|
|
9482
|
-
const home =
|
|
9633
|
+
const home = homedir2();
|
|
9483
9634
|
switch (editor) {
|
|
9484
9635
|
case "codex":
|
|
9485
|
-
return
|
|
9636
|
+
return path6.join(home, ".codex", "AGENTS.md");
|
|
9486
9637
|
case "claude":
|
|
9487
|
-
return
|
|
9638
|
+
return path6.join(home, ".claude", "CLAUDE.md");
|
|
9488
9639
|
case "windsurf":
|
|
9489
|
-
return
|
|
9640
|
+
return path6.join(home, ".codeium", "windsurf", "memories", "global_rules.md");
|
|
9490
9641
|
case "cline":
|
|
9491
|
-
return
|
|
9642
|
+
return path6.join(home, "Documents", "Cline", "Rules", "contextstream.md");
|
|
9492
9643
|
case "kilo":
|
|
9493
|
-
return
|
|
9644
|
+
return path6.join(home, ".kilocode", "rules", "contextstream.md");
|
|
9494
9645
|
case "roo":
|
|
9495
|
-
return
|
|
9646
|
+
return path6.join(home, ".roo", "rules", "contextstream.md");
|
|
9496
9647
|
case "aider":
|
|
9497
|
-
return
|
|
9648
|
+
return path6.join(home, ".aider.conf.yml");
|
|
9498
9649
|
case "cursor":
|
|
9499
9650
|
return null;
|
|
9500
9651
|
default:
|
|
@@ -9541,11 +9692,11 @@ function tryParseJsonLike(raw) {
|
|
|
9541
9692
|
}
|
|
9542
9693
|
}
|
|
9543
9694
|
async function upsertJsonMcpConfig(filePath, server) {
|
|
9544
|
-
await
|
|
9695
|
+
await fs4.mkdir(path6.dirname(filePath), { recursive: true });
|
|
9545
9696
|
const exists = await fileExists(filePath);
|
|
9546
9697
|
let root = {};
|
|
9547
9698
|
if (exists) {
|
|
9548
|
-
const raw = await
|
|
9699
|
+
const raw = await fs4.readFile(filePath, "utf8").catch(() => "");
|
|
9549
9700
|
const parsed = tryParseJsonLike(raw);
|
|
9550
9701
|
if (!parsed.ok) throw new Error(`Invalid JSON in ${filePath}: ${parsed.error}`);
|
|
9551
9702
|
root = parsed.value;
|
|
@@ -9555,16 +9706,16 @@ async function upsertJsonMcpConfig(filePath, server) {
|
|
|
9555
9706
|
const before = JSON.stringify(root.mcpServers.contextstream ?? null);
|
|
9556
9707
|
root.mcpServers.contextstream = server;
|
|
9557
9708
|
const after = JSON.stringify(root.mcpServers.contextstream ?? null);
|
|
9558
|
-
await
|
|
9709
|
+
await fs4.writeFile(filePath, JSON.stringify(root, null, 2) + "\n", "utf8");
|
|
9559
9710
|
if (!exists) return "created";
|
|
9560
9711
|
return before === after ? "skipped" : "updated";
|
|
9561
9712
|
}
|
|
9562
9713
|
async function upsertJsonVsCodeMcpConfig(filePath, server) {
|
|
9563
|
-
await
|
|
9714
|
+
await fs4.mkdir(path6.dirname(filePath), { recursive: true });
|
|
9564
9715
|
const exists = await fileExists(filePath);
|
|
9565
9716
|
let root = {};
|
|
9566
9717
|
if (exists) {
|
|
9567
|
-
const raw = await
|
|
9718
|
+
const raw = await fs4.readFile(filePath, "utf8").catch(() => "");
|
|
9568
9719
|
const parsed = tryParseJsonLike(raw);
|
|
9569
9720
|
if (!parsed.ok) throw new Error(`Invalid JSON in ${filePath}: ${parsed.error}`);
|
|
9570
9721
|
root = parsed.value;
|
|
@@ -9574,25 +9725,25 @@ async function upsertJsonVsCodeMcpConfig(filePath, server) {
|
|
|
9574
9725
|
const before = JSON.stringify(root.servers.contextstream ?? null);
|
|
9575
9726
|
root.servers.contextstream = server;
|
|
9576
9727
|
const after = JSON.stringify(root.servers.contextstream ?? null);
|
|
9577
|
-
await
|
|
9728
|
+
await fs4.writeFile(filePath, JSON.stringify(root, null, 2) + "\n", "utf8");
|
|
9578
9729
|
if (!exists) return "created";
|
|
9579
9730
|
return before === after ? "skipped" : "updated";
|
|
9580
9731
|
}
|
|
9581
9732
|
function claudeDesktopConfigPath() {
|
|
9582
|
-
const home =
|
|
9733
|
+
const home = homedir2();
|
|
9583
9734
|
if (process.platform === "darwin") {
|
|
9584
|
-
return
|
|
9735
|
+
return path6.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
9585
9736
|
}
|
|
9586
9737
|
if (process.platform === "win32") {
|
|
9587
|
-
const appData = process.env.APPDATA ||
|
|
9588
|
-
return
|
|
9738
|
+
const appData = process.env.APPDATA || path6.join(home, "AppData", "Roaming");
|
|
9739
|
+
return path6.join(appData, "Claude", "claude_desktop_config.json");
|
|
9589
9740
|
}
|
|
9590
9741
|
return null;
|
|
9591
9742
|
}
|
|
9592
9743
|
async function upsertCodexTomlConfig(filePath, params) {
|
|
9593
|
-
await
|
|
9744
|
+
await fs4.mkdir(path6.dirname(filePath), { recursive: true });
|
|
9594
9745
|
const exists = await fileExists(filePath);
|
|
9595
|
-
const existing = exists ? await
|
|
9746
|
+
const existing = exists ? await fs4.readFile(filePath, "utf8").catch(() => "") : "";
|
|
9596
9747
|
const marker = "[mcp_servers.contextstream]";
|
|
9597
9748
|
const envMarker = "[mcp_servers.contextstream.env]";
|
|
9598
9749
|
const block = `
|
|
@@ -9607,15 +9758,15 @@ CONTEXTSTREAM_API_URL = "${params.apiUrl}"
|
|
|
9607
9758
|
CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
9608
9759
|
`;
|
|
9609
9760
|
if (!exists) {
|
|
9610
|
-
await
|
|
9761
|
+
await fs4.writeFile(filePath, block.trimStart(), "utf8");
|
|
9611
9762
|
return "created";
|
|
9612
9763
|
}
|
|
9613
9764
|
if (!existing.includes(marker)) {
|
|
9614
|
-
await
|
|
9765
|
+
await fs4.writeFile(filePath, existing.trimEnd() + block, "utf8");
|
|
9615
9766
|
return "updated";
|
|
9616
9767
|
}
|
|
9617
9768
|
if (!existing.includes(envMarker)) {
|
|
9618
|
-
await
|
|
9769
|
+
await fs4.writeFile(filePath, existing.trimEnd() + "\n\n" + envMarker + `
|
|
9619
9770
|
CONTEXTSTREAM_API_URL = "${params.apiUrl}"
|
|
9620
9771
|
CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
9621
9772
|
`, "utf8");
|
|
@@ -9656,18 +9807,18 @@ CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
|
9656
9807
|
}
|
|
9657
9808
|
const updated = out.join("\n");
|
|
9658
9809
|
if (updated === existing) return "skipped";
|
|
9659
|
-
await
|
|
9810
|
+
await fs4.writeFile(filePath, updated, "utf8");
|
|
9660
9811
|
return "updated";
|
|
9661
9812
|
}
|
|
9662
9813
|
async function discoverProjectsUnderFolder(parentFolder) {
|
|
9663
|
-
const entries = await
|
|
9664
|
-
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
9814
|
+
const entries = await fs4.readdir(parentFolder, { withFileTypes: true });
|
|
9815
|
+
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => path6.join(parentFolder, e.name));
|
|
9665
9816
|
const projects = [];
|
|
9666
9817
|
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(
|
|
9818
|
+
const hasGit = await fileExists(path6.join(dir, ".git"));
|
|
9819
|
+
const hasPkg = await fileExists(path6.join(dir, "package.json"));
|
|
9820
|
+
const hasCargo = await fileExists(path6.join(dir, "Cargo.toml"));
|
|
9821
|
+
const hasPyProject = await fileExists(path6.join(dir, "pyproject.toml"));
|
|
9671
9822
|
if (hasGit || hasPkg || hasCargo || hasPyProject) projects.push(dir);
|
|
9672
9823
|
}
|
|
9673
9824
|
return projects;
|
|
@@ -9691,16 +9842,32 @@ async function runSetupWizard(args) {
|
|
|
9691
9842
|
console.log("This configures ContextStream MCP + rules for your AI editor(s).");
|
|
9692
9843
|
if (dryRun) console.log("DRY RUN: no files will be written.\n");
|
|
9693
9844
|
else console.log("");
|
|
9694
|
-
const
|
|
9695
|
-
const
|
|
9696
|
-
|
|
9697
|
-
|
|
9845
|
+
const savedCreds = await readSavedCredentials();
|
|
9846
|
+
const apiUrlDefault = normalizeApiUrl(process.env.CONTEXTSTREAM_API_URL || savedCreds?.api_url || "https://api.contextstream.io");
|
|
9847
|
+
const apiUrl = normalizeApiUrl(
|
|
9848
|
+
normalizeInput(await rl.question(`ContextStream API URL [${apiUrlDefault}]: `)) || apiUrlDefault
|
|
9849
|
+
);
|
|
9698
9850
|
let apiKey = normalizeInput(process.env.CONTEXTSTREAM_API_KEY || "");
|
|
9851
|
+
let apiKeySource = apiKey ? "env" : "unknown";
|
|
9699
9852
|
if (apiKey) {
|
|
9700
9853
|
const confirm = normalizeInput(
|
|
9701
9854
|
await rl.question(`Use CONTEXTSTREAM_API_KEY from environment (${maskApiKey(apiKey)})? [Y/n]: `)
|
|
9702
9855
|
);
|
|
9703
|
-
if (confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no")
|
|
9856
|
+
if (confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no") {
|
|
9857
|
+
apiKey = "";
|
|
9858
|
+
apiKeySource = "unknown";
|
|
9859
|
+
}
|
|
9860
|
+
}
|
|
9861
|
+
if (!apiKey && savedCreds?.api_key && normalizeApiUrl(savedCreds.api_url) === apiUrl) {
|
|
9862
|
+
const confirm = normalizeInput(
|
|
9863
|
+
await rl.question(
|
|
9864
|
+
`Use saved API key from ${credentialsFilePath()} (${maskApiKey(savedCreds.api_key)})? [Y/n]: `
|
|
9865
|
+
)
|
|
9866
|
+
);
|
|
9867
|
+
if (!(confirm.toLowerCase() === "n" || confirm.toLowerCase() === "no")) {
|
|
9868
|
+
apiKey = savedCreds.api_key;
|
|
9869
|
+
apiKeySource = "saved";
|
|
9870
|
+
}
|
|
9704
9871
|
}
|
|
9705
9872
|
if (!apiKey) {
|
|
9706
9873
|
console.log("\nAuthentication:");
|
|
@@ -9711,6 +9878,7 @@ async function runSetupWizard(args) {
|
|
|
9711
9878
|
console.log("\nYou need a ContextStream API key to continue.");
|
|
9712
9879
|
console.log("Create one here (then paste it): https://app.contextstream.io/settings/api-keys\n");
|
|
9713
9880
|
apiKey = normalizeInput(await rl.question("CONTEXTSTREAM_API_KEY: "));
|
|
9881
|
+
apiKeySource = "paste";
|
|
9714
9882
|
} else {
|
|
9715
9883
|
const anonClient = new ContextStreamClient(buildClientConfig({ apiUrl }));
|
|
9716
9884
|
let device;
|
|
@@ -9773,6 +9941,7 @@ Code: ${device.user_code}`);
|
|
|
9773
9941
|
throw new Error("API key creation returned an unexpected response.");
|
|
9774
9942
|
}
|
|
9775
9943
|
apiKey = createdKey.secret_key.trim();
|
|
9944
|
+
apiKeySource = "browser";
|
|
9776
9945
|
console.log(`
|
|
9777
9946
|
Created API key: ${maskApiKey(apiKey)}
|
|
9778
9947
|
`);
|
|
@@ -9789,6 +9958,17 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9789
9958
|
const email = typeof me?.data?.email === "string" ? me.data.email : typeof me?.email === "string" ? me.email : void 0;
|
|
9790
9959
|
console.log(`Authenticated as: ${email || "unknown user"} (${maskApiKey(apiKey)})
|
|
9791
9960
|
`);
|
|
9961
|
+
if (!dryRun && (apiKeySource === "browser" || apiKeySource === "paste")) {
|
|
9962
|
+
try {
|
|
9963
|
+
const saved = await writeSavedCredentials({ apiUrl, apiKey, email });
|
|
9964
|
+
console.log(`Saved API key for future runs: ${saved.path} (${maskApiKey(apiKey)})
|
|
9965
|
+
`);
|
|
9966
|
+
} catch (err) {
|
|
9967
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9968
|
+
console.log(`Warning: failed to save API key for future runs (${credentialsFilePath()}): ${msg}
|
|
9969
|
+
`);
|
|
9970
|
+
}
|
|
9971
|
+
}
|
|
9792
9972
|
let workspaceId;
|
|
9793
9973
|
let workspaceName;
|
|
9794
9974
|
console.log("Workspace setup:");
|
|
@@ -9840,9 +10020,9 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9840
10020
|
}
|
|
9841
10021
|
}
|
|
9842
10022
|
}
|
|
9843
|
-
console.log("
|
|
9844
|
-
console.log(" 1)
|
|
9845
|
-
console.log(" 2)
|
|
10023
|
+
console.log("Rules detail level (in the generated rules file):\n");
|
|
10024
|
+
console.log(" 1) Standard (recommended) \u2014 concise, high-signal (lower token overhead)");
|
|
10025
|
+
console.log(" 2) Extended \u2014 more guidance + examples (higher token overhead)");
|
|
9846
10026
|
const modeChoice = normalizeInput(await rl.question("Choose [1/2] (default 1): ")) || "1";
|
|
9847
10027
|
const mode = modeChoice === "2" ? "full" : "minimal";
|
|
9848
10028
|
const editors = ["codex", "claude", "cursor", "windsurf", "cline", "kilo", "roo", "aider"];
|
|
@@ -9851,6 +10031,8 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9851
10031
|
const selectedRaw = normalizeInput(await rl.question("Editors [all]: ")) || "all";
|
|
9852
10032
|
const selectedNums = parseNumberList(selectedRaw, editors.length);
|
|
9853
10033
|
const selectedEditors = selectedNums.length ? selectedNums.map((n) => editors[n - 1]) : editors;
|
|
10034
|
+
const hasCodex = selectedEditors.includes("codex");
|
|
10035
|
+
const hasProjectMcpEditors = selectedEditors.some((e) => supportsProjectMcpConfig(e));
|
|
9854
10036
|
console.log("\nInstall rules as:");
|
|
9855
10037
|
console.log(" 1) Global");
|
|
9856
10038
|
console.log(" 2) Project");
|
|
@@ -9858,20 +10040,31 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9858
10040
|
const scopeChoice = normalizeInput(await rl.question("Choose [1/2/3] (default 3): ")) || "3";
|
|
9859
10041
|
const scope = scopeChoice === "1" ? "global" : scopeChoice === "2" ? "project" : "both";
|
|
9860
10042
|
console.log("\nInstall MCP server config as:");
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
10043
|
+
if (hasCodex && !hasProjectMcpEditors) {
|
|
10044
|
+
console.log(" 1) Global (Codex CLI supports global config only)");
|
|
10045
|
+
console.log(" 2) Skip (rules only)");
|
|
10046
|
+
} else {
|
|
10047
|
+
console.log(" 1) Global");
|
|
10048
|
+
console.log(" 2) Project");
|
|
10049
|
+
console.log(" 3) Both");
|
|
10050
|
+
console.log(" 4) Skip (rules only)");
|
|
10051
|
+
if (hasCodex) {
|
|
10052
|
+
console.log(" Note: Codex CLI does not support per-project MCP config; it will be configured globally if selected.");
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
10055
|
+
const mcpChoiceDefault = hasCodex && !hasProjectMcpEditors ? "1" : "3";
|
|
10056
|
+
const mcpChoice = normalizeInput(await rl.question(`Choose [${hasCodex && !hasProjectMcpEditors ? "1/2" : "1/2/3/4"}] (default ${mcpChoiceDefault}): `)) || mcpChoiceDefault;
|
|
10057
|
+
const mcpScope = mcpChoice === "2" && hasCodex && !hasProjectMcpEditors ? "skip" : mcpChoice === "4" ? "skip" : mcpChoice === "1" ? "global" : mcpChoice === "2" ? "project" : "both";
|
|
9867
10058
|
const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey });
|
|
9868
10059
|
const vsCodeServer = buildContextStreamVsCodeServer({ apiUrl, apiKey });
|
|
9869
|
-
|
|
10060
|
+
const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex;
|
|
10061
|
+
if (needsGlobalMcpConfig) {
|
|
9870
10062
|
console.log("\nInstalling global MCP config...");
|
|
9871
10063
|
for (const editor of selectedEditors) {
|
|
10064
|
+
if (mcpScope === "project" && editor !== "codex") continue;
|
|
9872
10065
|
try {
|
|
9873
10066
|
if (editor === "codex") {
|
|
9874
|
-
const filePath =
|
|
10067
|
+
const filePath = path6.join(homedir2(), ".codex", "config.toml");
|
|
9875
10068
|
if (dryRun) {
|
|
9876
10069
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
9877
10070
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -9883,7 +10076,7 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9883
10076
|
continue;
|
|
9884
10077
|
}
|
|
9885
10078
|
if (editor === "windsurf") {
|
|
9886
|
-
const filePath =
|
|
10079
|
+
const filePath = path6.join(homedir2(), ".codeium", "windsurf", "mcp_config.json");
|
|
9887
10080
|
if (dryRun) {
|
|
9888
10081
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
9889
10082
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -9915,7 +10108,7 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9915
10108
|
continue;
|
|
9916
10109
|
}
|
|
9917
10110
|
if (editor === "cursor") {
|
|
9918
|
-
const filePath =
|
|
10111
|
+
const filePath = path6.join(homedir2(), ".cursor", "mcp.json");
|
|
9919
10112
|
if (dryRun) {
|
|
9920
10113
|
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
9921
10114
|
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
@@ -9969,12 +10162,12 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9969
10162
|
}
|
|
9970
10163
|
}
|
|
9971
10164
|
const projectPaths = /* @__PURE__ */ new Set();
|
|
9972
|
-
const needsProjects = scope === "project" || scope === "both" || mcpScope === "project" || mcpScope === "both";
|
|
10165
|
+
const needsProjects = scope === "project" || scope === "both" || (mcpScope === "project" || mcpScope === "both") && hasProjectMcpEditors;
|
|
9973
10166
|
if (needsProjects) {
|
|
9974
10167
|
console.log("\nProject setup...");
|
|
9975
10168
|
const addCwd = normalizeInput(await rl.question(`Add current folder as a project? [Y/n] (${process.cwd()}): `));
|
|
9976
10169
|
if (addCwd.toLowerCase() !== "n" && addCwd.toLowerCase() !== "no") {
|
|
9977
|
-
projectPaths.add(
|
|
10170
|
+
projectPaths.add(path6.resolve(process.cwd()));
|
|
9978
10171
|
}
|
|
9979
10172
|
while (true) {
|
|
9980
10173
|
console.log("\n 1) Add another project path");
|
|
@@ -9984,13 +10177,13 @@ Created API key: ${maskApiKey(apiKey)}
|
|
|
9984
10177
|
if (choice === "3") break;
|
|
9985
10178
|
if (choice === "1") {
|
|
9986
10179
|
const p = normalizeInput(await rl.question("Project folder path: "));
|
|
9987
|
-
if (p) projectPaths.add(
|
|
10180
|
+
if (p) projectPaths.add(path6.resolve(p));
|
|
9988
10181
|
continue;
|
|
9989
10182
|
}
|
|
9990
10183
|
if (choice === "2") {
|
|
9991
10184
|
const parent = normalizeInput(await rl.question("Parent folder path: "));
|
|
9992
10185
|
if (!parent) continue;
|
|
9993
|
-
const parentAbs =
|
|
10186
|
+
const parentAbs = path6.resolve(parent);
|
|
9994
10187
|
const projects2 = await discoverProjectsUnderFolder(parentAbs);
|
|
9995
10188
|
if (projects2.length === 0) {
|
|
9996
10189
|
console.log(`No projects detected under ${parentAbs} (looked for .git/package.json/Cargo.toml/pyproject.toml).`);
|
|
@@ -10020,21 +10213,21 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10020
10213
|
workspace_name: workspaceName,
|
|
10021
10214
|
create_parent_mapping: createParentMapping
|
|
10022
10215
|
});
|
|
10023
|
-
writeActions.push({ kind: "workspace-config", target:
|
|
10216
|
+
writeActions.push({ kind: "workspace-config", target: path6.join(projectPath, ".contextstream", "config.json"), status: "created" });
|
|
10024
10217
|
console.log(`- Linked workspace in ${projectPath}`);
|
|
10025
10218
|
} catch (err) {
|
|
10026
10219
|
const message = err instanceof Error ? err.message : String(err);
|
|
10027
10220
|
console.log(`- Failed to link workspace in ${projectPath}: ${message}`);
|
|
10028
10221
|
}
|
|
10029
10222
|
} else if (workspaceId && workspaceId !== "dry-run" && workspaceName && dryRun) {
|
|
10030
|
-
writeActions.push({ kind: "workspace-config", target:
|
|
10223
|
+
writeActions.push({ kind: "workspace-config", target: path6.join(projectPath, ".contextstream", "config.json"), status: "dry-run" });
|
|
10031
10224
|
}
|
|
10032
10225
|
if (mcpScope === "project" || mcpScope === "both") {
|
|
10033
10226
|
for (const editor of selectedEditors) {
|
|
10034
10227
|
try {
|
|
10035
10228
|
if (editor === "cursor") {
|
|
10036
|
-
const cursorPath =
|
|
10037
|
-
const vscodePath =
|
|
10229
|
+
const cursorPath = path6.join(projectPath, ".cursor", "mcp.json");
|
|
10230
|
+
const vscodePath = path6.join(projectPath, ".vscode", "mcp.json");
|
|
10038
10231
|
if (dryRun) {
|
|
10039
10232
|
writeActions.push({ kind: "mcp-config", target: cursorPath, status: "dry-run" });
|
|
10040
10233
|
writeActions.push({ kind: "mcp-config", target: vscodePath, status: "dry-run" });
|
|
@@ -10047,7 +10240,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10047
10240
|
continue;
|
|
10048
10241
|
}
|
|
10049
10242
|
if (editor === "claude") {
|
|
10050
|
-
const mcpPath =
|
|
10243
|
+
const mcpPath = path6.join(projectPath, ".mcp.json");
|
|
10051
10244
|
if (dryRun) {
|
|
10052
10245
|
writeActions.push({ kind: "mcp-config", target: mcpPath, status: "dry-run" });
|
|
10053
10246
|
} else {
|
|
@@ -10057,7 +10250,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10057
10250
|
continue;
|
|
10058
10251
|
}
|
|
10059
10252
|
if (editor === "kilo") {
|
|
10060
|
-
const kiloPath =
|
|
10253
|
+
const kiloPath = path6.join(projectPath, ".kilocode", "mcp.json");
|
|
10061
10254
|
if (dryRun) {
|
|
10062
10255
|
writeActions.push({ kind: "mcp-config", target: kiloPath, status: "dry-run" });
|
|
10063
10256
|
} else {
|
|
@@ -10067,7 +10260,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10067
10260
|
continue;
|
|
10068
10261
|
}
|
|
10069
10262
|
if (editor === "roo") {
|
|
10070
|
-
const rooPath =
|
|
10263
|
+
const rooPath = path6.join(projectPath, ".roo", "mcp.json");
|
|
10071
10264
|
if (dryRun) {
|
|
10072
10265
|
writeActions.push({ kind: "mcp-config", target: rooPath, status: "dry-run" });
|
|
10073
10266
|
} else {
|
|
@@ -10087,11 +10280,11 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10087
10280
|
const rule = generateRuleContent(editor, {
|
|
10088
10281
|
workspaceName,
|
|
10089
10282
|
workspaceId: workspaceId && workspaceId !== "dry-run" ? workspaceId : void 0,
|
|
10090
|
-
projectName:
|
|
10283
|
+
projectName: path6.basename(projectPath),
|
|
10091
10284
|
mode
|
|
10092
10285
|
});
|
|
10093
10286
|
if (!rule) continue;
|
|
10094
|
-
const filePath =
|
|
10287
|
+
const filePath = path6.join(projectPath, rule.filename);
|
|
10095
10288
|
if (dryRun) {
|
|
10096
10289
|
writeActions.push({ kind: "rules", target: filePath, status: "dry-run" });
|
|
10097
10290
|
continue;
|
|
@@ -10124,8 +10317,8 @@ Applying to ${projects.length} project(s)...`);
|
|
|
10124
10317
|
|
|
10125
10318
|
// src/index.ts
|
|
10126
10319
|
function showFirstRunMessage() {
|
|
10127
|
-
const configDir =
|
|
10128
|
-
const starShownFile =
|
|
10320
|
+
const configDir = join6(homedir3(), ".contextstream");
|
|
10321
|
+
const starShownFile = join6(configDir, ".star-shown");
|
|
10129
10322
|
if (existsSync2(starShownFile)) {
|
|
10130
10323
|
return;
|
|
10131
10324
|
}
|
package/package.json
CHANGED