@danielblomma/cortex-mcp 0.4.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 +203 -0
- package/bin/cortex.mjs +621 -0
- package/docs/MCP_MARKETPLACE.md +160 -0
- package/package.json +42 -0
- package/scaffold/.context/config.yaml +21 -0
- package/scaffold/.context/ontology.cypher +63 -0
- package/scaffold/.context/rules.yaml +25 -0
- package/scaffold/.githooks/_cortex-update-runner.sh +58 -0
- package/scaffold/.githooks/post-checkout +22 -0
- package/scaffold/.githooks/post-merge +14 -0
- package/scaffold/docs/architecture.md +22 -0
- package/scaffold/mcp/package-lock.json +2623 -0
- package/scaffold/mcp/package.json +29 -0
- package/scaffold/mcp/src/embed.ts +416 -0
- package/scaffold/mcp/src/embeddings.ts +192 -0
- package/scaffold/mcp/src/graph.ts +666 -0
- package/scaffold/mcp/src/loadGraph.ts +597 -0
- package/scaffold/mcp/src/paths.ts +33 -0
- package/scaffold/mcp/src/search.ts +412 -0
- package/scaffold/mcp/src/server.ts +98 -0
- package/scaffold/mcp/src/types.ts +109 -0
- package/scaffold/mcp/tests/server.test.mjs +60 -0
- package/scaffold/mcp/tsconfig.json +13 -0
- package/scaffold/scripts/bootstrap.sh +57 -0
- package/scaffold/scripts/capture-note.sh +55 -0
- package/scaffold/scripts/context.sh +109 -0
- package/scaffold/scripts/embed.sh +15 -0
- package/scaffold/scripts/ingest.mjs +1118 -0
- package/scaffold/scripts/ingest.sh +20 -0
- package/scaffold/scripts/install-git-hooks.sh +21 -0
- package/scaffold/scripts/load-kuzu.sh +6 -0
- package/scaffold/scripts/load-ryu.sh +18 -0
- package/scaffold/scripts/parsers/javascript.mjs +390 -0
- package/scaffold/scripts/parsers/package-lock.json +51 -0
- package/scaffold/scripts/parsers/package.json +17 -0
- package/scaffold/scripts/plan-state-engine.cjs +310 -0
- package/scaffold/scripts/plan-state.sh +71 -0
- package/scaffold/scripts/refresh.sh +9 -0
- package/scaffold/scripts/status.sh +282 -0
- package/scaffold/scripts/update-context.sh +18 -0
- package/scaffold/scripts/watch.sh +374 -0
package/bin/cortex.mjs
ADDED
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
10
|
+
const SCAFFOLD_ROOT = path.join(PACKAGE_ROOT, "scaffold");
|
|
11
|
+
const PACKAGE_JSON_PATH = path.join(PACKAGE_ROOT, "package.json");
|
|
12
|
+
|
|
13
|
+
const GITIGNORE_LINES = [
|
|
14
|
+
"",
|
|
15
|
+
"# Cortex local storage",
|
|
16
|
+
".context/db/",
|
|
17
|
+
".context/embeddings/",
|
|
18
|
+
".context/cache/",
|
|
19
|
+
".context/plan/",
|
|
20
|
+
".context/hooks/",
|
|
21
|
+
".npm-cache/",
|
|
22
|
+
"mcp/.npm-cache/",
|
|
23
|
+
"mcp/dist/",
|
|
24
|
+
"mcp/node_modules/"
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const CORTEX_LOGO = [
|
|
28
|
+
" CCC OOO RRRR TTTTT EEEEE X X",
|
|
29
|
+
" C C O O R R T E X X",
|
|
30
|
+
" C O O RRRR T EEEE X",
|
|
31
|
+
" C C O O R R T E X X",
|
|
32
|
+
" CCC OOO R R T EEEEE X X"
|
|
33
|
+
].join("\n");
|
|
34
|
+
|
|
35
|
+
function printBanner(title) {
|
|
36
|
+
console.log(CORTEX_LOGO);
|
|
37
|
+
if (title) {
|
|
38
|
+
console.log(title);
|
|
39
|
+
}
|
|
40
|
+
console.log("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function printHelp() {
|
|
44
|
+
console.log("Cortex CLI");
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log("Usage:");
|
|
47
|
+
console.log(" cortex init [path] [--force] [--bootstrap] [--connect] [--no-connect] [--watch] [--no-watch]");
|
|
48
|
+
console.log(" cortex connect [path] [--skip-build]");
|
|
49
|
+
console.log(" cortex mcp");
|
|
50
|
+
console.log(
|
|
51
|
+
" cortex watch [start|stop|status|run|once] [--interval <sec>] [--debounce <sec>] [--mode <auto|event|poll>]"
|
|
52
|
+
);
|
|
53
|
+
console.log(" cortex bootstrap");
|
|
54
|
+
console.log(" cortex update");
|
|
55
|
+
console.log(" cortex status");
|
|
56
|
+
console.log(" cortex ingest [--changed] [--verbose]");
|
|
57
|
+
console.log(" cortex embed [--changed]");
|
|
58
|
+
console.log(" cortex graph-load [--no-reset]");
|
|
59
|
+
console.log(" cortex note <title> [text]");
|
|
60
|
+
console.log(" cortex plan");
|
|
61
|
+
console.log(" cortex todo [text|list|done <id>|reopen <id>|remove <id>]");
|
|
62
|
+
console.log(" cortex help");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readCliVersion() {
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, "utf8"));
|
|
68
|
+
return typeof parsed.version === "string" ? parsed.version : "0.0.0";
|
|
69
|
+
} catch {
|
|
70
|
+
return "0.0.0";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseInitArgs(args) {
|
|
75
|
+
let target = process.cwd();
|
|
76
|
+
let force = false;
|
|
77
|
+
let bootstrap = false;
|
|
78
|
+
let connect = true;
|
|
79
|
+
let watch = true;
|
|
80
|
+
|
|
81
|
+
for (const arg of args) {
|
|
82
|
+
if (arg === "--force") {
|
|
83
|
+
force = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (arg === "--bootstrap") {
|
|
88
|
+
bootstrap = true;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (arg === "--connect") {
|
|
93
|
+
connect = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (arg === "--no-connect") {
|
|
98
|
+
connect = false;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (arg === "--watch") {
|
|
103
|
+
watch = true;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (arg === "--no-watch") {
|
|
108
|
+
watch = false;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (arg.startsWith("-")) {
|
|
113
|
+
throw new Error(`Unknown init option: ${arg}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
target = path.resolve(arg);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { target, force, bootstrap, connect, watch };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseConnectArgs(args) {
|
|
123
|
+
let target = process.cwd();
|
|
124
|
+
let skipBuild = false;
|
|
125
|
+
|
|
126
|
+
for (const arg of args) {
|
|
127
|
+
if (arg === "--skip-build") {
|
|
128
|
+
skipBuild = true;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (arg.startsWith("-")) {
|
|
133
|
+
throw new Error(`Unknown connect option: ${arg}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
target = path.resolve(arg);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { target, skipBuild };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function ensureScaffoldExists() {
|
|
143
|
+
if (!fs.existsSync(SCAFFOLD_ROOT)) {
|
|
144
|
+
throw new Error(`Scaffold not found at ${SCAFFOLD_ROOT}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function copyDirectory(sourceDir, targetDir) {
|
|
149
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
150
|
+
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
|
|
151
|
+
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
154
|
+
const targetPath = path.join(targetDir, entry.name);
|
|
155
|
+
|
|
156
|
+
if (entry.isDirectory()) {
|
|
157
|
+
copyDirectory(sourcePath, targetPath);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
162
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
163
|
+
const sourceMode = fs.statSync(sourcePath).mode;
|
|
164
|
+
fs.chmodSync(targetPath, sourceMode);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function ensurePathWritable(targetPath, force) {
|
|
169
|
+
if (!fs.existsSync(targetPath)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!force) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Refusing to overwrite existing path: ${targetPath}\nRun with --force to overwrite scaffold files.`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function mergeGitignore(targetDir) {
|
|
181
|
+
const gitignorePath = path.join(targetDir, ".gitignore");
|
|
182
|
+
const current = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
|
|
183
|
+
const merged = current + GITIGNORE_LINES.filter((line) => !current.includes(line)).join("\n") + "\n";
|
|
184
|
+
fs.writeFileSync(gitignorePath, merged, "utf8");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function installScaffold(targetDir, force) {
|
|
188
|
+
const copyMap = [
|
|
189
|
+
[path.join(SCAFFOLD_ROOT, ".context"), path.join(targetDir, ".context")],
|
|
190
|
+
[path.join(SCAFFOLD_ROOT, "scripts"), path.join(targetDir, "scripts")],
|
|
191
|
+
[path.join(SCAFFOLD_ROOT, "mcp"), path.join(targetDir, "mcp")],
|
|
192
|
+
[path.join(SCAFFOLD_ROOT, ".githooks"), path.join(targetDir, ".githooks")]
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
for (const [sourcePath, targetPath] of copyMap) {
|
|
196
|
+
ensurePathWritable(targetPath, force);
|
|
197
|
+
copyDirectory(sourcePath, targetPath);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const docsDir = path.join(targetDir, "docs");
|
|
201
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
202
|
+
const docsSource = path.join(SCAFFOLD_ROOT, "docs", "architecture.md");
|
|
203
|
+
const docsTarget = path.join(docsDir, "cortex-architecture.md");
|
|
204
|
+
if (!fs.existsSync(docsTarget) || force) {
|
|
205
|
+
fs.copyFileSync(docsSource, docsTarget);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
mergeGitignore(targetDir);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function writeTextFile(targetPath, content) {
|
|
212
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
213
|
+
fs.writeFileSync(targetPath, content, "utf8");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function upsertTextFile(targetPath, content) {
|
|
217
|
+
if (fs.existsSync(targetPath)) {
|
|
218
|
+
const existing = fs.readFileSync(targetPath, "utf8");
|
|
219
|
+
if (existing === content) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
writeTextFile(targetPath, content);
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function escapeRegex(value) {
|
|
228
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function upsertSectionByMarkers(targetPath, startMarker, endMarker, sectionContent) {
|
|
232
|
+
const block = `${startMarker}\n${sectionContent.trimEnd()}\n${endMarker}`;
|
|
233
|
+
const existing = fs.existsSync(targetPath) ? fs.readFileSync(targetPath, "utf8") : "";
|
|
234
|
+
const hasMarkers = existing.includes(startMarker) && existing.includes(endMarker);
|
|
235
|
+
|
|
236
|
+
if (hasMarkers) {
|
|
237
|
+
const pattern = new RegExp(`${escapeRegex(startMarker)}[\\s\\S]*?${escapeRegex(endMarker)}`);
|
|
238
|
+
const replaced = existing.replace(pattern, block);
|
|
239
|
+
if (replaced === existing) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
writeTextFile(targetPath, replaced.endsWith("\n") ? replaced : `${replaced}\n`);
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let next = existing;
|
|
247
|
+
if (next.length > 0 && !next.endsWith("\n")) {
|
|
248
|
+
next += "\n";
|
|
249
|
+
}
|
|
250
|
+
if (next.trim().length > 0 && !next.endsWith("\n\n")) {
|
|
251
|
+
next += "\n";
|
|
252
|
+
}
|
|
253
|
+
next += `${block}\n`;
|
|
254
|
+
writeTextFile(targetPath, next);
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function installClaudeCommands(targetDir) {
|
|
259
|
+
const commandSpecs = [
|
|
260
|
+
{
|
|
261
|
+
file: "note.md",
|
|
262
|
+
content: `---
|
|
263
|
+
description: "Save important project context into Cortex notes"
|
|
264
|
+
argument-hint: "<title> [details]"
|
|
265
|
+
---
|
|
266
|
+
Execute: cortex note "$ARGUMENTS"
|
|
267
|
+
`
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
file: "todo.md",
|
|
271
|
+
content: `---
|
|
272
|
+
description: "Add or list Cortex TODOs"
|
|
273
|
+
argument-hint: "<text|list|done <id>|reopen <id>|remove <id>>"
|
|
274
|
+
---
|
|
275
|
+
Execute: cortex todo "$ARGUMENTS"
|
|
276
|
+
`
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
file: "plan.md",
|
|
280
|
+
content: `---
|
|
281
|
+
description: "Show Cortex auto-plan progress"
|
|
282
|
+
---
|
|
283
|
+
Execute: cortex plan
|
|
284
|
+
`
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
file: "context-update.md",
|
|
288
|
+
content: `---
|
|
289
|
+
description: "Refresh Cortex context for changed files"
|
|
290
|
+
---
|
|
291
|
+
Execute: cortex update
|
|
292
|
+
`
|
|
293
|
+
}
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
const commandsDir = path.join(targetDir, ".claude", "commands");
|
|
297
|
+
let changed = 0;
|
|
298
|
+
for (const spec of commandSpecs) {
|
|
299
|
+
const targetPath = path.join(commandsDir, spec.file);
|
|
300
|
+
if (upsertTextFile(targetPath, spec.content)) {
|
|
301
|
+
changed += 1;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return { total: commandSpecs.length, changed };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function installCodexAgentsSection(targetDir) {
|
|
308
|
+
const agentsPath = path.join(targetDir, "AGENTS.md");
|
|
309
|
+
const startMarker = "<!-- cortex:auto:start -->";
|
|
310
|
+
const endMarker = "<!-- cortex:auto:end -->";
|
|
311
|
+
const section = `## Cortex Auto Workflow
|
|
312
|
+
- Use \`cortex todo "<task>"\` for every new implementation task.
|
|
313
|
+
- Use \`cortex note "<title>" "<details>"\` when an important decision is made.
|
|
314
|
+
- Run \`cortex update\` before completing substantial code changes.
|
|
315
|
+
- Use \`cortex plan\` to inspect current progress and next command.
|
|
316
|
+
- If background sync is enabled, check with \`cortex watch status\`.`;
|
|
317
|
+
const changed = upsertSectionByMarkers(agentsPath, startMarker, endMarker, section);
|
|
318
|
+
return { path: agentsPath, changed };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function installAssistantHelpers(targetDir) {
|
|
322
|
+
const claude = installClaudeCommands(targetDir);
|
|
323
|
+
const codex = installCodexAgentsSection(targetDir);
|
|
324
|
+
return { claude, codex };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function runCommand(command, args, cwd) {
|
|
328
|
+
return new Promise((resolve, reject) => {
|
|
329
|
+
const child = spawn(command, args, {
|
|
330
|
+
cwd,
|
|
331
|
+
stdio: "inherit",
|
|
332
|
+
env: process.env
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
child.on("error", reject);
|
|
336
|
+
child.on("exit", (code) => {
|
|
337
|
+
if (code === 0) {
|
|
338
|
+
resolve();
|
|
339
|
+
} else {
|
|
340
|
+
reject(new Error(`${command} exited with code ${code ?? "unknown"}`));
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function runCommandResult(command, args, cwd, stdio = "ignore") {
|
|
347
|
+
return new Promise((resolve) => {
|
|
348
|
+
let done = false;
|
|
349
|
+
const finish = (result) => {
|
|
350
|
+
if (done) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
done = true;
|
|
354
|
+
resolve(result);
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const child = spawn(command, args, {
|
|
358
|
+
cwd,
|
|
359
|
+
stdio,
|
|
360
|
+
env: process.env
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
child.on("error", (error) => finish({ ok: false, code: null, error }));
|
|
364
|
+
child.on("exit", (code) => finish({ ok: code === 0, code, error: null }));
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function toErrorMessage(error) {
|
|
369
|
+
return error instanceof Error ? error.message : String(error);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function commandExists(command, cwd) {
|
|
373
|
+
const result = await runCommandResult(command, ["--version"], cwd, "ignore");
|
|
374
|
+
return result.ok;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function normalizeName(value) {
|
|
378
|
+
const cleaned = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
379
|
+
return cleaned || "repo";
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function connectCodex(targetDir, serverEntry) {
|
|
383
|
+
if (!(await commandExists("codex", targetDir))) {
|
|
384
|
+
console.log("[cortex] codex CLI not found, skipping Codex MCP registration");
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const repoName = normalizeName(path.basename(targetDir));
|
|
389
|
+
const serverName = `cortex-${repoName}`;
|
|
390
|
+
await runCommandResult("codex", ["mcp", "remove", serverName], targetDir, "ignore");
|
|
391
|
+
await runCommand("codex", ["mcp", "add", serverName, "--", "node", serverEntry], targetDir);
|
|
392
|
+
console.log(`[cortex] connected Codex MCP server: ${serverName}`);
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async function connectClaude(targetDir) {
|
|
397
|
+
if (!(await commandExists("claude", targetDir))) {
|
|
398
|
+
console.log("[cortex] claude CLI not found, skipping Claude Code MCP registration");
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const serverName = "cortex";
|
|
403
|
+
const projectServerEntry = path.join("mcp", "dist", "server.js");
|
|
404
|
+
await runCommandResult("claude", ["mcp", "remove", "-s", "project", serverName], targetDir, "ignore");
|
|
405
|
+
await runCommand(
|
|
406
|
+
"claude",
|
|
407
|
+
["mcp", "add", "-s", "project", serverName, "--", "node", projectServerEntry],
|
|
408
|
+
targetDir
|
|
409
|
+
);
|
|
410
|
+
console.log("[cortex] connected Claude Code MCP server: cortex (project scope)");
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function connectMcpClients(targetDir, options = {}) {
|
|
415
|
+
const { skipBuild = false } = options;
|
|
416
|
+
const mcpDir = path.join(targetDir, "mcp");
|
|
417
|
+
const packageJson = path.join(mcpDir, "package.json");
|
|
418
|
+
const nodeModules = path.join(mcpDir, "node_modules");
|
|
419
|
+
const serverEntry = path.join(mcpDir, "dist", "server.js");
|
|
420
|
+
|
|
421
|
+
if (!fs.existsSync(packageJson)) {
|
|
422
|
+
throw new Error(`Missing ${packageJson}. Run 'cortex init' first.`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (!skipBuild && fs.existsSync(nodeModules)) {
|
|
426
|
+
try {
|
|
427
|
+
await runCommand("npm", ["--prefix", mcpDir, "run", "build", "--silent"], targetDir);
|
|
428
|
+
} catch (error) {
|
|
429
|
+
console.log(`[cortex] MCP build failed, continuing with existing dist output: ${toErrorMessage(error)}`);
|
|
430
|
+
}
|
|
431
|
+
} else if (!skipBuild) {
|
|
432
|
+
console.log("[cortex] mcp/node_modules not found, skipping build (run cortex bootstrap first)");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (!fs.existsSync(serverEntry)) {
|
|
436
|
+
console.log(`[cortex] warning: ${serverEntry} not found yet; run cortex bootstrap before first MCP call`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
let connected = 0;
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
if (await connectCodex(targetDir, serverEntry)) {
|
|
443
|
+
connected += 1;
|
|
444
|
+
}
|
|
445
|
+
} catch (error) {
|
|
446
|
+
console.log(`[cortex] failed to connect Codex MCP: ${toErrorMessage(error)}`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
try {
|
|
450
|
+
if (await connectClaude(targetDir)) {
|
|
451
|
+
connected += 1;
|
|
452
|
+
}
|
|
453
|
+
} catch (error) {
|
|
454
|
+
console.log(`[cortex] failed to connect Claude MCP: ${toErrorMessage(error)}`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (connected === 0) {
|
|
458
|
+
console.log("[cortex] no MCP clients connected");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return connected;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function ensureProjectInitialized(targetDir) {
|
|
465
|
+
const mcpPackageJson = path.join(targetDir, "mcp", "package.json");
|
|
466
|
+
if (!fs.existsSync(mcpPackageJson)) {
|
|
467
|
+
throw new Error(`Missing ${mcpPackageJson}. Run 'cortex init --bootstrap' first.`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function runContextCommand(cwd, contextArgs) {
|
|
472
|
+
const contextScript = path.join(cwd, "scripts", "context.sh");
|
|
473
|
+
if (!fs.existsSync(contextScript)) {
|
|
474
|
+
throw new Error(`Missing ${contextScript}. Run 'cortex init' first.`);
|
|
475
|
+
}
|
|
476
|
+
await runCommand("bash", [contextScript, ...contextArgs], cwd);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function markPlanEvent(targetDir, eventName) {
|
|
480
|
+
const planScript = path.join(targetDir, "scripts", "plan-state.sh");
|
|
481
|
+
if (!fs.existsSync(planScript)) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const result = await runCommandResult("bash", [planScript, "event", eventName], targetDir, "ignore");
|
|
486
|
+
if (!result.ok) {
|
|
487
|
+
console.log(`[cortex] warning: failed to update automatic plan state for event '${eventName}'`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function run() {
|
|
492
|
+
const cliVersion = readCliVersion();
|
|
493
|
+
process.env.CORTEX_CLI_VERSION = cliVersion;
|
|
494
|
+
|
|
495
|
+
const [rawCommand, ...rest] = process.argv.slice(2);
|
|
496
|
+
const command = rawCommand ?? "help";
|
|
497
|
+
|
|
498
|
+
if (command === "version" || command === "--version" || command === "-V") {
|
|
499
|
+
console.log(cliVersion);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
504
|
+
printHelp();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (command === "init") {
|
|
509
|
+
ensureScaffoldExists();
|
|
510
|
+
const { target, force, bootstrap, connect, watch } = parseInitArgs(rest);
|
|
511
|
+
printBanner("Cortex initializes repo-scoped context for AI coding agents.");
|
|
512
|
+
fs.mkdirSync(target, { recursive: true });
|
|
513
|
+
installScaffold(target, force);
|
|
514
|
+
const helpers = installAssistantHelpers(target);
|
|
515
|
+
await markPlanEvent(target, "init");
|
|
516
|
+
|
|
517
|
+
console.log(`[cortex] initialized in ${target}`);
|
|
518
|
+
console.log("[cortex] scaffold copied: .context/, scripts/, mcp/, .githooks/, docs/");
|
|
519
|
+
console.log(`[cortex] Claude commands ready: /note /todo /plan /context-update (${helpers.claude.total} files)`);
|
|
520
|
+
if (helpers.codex.changed) {
|
|
521
|
+
console.log("[cortex] Codex workflow instructions added to AGENTS.md");
|
|
522
|
+
} else {
|
|
523
|
+
console.log("[cortex] Codex workflow instructions already up to date in AGENTS.md");
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (bootstrap) {
|
|
527
|
+
console.log("[cortex] bootstrap: install deps -> ingest -> embeddings -> graph");
|
|
528
|
+
} else {
|
|
529
|
+
console.log("[cortex] next: cortex bootstrap");
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (connect) {
|
|
533
|
+
console.log("[cortex] MCP connect: Codex + Claude Code (if CLIs are installed)");
|
|
534
|
+
} else {
|
|
535
|
+
console.log("[cortex] MCP connect skipped (--no-connect)");
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (watch) {
|
|
539
|
+
if (bootstrap) {
|
|
540
|
+
console.log("[cortex] background sync: cortex watch start");
|
|
541
|
+
} else {
|
|
542
|
+
console.log("[cortex] background sync pending: run cortex watch start after bootstrap");
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
console.log("[cortex] background sync skipped (--no-watch)");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (!bootstrap) {
|
|
549
|
+
console.log("");
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (bootstrap) {
|
|
553
|
+
await runContextCommand(target, ["bootstrap"]);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (connect) {
|
|
557
|
+
const connected = await connectMcpClients(target);
|
|
558
|
+
if (connected > 0) {
|
|
559
|
+
await markPlanEvent(target, "connect");
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (watch && bootstrap) {
|
|
564
|
+
await runContextCommand(target, ["watch", "start"]);
|
|
565
|
+
}
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (command === "connect") {
|
|
570
|
+
const { target, skipBuild } = parseConnectArgs(rest);
|
|
571
|
+
ensureProjectInitialized(target);
|
|
572
|
+
const helpers = installAssistantHelpers(target);
|
|
573
|
+
if (helpers.claude.changed > 0 || helpers.codex.changed) {
|
|
574
|
+
console.log("[cortex] assistant helpers updated (.claude/commands + AGENTS.md)");
|
|
575
|
+
}
|
|
576
|
+
const connected = await connectMcpClients(target, { skipBuild });
|
|
577
|
+
if (connected > 0) {
|
|
578
|
+
await markPlanEvent(target, "connect");
|
|
579
|
+
}
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (command === "mcp") {
|
|
584
|
+
const target = process.env.CORTEX_PROJECT_ROOT
|
|
585
|
+
? path.resolve(process.env.CORTEX_PROJECT_ROOT)
|
|
586
|
+
: process.cwd();
|
|
587
|
+
ensureProjectInitialized(target);
|
|
588
|
+
const serverEntry = path.join(target, "mcp", "dist", "server.js");
|
|
589
|
+
if (!fs.existsSync(serverEntry)) {
|
|
590
|
+
throw new Error(`Missing ${serverEntry}. Run 'cortex bootstrap' in ${target} first.`);
|
|
591
|
+
}
|
|
592
|
+
process.stderr.write(`[cortex] starting MCP stdio server from ${serverEntry}\n`);
|
|
593
|
+
await runCommand("node", [serverEntry], target);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const passthrough = new Set([
|
|
598
|
+
"bootstrap",
|
|
599
|
+
"update",
|
|
600
|
+
"status",
|
|
601
|
+
"ingest",
|
|
602
|
+
"embed",
|
|
603
|
+
"graph-load",
|
|
604
|
+
"watch",
|
|
605
|
+
"note",
|
|
606
|
+
"plan",
|
|
607
|
+
"todo",
|
|
608
|
+
"refresh"
|
|
609
|
+
]);
|
|
610
|
+
|
|
611
|
+
if (!passthrough.has(command)) {
|
|
612
|
+
throw new Error(`Unknown command: ${command}`);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
await runContextCommand(process.cwd(), [command, ...rest]);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
run().catch((error) => {
|
|
619
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
620
|
+
process.exit(1);
|
|
621
|
+
});
|