codesight 1.0.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/README.md +213 -0
- package/dist/detectors/components.d.ts +2 -0
- package/dist/detectors/components.js +237 -0
- package/dist/detectors/config.d.ts +2 -0
- package/dist/detectors/config.js +142 -0
- package/dist/detectors/contracts.d.ts +6 -0
- package/dist/detectors/contracts.js +118 -0
- package/dist/detectors/graph.d.ts +2 -0
- package/dist/detectors/graph.js +113 -0
- package/dist/detectors/libs.d.ts +2 -0
- package/dist/detectors/libs.js +206 -0
- package/dist/detectors/middleware.d.ts +2 -0
- package/dist/detectors/middleware.js +116 -0
- package/dist/detectors/routes.d.ts +2 -0
- package/dist/detectors/routes.js +356 -0
- package/dist/detectors/schema.d.ts +2 -0
- package/dist/detectors/schema.js +283 -0
- package/dist/detectors/tokens.d.ts +6 -0
- package/dist/detectors/tokens.js +48 -0
- package/dist/formatter.d.ts +2 -0
- package/dist/formatter.js +268 -0
- package/dist/generators/ai-config.d.ts +2 -0
- package/dist/generators/ai-config.js +137 -0
- package/dist/generators/html-report.d.ts +2 -0
- package/dist/generators/html-report.js +200 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +304 -0
- package/dist/mcp-server.d.ts +1 -0
- package/dist/mcp-server.js +171 -0
- package/dist/scanner.d.ts +4 -0
- package/dist/scanner.js +329 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.js +1 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve, join } from "node:path";
|
|
3
|
+
import { writeFile, stat, mkdir } from "node:fs/promises";
|
|
4
|
+
import { collectFiles, detectProject } from "./scanner.js";
|
|
5
|
+
import { detectRoutes } from "./detectors/routes.js";
|
|
6
|
+
import { detectSchemas } from "./detectors/schema.js";
|
|
7
|
+
import { detectComponents } from "./detectors/components.js";
|
|
8
|
+
import { detectLibs } from "./detectors/libs.js";
|
|
9
|
+
import { detectConfig } from "./detectors/config.js";
|
|
10
|
+
import { detectMiddleware } from "./detectors/middleware.js";
|
|
11
|
+
import { detectDependencyGraph } from "./detectors/graph.js";
|
|
12
|
+
import { enrichRouteContracts } from "./detectors/contracts.js";
|
|
13
|
+
import { calculateTokenStats } from "./detectors/tokens.js";
|
|
14
|
+
import { writeOutput } from "./formatter.js";
|
|
15
|
+
import { generateAIConfigs } from "./generators/ai-config.js";
|
|
16
|
+
import { generateHtmlReport } from "./generators/html-report.js";
|
|
17
|
+
const VERSION = "1.0.0";
|
|
18
|
+
const BRAND = "codesight";
|
|
19
|
+
function printHelp() {
|
|
20
|
+
console.log(`
|
|
21
|
+
${BRAND} v${VERSION} — See your codebase clearly
|
|
22
|
+
|
|
23
|
+
Usage: ${BRAND} [options] [directory]
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
-o, --output <dir> Output directory (default: .codesight)
|
|
27
|
+
-d, --depth <n> Max directory depth (default: 10)
|
|
28
|
+
--init Generate AI config files (CLAUDE.md, .cursorrules, etc.)
|
|
29
|
+
--watch Re-scan on file changes
|
|
30
|
+
--hook Install git pre-commit hook
|
|
31
|
+
--html Generate interactive HTML report
|
|
32
|
+
--open Generate HTML report and open in browser
|
|
33
|
+
--mcp Start as MCP server (for Claude Code, Cursor)
|
|
34
|
+
--json Output JSON instead of markdown
|
|
35
|
+
-v, --version Show version
|
|
36
|
+
-h, --help Show this help
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
npx ${BRAND} # Scan current directory
|
|
40
|
+
npx ${BRAND} --init # Scan + generate AI config files
|
|
41
|
+
npx ${BRAND} --open # Scan + open visual report
|
|
42
|
+
npx ${BRAND} --watch # Watch mode, re-scan on changes
|
|
43
|
+
npx ${BRAND} --mcp # Start MCP server
|
|
44
|
+
npx ${BRAND} --hook # Install git pre-commit hook
|
|
45
|
+
npx ${BRAND} ./my-project # Scan specific directory
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
async function fileExists(path) {
|
|
49
|
+
try {
|
|
50
|
+
await stat(path);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function scan(root, outputDirName, maxDepth) {
|
|
58
|
+
const outputDir = join(root, outputDirName);
|
|
59
|
+
console.log(`\n ${BRAND} v${VERSION}`);
|
|
60
|
+
console.log(` Scanning: ${root}\n`);
|
|
61
|
+
const startTime = Date.now();
|
|
62
|
+
// Step 1: Detect project
|
|
63
|
+
process.stdout.write(" Detecting project...");
|
|
64
|
+
const project = await detectProject(root);
|
|
65
|
+
console.log(` ${project.frameworks.length > 0 ? project.frameworks.join(", ") : "generic"} | ${project.orms.length > 0 ? project.orms.join(", ") : "no ORM"} | ${project.language}`);
|
|
66
|
+
if (project.isMonorepo) {
|
|
67
|
+
console.log(` Monorepo: ${project.workspaces.map((w) => w.name).join(", ")}`);
|
|
68
|
+
}
|
|
69
|
+
// Step 2: Collect files
|
|
70
|
+
process.stdout.write(" Collecting files...");
|
|
71
|
+
const files = await collectFiles(root, maxDepth);
|
|
72
|
+
console.log(` ${files.length} files`);
|
|
73
|
+
// Step 3: Run all detectors in parallel
|
|
74
|
+
process.stdout.write(" Analyzing...");
|
|
75
|
+
const [rawRoutes, schemas, components, libs, config, middleware, graph] = await Promise.all([
|
|
76
|
+
detectRoutes(files, project),
|
|
77
|
+
detectSchemas(files, project),
|
|
78
|
+
detectComponents(files, project),
|
|
79
|
+
detectLibs(files, project),
|
|
80
|
+
detectConfig(files, project),
|
|
81
|
+
detectMiddleware(files, project),
|
|
82
|
+
detectDependencyGraph(files, project),
|
|
83
|
+
]);
|
|
84
|
+
// Step 4: Enrich routes with contract info
|
|
85
|
+
const routes = await enrichRouteContracts(rawRoutes, project);
|
|
86
|
+
console.log(" done");
|
|
87
|
+
// Step 5: Write output
|
|
88
|
+
process.stdout.write(" Writing output...");
|
|
89
|
+
// Temporary result without token stats to generate output
|
|
90
|
+
const tempResult = {
|
|
91
|
+
project,
|
|
92
|
+
routes,
|
|
93
|
+
schemas,
|
|
94
|
+
components,
|
|
95
|
+
libs,
|
|
96
|
+
config,
|
|
97
|
+
middleware,
|
|
98
|
+
graph,
|
|
99
|
+
tokenStats: { outputTokens: 0, estimatedExplorationTokens: 0, saved: 0, fileCount: files.length },
|
|
100
|
+
};
|
|
101
|
+
const outputContent = await writeOutput(tempResult, outputDir);
|
|
102
|
+
// Step 6: Calculate real token stats
|
|
103
|
+
const tokenStats = calculateTokenStats(tempResult, outputContent, files.length);
|
|
104
|
+
const result = { ...tempResult, tokenStats };
|
|
105
|
+
// Re-write with accurate token stats
|
|
106
|
+
await writeOutput(result, outputDir);
|
|
107
|
+
console.log(` ${outputDirName}/`);
|
|
108
|
+
const elapsed = Date.now() - startTime;
|
|
109
|
+
// Stats
|
|
110
|
+
console.log(`
|
|
111
|
+
Results:
|
|
112
|
+
Routes: ${routes.length}
|
|
113
|
+
Models: ${schemas.length}
|
|
114
|
+
Components: ${components.length}
|
|
115
|
+
Libraries: ${libs.length}
|
|
116
|
+
Env vars: ${config.envVars.length}
|
|
117
|
+
Middleware: ${middleware.length}
|
|
118
|
+
Import links: ${graph.edges.length}
|
|
119
|
+
Hot files: ${graph.hotFiles.length}
|
|
120
|
+
|
|
121
|
+
Tokens:
|
|
122
|
+
Output size: ~${tokenStats.outputTokens.toLocaleString()} tokens
|
|
123
|
+
Exploration cost: ~${tokenStats.estimatedExplorationTokens.toLocaleString()} tokens
|
|
124
|
+
Saved: ~${tokenStats.saved.toLocaleString()} tokens per conversation
|
|
125
|
+
|
|
126
|
+
Done in ${elapsed}ms
|
|
127
|
+
`);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
async function installGitHook(root, outputDirName) {
|
|
131
|
+
const hooksDir = join(root, ".git", "hooks");
|
|
132
|
+
const hookPath = join(hooksDir, "pre-commit");
|
|
133
|
+
if (!(await fileExists(join(root, ".git")))) {
|
|
134
|
+
console.log(" No .git directory found. Initialize a git repo first.");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
await mkdir(hooksDir, { recursive: true });
|
|
138
|
+
let existingContent = "";
|
|
139
|
+
try {
|
|
140
|
+
const { readFile } = await import("node:fs/promises");
|
|
141
|
+
existingContent = await readFile(hookPath, "utf-8");
|
|
142
|
+
}
|
|
143
|
+
catch { }
|
|
144
|
+
const hookCommand = `\n# codesight: regenerate AI context\nnpx codesight -o ${outputDirName}\ngit add ${outputDirName}/\n`;
|
|
145
|
+
if (existingContent.includes("codesight")) {
|
|
146
|
+
console.log(" Git hook already installed.");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (existingContent) {
|
|
150
|
+
await writeFile(hookPath, existingContent + hookCommand);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
await writeFile(hookPath, `#!/bin/sh\n${hookCommand}`);
|
|
154
|
+
}
|
|
155
|
+
// Make executable
|
|
156
|
+
const { chmod } = await import("node:fs/promises");
|
|
157
|
+
await chmod(hookPath, 0o755);
|
|
158
|
+
console.log(` Git pre-commit hook installed at .git/hooks/pre-commit`);
|
|
159
|
+
}
|
|
160
|
+
async function watchMode(root, outputDirName, maxDepth) {
|
|
161
|
+
console.log(` Watching for changes... (Ctrl+C to stop)\n`);
|
|
162
|
+
let debounceTimer = null;
|
|
163
|
+
let isScanning = false;
|
|
164
|
+
const runScan = async () => {
|
|
165
|
+
if (isScanning)
|
|
166
|
+
return;
|
|
167
|
+
isScanning = true;
|
|
168
|
+
try {
|
|
169
|
+
console.log("\n Changes detected, re-scanning...\n");
|
|
170
|
+
await scan(root, outputDirName, maxDepth);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
console.error(" Scan error:", err.message);
|
|
174
|
+
}
|
|
175
|
+
isScanning = false;
|
|
176
|
+
};
|
|
177
|
+
// Use polling approach for cross-platform compatibility
|
|
178
|
+
const { watch } = await import("node:fs");
|
|
179
|
+
const watcher = watch(root, { recursive: true }, (_event, filename) => {
|
|
180
|
+
if (!filename)
|
|
181
|
+
return;
|
|
182
|
+
// Skip output directory and hidden files
|
|
183
|
+
if (filename.startsWith(outputDirName) || filename.startsWith(".git"))
|
|
184
|
+
return;
|
|
185
|
+
if (filename.includes("node_modules"))
|
|
186
|
+
return;
|
|
187
|
+
if (debounceTimer)
|
|
188
|
+
clearTimeout(debounceTimer);
|
|
189
|
+
debounceTimer = setTimeout(runScan, 500);
|
|
190
|
+
});
|
|
191
|
+
// Keep process alive
|
|
192
|
+
process.on("SIGINT", () => {
|
|
193
|
+
watcher.close();
|
|
194
|
+
console.log("\n Watch mode stopped.");
|
|
195
|
+
process.exit(0);
|
|
196
|
+
});
|
|
197
|
+
// Wait forever
|
|
198
|
+
await new Promise(() => { });
|
|
199
|
+
}
|
|
200
|
+
async function main() {
|
|
201
|
+
const args = process.argv.slice(2);
|
|
202
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
203
|
+
printHelp();
|
|
204
|
+
process.exit(0);
|
|
205
|
+
}
|
|
206
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
207
|
+
console.log(`${BRAND} v${VERSION}`);
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}
|
|
210
|
+
// Parse args
|
|
211
|
+
let targetDir = process.cwd();
|
|
212
|
+
let outputDirName = ".codesight";
|
|
213
|
+
let maxDepth = 10;
|
|
214
|
+
let jsonOutput = false;
|
|
215
|
+
let doInit = false;
|
|
216
|
+
let doWatch = false;
|
|
217
|
+
let doHook = false;
|
|
218
|
+
let doHtml = false;
|
|
219
|
+
let doOpen = false;
|
|
220
|
+
let doMcp = false;
|
|
221
|
+
for (let i = 0; i < args.length; i++) {
|
|
222
|
+
const arg = args[i];
|
|
223
|
+
if ((arg === "-o" || arg === "--output") && args[i + 1]) {
|
|
224
|
+
outputDirName = args[++i];
|
|
225
|
+
}
|
|
226
|
+
else if ((arg === "-d" || arg === "--depth") && args[i + 1]) {
|
|
227
|
+
maxDepth = parseInt(args[++i], 10);
|
|
228
|
+
}
|
|
229
|
+
else if (arg === "--json") {
|
|
230
|
+
jsonOutput = true;
|
|
231
|
+
}
|
|
232
|
+
else if (arg === "--init") {
|
|
233
|
+
doInit = true;
|
|
234
|
+
}
|
|
235
|
+
else if (arg === "--watch") {
|
|
236
|
+
doWatch = true;
|
|
237
|
+
}
|
|
238
|
+
else if (arg === "--hook") {
|
|
239
|
+
doHook = true;
|
|
240
|
+
}
|
|
241
|
+
else if (arg === "--html") {
|
|
242
|
+
doHtml = true;
|
|
243
|
+
}
|
|
244
|
+
else if (arg === "--open") {
|
|
245
|
+
doHtml = true;
|
|
246
|
+
doOpen = true;
|
|
247
|
+
}
|
|
248
|
+
else if (arg === "--mcp") {
|
|
249
|
+
doMcp = true;
|
|
250
|
+
}
|
|
251
|
+
else if (!arg.startsWith("-")) {
|
|
252
|
+
targetDir = resolve(arg);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// MCP server mode (blocks, no other output)
|
|
256
|
+
if (doMcp) {
|
|
257
|
+
const { startMCPServer } = await import("./mcp-server.js");
|
|
258
|
+
await startMCPServer();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const root = resolve(targetDir);
|
|
262
|
+
// Install git hook
|
|
263
|
+
if (doHook) {
|
|
264
|
+
await installGitHook(root, outputDirName);
|
|
265
|
+
}
|
|
266
|
+
// Run scan
|
|
267
|
+
const result = await scan(root, outputDirName, maxDepth);
|
|
268
|
+
// JSON output
|
|
269
|
+
if (jsonOutput) {
|
|
270
|
+
console.log(JSON.stringify(result, null, 2));
|
|
271
|
+
}
|
|
272
|
+
// Generate AI config files
|
|
273
|
+
if (doInit) {
|
|
274
|
+
process.stdout.write(" Generating AI configs...");
|
|
275
|
+
const generated = await generateAIConfigs(result, root);
|
|
276
|
+
if (generated.length > 0) {
|
|
277
|
+
console.log(` ${generated.join(", ")}`);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
console.log(" all configs already exist");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// Generate HTML report
|
|
284
|
+
if (doHtml) {
|
|
285
|
+
const outputDir = join(root, outputDirName);
|
|
286
|
+
process.stdout.write(" Generating HTML report...");
|
|
287
|
+
const reportPath = await generateHtmlReport(result, outputDir);
|
|
288
|
+
console.log(` ${outputDirName}/report.html`);
|
|
289
|
+
if (doOpen) {
|
|
290
|
+
const { exec } = await import("node:child_process");
|
|
291
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
292
|
+
exec(`${cmd} "${reportPath}"`);
|
|
293
|
+
console.log(" Opening in browser...");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// Watch mode (blocks)
|
|
297
|
+
if (doWatch) {
|
|
298
|
+
await watchMode(root, outputDirName, maxDepth);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
main().catch((err) => {
|
|
302
|
+
console.error("Error:", err.message);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startMCPServer(): Promise<void>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { collectFiles, detectProject } from "./scanner.js";
|
|
3
|
+
import { detectRoutes } from "./detectors/routes.js";
|
|
4
|
+
import { detectSchemas } from "./detectors/schema.js";
|
|
5
|
+
import { detectComponents } from "./detectors/components.js";
|
|
6
|
+
import { detectLibs } from "./detectors/libs.js";
|
|
7
|
+
import { detectConfig } from "./detectors/config.js";
|
|
8
|
+
import { detectMiddleware } from "./detectors/middleware.js";
|
|
9
|
+
import { detectDependencyGraph } from "./detectors/graph.js";
|
|
10
|
+
import { enrichRouteContracts } from "./detectors/contracts.js";
|
|
11
|
+
import { calculateTokenStats } from "./detectors/tokens.js";
|
|
12
|
+
import { writeOutput } from "./formatter.js";
|
|
13
|
+
function send(msg) {
|
|
14
|
+
const json = JSON.stringify(msg);
|
|
15
|
+
const header = `Content-Length: ${Buffer.byteLength(json)}\r\n\r\n`;
|
|
16
|
+
process.stdout.write(header + json);
|
|
17
|
+
}
|
|
18
|
+
async function runScan(directory) {
|
|
19
|
+
const root = resolve(directory || process.cwd());
|
|
20
|
+
const project = await detectProject(root);
|
|
21
|
+
const files = await collectFiles(root, 10);
|
|
22
|
+
const [rawRoutes, schemas, components, libs, config, middleware, graph] = await Promise.all([
|
|
23
|
+
detectRoutes(files, project),
|
|
24
|
+
detectSchemas(files, project),
|
|
25
|
+
detectComponents(files, project),
|
|
26
|
+
detectLibs(files, project),
|
|
27
|
+
detectConfig(files, project),
|
|
28
|
+
detectMiddleware(files, project),
|
|
29
|
+
detectDependencyGraph(files, project),
|
|
30
|
+
]);
|
|
31
|
+
const routes = await enrichRouteContracts(rawRoutes, project);
|
|
32
|
+
const tempResult = {
|
|
33
|
+
project,
|
|
34
|
+
routes,
|
|
35
|
+
schemas,
|
|
36
|
+
components,
|
|
37
|
+
libs,
|
|
38
|
+
config,
|
|
39
|
+
middleware,
|
|
40
|
+
graph,
|
|
41
|
+
tokenStats: { outputTokens: 0, estimatedExplorationTokens: 0, saved: 0, fileCount: files.length },
|
|
42
|
+
};
|
|
43
|
+
const outputContent = await writeOutput(tempResult, resolve(root, ".codesight"));
|
|
44
|
+
const tokenStats = calculateTokenStats(tempResult, outputContent, files.length);
|
|
45
|
+
return outputContent.replace(/Saves ~\d[\d,]* tokens/, `Saves ~${tokenStats.saved.toLocaleString()} tokens`);
|
|
46
|
+
}
|
|
47
|
+
async function handleRequest(req) {
|
|
48
|
+
// MCP initialize
|
|
49
|
+
if (req.method === "initialize") {
|
|
50
|
+
send({
|
|
51
|
+
jsonrpc: "2.0",
|
|
52
|
+
id: req.id ?? null,
|
|
53
|
+
result: {
|
|
54
|
+
protocolVersion: "2024-11-05",
|
|
55
|
+
capabilities: { tools: {} },
|
|
56
|
+
serverInfo: { name: "codesight", version: "1.0.0" },
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// MCP initialized notification
|
|
62
|
+
if (req.method === "notifications/initialized") {
|
|
63
|
+
return; // no response for notifications
|
|
64
|
+
}
|
|
65
|
+
// List tools
|
|
66
|
+
if (req.method === "tools/list") {
|
|
67
|
+
send({
|
|
68
|
+
jsonrpc: "2.0",
|
|
69
|
+
id: req.id ?? null,
|
|
70
|
+
result: {
|
|
71
|
+
tools: [
|
|
72
|
+
{
|
|
73
|
+
name: "codesight_scan",
|
|
74
|
+
description: "Scans a codebase and returns a complete AI context map including routes, database schema, components, libraries, config, middleware, and dependency graph. Saves thousands of tokens vs manual exploration.",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
directory: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "Directory to scan (defaults to current working directory)",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Call tool
|
|
91
|
+
if (req.method === "tools/call") {
|
|
92
|
+
const toolName = req.params?.name;
|
|
93
|
+
const args = req.params?.arguments || {};
|
|
94
|
+
if (toolName === "codesight_scan") {
|
|
95
|
+
try {
|
|
96
|
+
const result = await runScan(args.directory || process.cwd());
|
|
97
|
+
send({
|
|
98
|
+
jsonrpc: "2.0",
|
|
99
|
+
id: req.id ?? null,
|
|
100
|
+
result: {
|
|
101
|
+
content: [{ type: "text", text: result }],
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
send({
|
|
107
|
+
jsonrpc: "2.0",
|
|
108
|
+
id: req.id ?? null,
|
|
109
|
+
result: {
|
|
110
|
+
content: [{ type: "text", text: `Error scanning: ${err.message}` }],
|
|
111
|
+
isError: true,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
send({
|
|
118
|
+
jsonrpc: "2.0",
|
|
119
|
+
id: req.id ?? null,
|
|
120
|
+
error: { code: -32601, message: `Unknown tool: ${toolName}` },
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Unknown method
|
|
125
|
+
if (req.id !== undefined) {
|
|
126
|
+
send({
|
|
127
|
+
jsonrpc: "2.0",
|
|
128
|
+
id: req.id,
|
|
129
|
+
error: { code: -32601, message: `Method not found: ${req.method}` },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export async function startMCPServer() {
|
|
134
|
+
// Read Content-Length delimited JSON-RPC messages from stdin
|
|
135
|
+
let buffer = "";
|
|
136
|
+
process.stdin.setEncoding("utf-8");
|
|
137
|
+
process.stdin.on("data", async (chunk) => {
|
|
138
|
+
buffer += chunk;
|
|
139
|
+
while (true) {
|
|
140
|
+
// Parse Content-Length header
|
|
141
|
+
const headerEnd = buffer.indexOf("\r\n\r\n");
|
|
142
|
+
if (headerEnd === -1)
|
|
143
|
+
break;
|
|
144
|
+
const header = buffer.substring(0, headerEnd);
|
|
145
|
+
const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
|
|
146
|
+
if (!lengthMatch) {
|
|
147
|
+
buffer = buffer.substring(headerEnd + 4);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const contentLength = parseInt(lengthMatch[1], 10);
|
|
151
|
+
const bodyStart = headerEnd + 4;
|
|
152
|
+
if (buffer.length < bodyStart + contentLength)
|
|
153
|
+
break;
|
|
154
|
+
const body = buffer.substring(bodyStart, bodyStart + contentLength);
|
|
155
|
+
buffer = buffer.substring(bodyStart + contentLength);
|
|
156
|
+
try {
|
|
157
|
+
const req = JSON.parse(body);
|
|
158
|
+
await handleRequest(req);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
send({
|
|
162
|
+
jsonrpc: "2.0",
|
|
163
|
+
id: null,
|
|
164
|
+
error: { code: -32700, message: "Parse error" },
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
// Keep alive
|
|
170
|
+
await new Promise(() => { });
|
|
171
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProjectInfo } from "./types.js";
|
|
2
|
+
export declare function collectFiles(root: string, maxDepth?: number): Promise<string[]>;
|
|
3
|
+
export declare function readFileSafe(path: string): Promise<string>;
|
|
4
|
+
export declare function detectProject(root: string): Promise<ProjectInfo>;
|