code-graph-builder 0.2.0 → 0.3.0
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/bin/cli.mjs +259 -54
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* code-graph-builder MCP server launcher
|
|
5
|
-
*
|
|
6
|
-
* Automatically detects the best way to run the Python MCP server:
|
|
7
|
-
* 1. uvx (fastest, auto-installs in isolated env)
|
|
8
|
-
* 2. pipx (similar to uvx)
|
|
9
|
-
* 3. Direct python3 (requires prior pip install)
|
|
4
|
+
* code-graph-builder MCP server launcher & setup wizard
|
|
10
5
|
*
|
|
11
6
|
* Usage:
|
|
12
|
-
* npx code-graph-builder
|
|
13
|
-
* npx code-graph-builder --
|
|
7
|
+
* npx code-graph-builder # interactive setup (first run)
|
|
8
|
+
* npx code-graph-builder --server # start MCP server (used by MCP clients)
|
|
9
|
+
* npx code-graph-builder --setup # re-run setup wizard
|
|
10
|
+
* npx code-graph-builder --pip # force python3 direct mode
|
|
14
11
|
*/
|
|
15
12
|
|
|
16
13
|
import { spawn, execFileSync } from "node:child_process";
|
|
14
|
+
import { createInterface } from "node:readline";
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
17
18
|
|
|
18
19
|
const PYTHON_PACKAGE = "code-graph-builder";
|
|
19
20
|
const MODULE_PATH = "code_graph_builder.mcp.server";
|
|
21
|
+
const WORKSPACE_DIR = join(homedir(), ".code-graph-builder");
|
|
22
|
+
const ENV_FILE = join(WORKSPACE_DIR, ".env");
|
|
20
23
|
|
|
21
|
-
//
|
|
22
|
-
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Utilities
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
23
27
|
|
|
24
|
-
/**
|
|
25
|
-
* Check if a command exists on PATH.
|
|
26
|
-
*/
|
|
27
28
|
function commandExists(cmd) {
|
|
28
29
|
try {
|
|
29
30
|
execFileSync("which", [cmd], { stdio: "pipe" });
|
|
@@ -33,9 +34,6 @@ function commandExists(cmd) {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
/**
|
|
37
|
-
* Check if the Python package is importable.
|
|
38
|
-
*/
|
|
39
37
|
function pythonPackageInstalled() {
|
|
40
38
|
try {
|
|
41
39
|
execFileSync("python3", ["-c", `import ${MODULE_PATH.split(".")[0]}`], {
|
|
@@ -47,17 +45,199 @@ function pythonPackageInstalled() {
|
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
function loadEnvFile() {
|
|
49
|
+
if (!existsSync(ENV_FILE)) return {};
|
|
50
|
+
const vars = {};
|
|
51
|
+
for (const line of readFileSync(ENV_FILE, "utf-8").split("\n")) {
|
|
52
|
+
const trimmed = line.trim();
|
|
53
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
54
|
+
const eq = trimmed.indexOf("=");
|
|
55
|
+
if (eq === -1) continue;
|
|
56
|
+
const key = trimmed.slice(0, eq).trim();
|
|
57
|
+
let val = trimmed.slice(eq + 1).trim();
|
|
58
|
+
// Strip surrounding quotes
|
|
59
|
+
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
60
|
+
(val.startsWith("'") && val.endsWith("'"))) {
|
|
61
|
+
val = val.slice(1, -1);
|
|
62
|
+
}
|
|
63
|
+
vars[key] = val;
|
|
64
|
+
}
|
|
65
|
+
return vars;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function saveEnvFile(vars) {
|
|
69
|
+
mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
70
|
+
const lines = [
|
|
71
|
+
"# code-graph-builder configuration",
|
|
72
|
+
"# Generated by setup wizard. Edit freely.",
|
|
73
|
+
"",
|
|
74
|
+
];
|
|
75
|
+
for (const [key, val] of Object.entries(vars)) {
|
|
76
|
+
if (val) lines.push(`${key}=${val}`);
|
|
77
|
+
}
|
|
78
|
+
lines.push("");
|
|
79
|
+
writeFileSync(ENV_FILE, lines.join("\n"), "utf-8");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function mask(s) {
|
|
83
|
+
if (!s || s.length < 8) return s ? "****" : "(not set)";
|
|
84
|
+
return s.slice(0, 4) + "****" + s.slice(-4);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Interactive setup wizard (runs on stderr so stdout stays clean)
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
async function runSetup() {
|
|
92
|
+
const rl = createInterface({
|
|
93
|
+
input: process.stdin,
|
|
94
|
+
output: process.stderr,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
98
|
+
const log = (msg) => process.stderr.write(msg + "\n");
|
|
99
|
+
|
|
100
|
+
log("");
|
|
101
|
+
log("╔══════════════════════════════════════════════════════════╗");
|
|
102
|
+
log("║ code-graph-builder Setup Wizard ║");
|
|
103
|
+
log("╚══════════════════════════════════════════════════════════╝");
|
|
104
|
+
log("");
|
|
105
|
+
|
|
106
|
+
// Load existing config
|
|
107
|
+
const existing = loadEnvFile();
|
|
108
|
+
|
|
109
|
+
// --- Workspace ---
|
|
110
|
+
log("── 1/3 Workspace ──────────────────────────────────────────");
|
|
111
|
+
log(`Workspace stores indexed repos, graphs, and embeddings.`);
|
|
112
|
+
const workspace =
|
|
113
|
+
(await ask(` Workspace path [${WORKSPACE_DIR}]: `)).trim() || WORKSPACE_DIR;
|
|
114
|
+
log("");
|
|
115
|
+
|
|
116
|
+
// --- LLM API Key ---
|
|
117
|
+
log("── 2/3 LLM API Key (for natural language queries & descriptions) ──");
|
|
118
|
+
log("");
|
|
119
|
+
log(" Supported providers (OpenAI-compatible):");
|
|
120
|
+
log(" - Moonshot / Kimi https://platform.moonshot.cn");
|
|
121
|
+
log(" - OpenAI https://platform.openai.com");
|
|
122
|
+
log(" - DeepSeek https://platform.deepseek.com");
|
|
123
|
+
log(" - Any OpenAI-compatible endpoint");
|
|
124
|
+
log("");
|
|
125
|
+
|
|
126
|
+
if (existing.LLM_API_KEY) {
|
|
127
|
+
log(` Current key: ${mask(existing.LLM_API_KEY)}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const llmKey =
|
|
131
|
+
(await ask(" LLM API Key (sk-...): ")).trim() || existing.LLM_API_KEY || "";
|
|
132
|
+
|
|
133
|
+
let llmBaseUrl = existing.LLM_BASE_URL || "";
|
|
134
|
+
let llmModel = existing.LLM_MODEL || "";
|
|
135
|
+
|
|
136
|
+
if (llmKey) {
|
|
137
|
+
log("");
|
|
138
|
+
log(" Detecting provider from key...");
|
|
139
|
+
|
|
140
|
+
if (llmKey.startsWith("sk-") && !llmKey.startsWith("sk-ant-")) {
|
|
141
|
+
// Could be Moonshot, OpenAI, or other
|
|
142
|
+
const urlInput = (
|
|
143
|
+
await ask(
|
|
144
|
+
` API Base URL [${llmBaseUrl || "https://api.moonshot.cn/v1"}]: `
|
|
145
|
+
)
|
|
146
|
+
).trim();
|
|
147
|
+
llmBaseUrl = urlInput || llmBaseUrl || "https://api.moonshot.cn/v1";
|
|
148
|
+
|
|
149
|
+
if (llmBaseUrl.includes("moonshot")) {
|
|
150
|
+
llmModel = (await ask(` Model name [kimi-k2.5]: `)).trim() || "kimi-k2.5";
|
|
151
|
+
} else if (llmBaseUrl.includes("openai")) {
|
|
152
|
+
llmModel = (await ask(` Model name [gpt-4o]: `)).trim() || "gpt-4o";
|
|
153
|
+
} else if (llmBaseUrl.includes("deepseek")) {
|
|
154
|
+
llmModel = (await ask(` Model name [deepseek-chat]: `)).trim() || "deepseek-chat";
|
|
155
|
+
} else {
|
|
156
|
+
llmModel =
|
|
157
|
+
(await ask(` Model name [${llmModel || "gpt-4o"}]: `)).trim() ||
|
|
158
|
+
llmModel ||
|
|
159
|
+
"gpt-4o";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
log("");
|
|
164
|
+
|
|
165
|
+
// --- Embedding API Key ---
|
|
166
|
+
log("── 3/3 Embedding API Key (for semantic code search) ─────");
|
|
167
|
+
log("");
|
|
168
|
+
log(" Used for vector embedding of code (Qwen3 text-embedding-v4).");
|
|
169
|
+
log(" Get a free key at: https://dashscope.console.aliyun.com");
|
|
170
|
+
log("");
|
|
171
|
+
|
|
172
|
+
if (existing.DASHSCOPE_API_KEY) {
|
|
173
|
+
log(` Current key: ${mask(existing.DASHSCOPE_API_KEY)}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const dashscopeKey =
|
|
177
|
+
(await ask(" DashScope API Key (sk-...): ")).trim() ||
|
|
178
|
+
existing.DASHSCOPE_API_KEY ||
|
|
179
|
+
"";
|
|
180
|
+
|
|
181
|
+
rl.close();
|
|
182
|
+
|
|
183
|
+
// --- Save ---
|
|
184
|
+
const config = {
|
|
185
|
+
CGB_WORKSPACE: workspace,
|
|
186
|
+
LLM_API_KEY: llmKey,
|
|
187
|
+
LLM_BASE_URL: llmBaseUrl,
|
|
188
|
+
LLM_MODEL: llmModel,
|
|
189
|
+
DASHSCOPE_API_KEY: dashscopeKey,
|
|
190
|
+
DASHSCOPE_BASE_URL: "https://dashscope.aliyuncs.com/api/v1",
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
saveEnvFile(config);
|
|
194
|
+
|
|
195
|
+
log("");
|
|
196
|
+
log("── Configuration saved ─────────────────────────────────────");
|
|
197
|
+
log(` File: ${ENV_FILE}`);
|
|
198
|
+
log("");
|
|
199
|
+
log(" LLM: " + (llmKey ? `${mask(llmKey)} → ${llmModel}` : "not configured (optional)"));
|
|
200
|
+
log(" Embedding: " + (dashscopeKey ? mask(dashscopeKey) : "not configured (optional)"));
|
|
201
|
+
log(" Workspace: " + workspace);
|
|
202
|
+
log("");
|
|
203
|
+
log("── Next steps ──────────────────────────────────────────────");
|
|
204
|
+
log("");
|
|
205
|
+
log(" Add to your MCP client config:");
|
|
206
|
+
log("");
|
|
207
|
+
log(' {');
|
|
208
|
+
log(' "mcpServers": {');
|
|
209
|
+
log(' "code-graph-builder": {');
|
|
210
|
+
log(' "command": "npx",');
|
|
211
|
+
log(' "args": ["-y", "code-graph-builder", "--server"]');
|
|
212
|
+
log(" }");
|
|
213
|
+
log(" }");
|
|
214
|
+
log(" }");
|
|
215
|
+
log("");
|
|
216
|
+
log(" Or run directly: npx code-graph-builder --server");
|
|
217
|
+
log("");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Start MCP server
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
53
224
|
function runServer(cmd, args) {
|
|
225
|
+
// Merge .env file into environment
|
|
226
|
+
const envVars = loadEnvFile();
|
|
227
|
+
const mergedEnv = { ...process.env, ...envVars };
|
|
228
|
+
|
|
229
|
+
// Ensure CGB_WORKSPACE is set
|
|
230
|
+
if (!mergedEnv.CGB_WORKSPACE) {
|
|
231
|
+
mergedEnv.CGB_WORKSPACE = WORKSPACE_DIR;
|
|
232
|
+
}
|
|
233
|
+
|
|
54
234
|
const child = spawn(cmd, args, {
|
|
55
235
|
stdio: "inherit",
|
|
56
|
-
env,
|
|
236
|
+
env: mergedEnv,
|
|
57
237
|
});
|
|
58
238
|
|
|
59
239
|
child.on("error", (err) => {
|
|
60
|
-
|
|
240
|
+
process.stderr.write(`Failed to start MCP server: ${err.message}\n`);
|
|
61
241
|
process.exit(1);
|
|
62
242
|
});
|
|
63
243
|
|
|
@@ -66,44 +246,69 @@ function runServer(cmd, args) {
|
|
|
66
246
|
});
|
|
67
247
|
}
|
|
68
248
|
|
|
249
|
+
function startServer(extraArgs = []) {
|
|
250
|
+
if (commandExists("uvx")) {
|
|
251
|
+
runServer("uvx", [PYTHON_PACKAGE, ...extraArgs]);
|
|
252
|
+
} else if (commandExists("uv")) {
|
|
253
|
+
runServer("uv", ["tool", "run", PYTHON_PACKAGE, ...extraArgs]);
|
|
254
|
+
} else if (commandExists("pipx")) {
|
|
255
|
+
runServer("pipx", ["run", PYTHON_PACKAGE, ...extraArgs]);
|
|
256
|
+
} else if (pythonPackageInstalled()) {
|
|
257
|
+
runServer("python3", ["-m", MODULE_PATH]);
|
|
258
|
+
} else {
|
|
259
|
+
process.stderr.write(
|
|
260
|
+
`code-graph-builder requires Python 3.10+.\n\n` +
|
|
261
|
+
`Install options:\n` +
|
|
262
|
+
` 1. pip install ${PYTHON_PACKAGE}\n` +
|
|
263
|
+
` 2. curl -LsSf https://astral.sh/uv/install.sh | sh (installs uv)\n` +
|
|
264
|
+
` 3. pip install pipx\n\n` +
|
|
265
|
+
`Then run: npx code-graph-builder --server\n`
|
|
266
|
+
);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
69
271
|
// ---------------------------------------------------------------------------
|
|
70
272
|
// Main
|
|
71
273
|
// ---------------------------------------------------------------------------
|
|
72
274
|
|
|
73
|
-
const
|
|
275
|
+
const args = process.argv.slice(2);
|
|
276
|
+
const mode = args[0];
|
|
74
277
|
|
|
75
|
-
if (
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)
|
|
82
|
-
|
|
278
|
+
if (mode === "--setup") {
|
|
279
|
+
// Explicit setup request
|
|
280
|
+
runSetup();
|
|
281
|
+
} else if (mode === "--server" || mode === "--pip" || mode === "--python") {
|
|
282
|
+
// Start MCP server directly
|
|
283
|
+
if (mode === "--pip" || mode === "--python") {
|
|
284
|
+
if (!pythonPackageInstalled()) {
|
|
285
|
+
process.stderr.write(
|
|
286
|
+
`Error: Python package '${PYTHON_PACKAGE}' is not installed.\n` +
|
|
287
|
+
`Run: pip install ${PYTHON_PACKAGE}\n`
|
|
288
|
+
);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
runServer("python3", ["-m", MODULE_PATH]);
|
|
292
|
+
} else {
|
|
293
|
+
startServer(args.slice(1));
|
|
83
294
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
runServer("pipx", ["run", PYTHON_PACKAGE, ...process.argv.slice(2)]);
|
|
94
|
-
} else if (pythonPackageInstalled()) {
|
|
95
|
-
// Fallback: direct python3
|
|
96
|
-
runServer("python3", ["-m", MODULE_PATH]);
|
|
97
|
-
} else {
|
|
98
|
-
// Nothing works — guide the user
|
|
99
|
-
console.error(
|
|
100
|
-
`code-graph-builder MCP server requires Python 3.10+.\n\n` +
|
|
101
|
-
`Install options (pick one):\n` +
|
|
102
|
-
` 1. pip install ${PYTHON_PACKAGE} # then: npx code-graph-builder --pip\n` +
|
|
103
|
-
` 2. Install uv (recommended): curl -LsSf https://astral.sh/uv/install.sh | sh\n` +
|
|
104
|
-
` Then: npx code-graph-builder # auto-installs via uvx\n` +
|
|
105
|
-
` 3. Install pipx: pip install pipx\n` +
|
|
106
|
-
` Then: npx code-graph-builder # auto-installs via pipx\n`
|
|
295
|
+
} else if (mode === "--help" || mode === "-h") {
|
|
296
|
+
process.stderr.write(
|
|
297
|
+
`code-graph-builder - Code knowledge graph MCP server\n\n` +
|
|
298
|
+
`Usage:\n` +
|
|
299
|
+
` npx code-graph-builder Interactive setup wizard\n` +
|
|
300
|
+
` npx code-graph-builder --server Start MCP server\n` +
|
|
301
|
+
` npx code-graph-builder --setup Re-run setup wizard\n` +
|
|
302
|
+
` npx code-graph-builder --help Show this help\n\n` +
|
|
303
|
+
`Config: ${ENV_FILE}\n`
|
|
107
304
|
);
|
|
108
|
-
|
|
305
|
+
} else {
|
|
306
|
+
// No args: auto-detect
|
|
307
|
+
if (!existsSync(ENV_FILE)) {
|
|
308
|
+
// First run → setup wizard
|
|
309
|
+
runSetup();
|
|
310
|
+
} else {
|
|
311
|
+
// Config exists → start server
|
|
312
|
+
startServer(args);
|
|
313
|
+
}
|
|
109
314
|
}
|