@danielblomma/cortex-mcp 1.3.0 → 1.3.2
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/cortex.mjs +92 -5
- package/package.json +1 -1
- package/scaffold/scripts/doctor.sh +64 -10
package/bin/cortex.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { normalizeProjectRoot } from "./wsl.mjs";
|
|
7
7
|
|
|
@@ -490,6 +490,78 @@ function canAutoInitialize(targetDir) {
|
|
|
490
490
|
return scaffoldPaths.every((entryPath) => !fs.existsSync(entryPath));
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
+
function isScaffoldOutOfDate(targetDir) {
|
|
494
|
+
const contextScript = path.join(targetDir, "scripts", "context.sh");
|
|
495
|
+
if (!fs.existsSync(contextScript)) {
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
const doctorScript = path.join(targetDir, "scripts", "doctor.sh");
|
|
499
|
+
if (!fs.existsSync(doctorScript)) {
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
const mcpPackage = path.join(targetDir, "mcp", "package.json");
|
|
503
|
+
if (!fs.existsSync(mcpPackage)) {
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
const contents = fs.readFileSync(contextScript, "utf8");
|
|
508
|
+
if (!/\bdoctor\)\s*\n/.test(contents)) {
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
} catch {
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async function confirmPrompt(message) {
|
|
518
|
+
const { createInterface } = await import("node:readline/promises");
|
|
519
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
520
|
+
try {
|
|
521
|
+
const answer = (await rl.question(message)).trim().toLowerCase();
|
|
522
|
+
return answer === "y" || answer === "yes";
|
|
523
|
+
} finally {
|
|
524
|
+
rl.close();
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
async function maybeMigrateScaffold(targetDir, command) {
|
|
529
|
+
if (!isScaffoldOutOfDate(targetDir)) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const autoYes = isTruthyEnv(process.env.CORTEX_AUTO_MIGRATE);
|
|
534
|
+
const interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
535
|
+
|
|
536
|
+
console.error(
|
|
537
|
+
`[cortex] scaffold in ${targetDir} is out of date ` +
|
|
538
|
+
`(missing scripts/doctor.sh, mcp/package.json, or doctor subcommand in context.sh).`
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
let proceed = autoYes;
|
|
542
|
+
if (!autoYes) {
|
|
543
|
+
if (!interactive) {
|
|
544
|
+
throw new Error(
|
|
545
|
+
`Cortex CLI ${process.env.CORTEX_CLI_VERSION ?? ""} needs an updated scaffold to run '${command}'. ` +
|
|
546
|
+
`Run 'cortex init --bootstrap' to upgrade, or re-run with CORTEX_AUTO_MIGRATE=true.`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
proceed = await confirmPrompt("[cortex] Upgrade scaffold now (runs 'cortex init --bootstrap')? [y/N] ");
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (!proceed) {
|
|
553
|
+
throw new Error("Scaffold upgrade declined. Run 'cortex init --bootstrap' manually to continue.");
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
console.error(`[cortex] migrating scaffold in ${targetDir}`);
|
|
557
|
+
ensureScaffoldExists();
|
|
558
|
+
installScaffold(targetDir, true);
|
|
559
|
+
installAssistantHelpers(targetDir);
|
|
560
|
+
await maybeInstallGitHooks(targetDir);
|
|
561
|
+
await runContextCommand(targetDir, ["bootstrap"]);
|
|
562
|
+
console.error(`[cortex] scaffold upgraded; continuing with '${command}'`);
|
|
563
|
+
}
|
|
564
|
+
|
|
493
565
|
async function ensureProjectInitializedForMcp(targetDir) {
|
|
494
566
|
const mcpPackageJson = path.join(targetDir, "mcp", "package.json");
|
|
495
567
|
const serverEntry = path.join(targetDir, "mcp", "dist", "server.js");
|
|
@@ -498,6 +570,13 @@ async function ensureProjectInitializedForMcp(targetDir) {
|
|
|
498
570
|
return;
|
|
499
571
|
}
|
|
500
572
|
|
|
573
|
+
if (isScaffoldOutOfDate(targetDir)) {
|
|
574
|
+
await maybeMigrateScaffold(targetDir, "mcp");
|
|
575
|
+
if (fs.existsSync(mcpPackageJson) && fs.existsSync(serverEntry)) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
501
580
|
if (!isTruthyEnv(process.env.CORTEX_AUTO_BOOTSTRAP_ON_MCP)) {
|
|
502
581
|
ensureProjectInitialized(targetDir);
|
|
503
582
|
return;
|
|
@@ -651,10 +730,18 @@ async function run() {
|
|
|
651
730
|
throw new Error(`Unknown command: ${command}`);
|
|
652
731
|
}
|
|
653
732
|
|
|
733
|
+
await maybeMigrateScaffold(process.cwd(), command);
|
|
654
734
|
await runContextCommand(process.cwd(), [command, ...rest]);
|
|
655
735
|
}
|
|
656
736
|
|
|
657
|
-
|
|
658
|
-
process.
|
|
659
|
-
|
|
660
|
-
|
|
737
|
+
const invokedAsScript =
|
|
738
|
+
process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
739
|
+
|
|
740
|
+
if (invokedAsScript) {
|
|
741
|
+
run().catch((error) => {
|
|
742
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
743
|
+
process.exit(1);
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
export { isScaffoldOutOfDate };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danielblomma/cortex-mcp",
|
|
3
3
|
"mcpName": "io.github.DanielBlomma/cortex",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.2",
|
|
5
5
|
"description": "Local, repo-scoped context platform for coding assistants. Semantic search, graph relationships, and architectural rule context.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"author": "Daniel Blomma",
|
|
@@ -221,19 +221,59 @@ if [[ -n "$ENTERPRISE_CONFIG" ]]; then
|
|
|
221
221
|
console.log(fields["policy.endpoint"] || "");
|
|
222
222
|
' "$ENTERPRISE_CONFIG" 2>/dev/null || echo "")
|
|
223
223
|
|
|
224
|
+
TELEMETRY_API_KEY=$(node -e '
|
|
225
|
+
const fs = require("node:fs");
|
|
226
|
+
const raw = fs.readFileSync(process.argv[1], "utf8");
|
|
227
|
+
let section = "", fields = {};
|
|
228
|
+
for (const line of raw.split("\n")) {
|
|
229
|
+
const t = line.trimEnd();
|
|
230
|
+
if (!t || t.startsWith("#")) continue;
|
|
231
|
+
const sm = t.match(/^(\w+):\s*$/);
|
|
232
|
+
if (sm) { section = sm[1]; continue; }
|
|
233
|
+
const kv = t.match(/^\s+(\w+):\s*(.+?)\s*$/);
|
|
234
|
+
if (kv && section) fields[section + "." + kv[1]] = kv[2].replace(/^["\x27]|["\x27]$/g, "");
|
|
235
|
+
}
|
|
236
|
+
console.log(fields["telemetry.api_key"] || "");
|
|
237
|
+
' "$ENTERPRISE_CONFIG" 2>/dev/null || echo "")
|
|
238
|
+
|
|
239
|
+
POLICY_API_KEY=$(node -e '
|
|
240
|
+
const fs = require("node:fs");
|
|
241
|
+
const raw = fs.readFileSync(process.argv[1], "utf8");
|
|
242
|
+
let section = "", fields = {};
|
|
243
|
+
for (const line of raw.split("\n")) {
|
|
244
|
+
const t = line.trimEnd();
|
|
245
|
+
if (!t || t.startsWith("#")) continue;
|
|
246
|
+
const sm = t.match(/^(\w+):\s*$/);
|
|
247
|
+
if (sm) { section = sm[1]; continue; }
|
|
248
|
+
const kv = t.match(/^\s+(\w+):\s*(.+?)\s*$/);
|
|
249
|
+
if (kv && section) fields[section + "." + kv[1]] = kv[2].replace(/^["\x27]|["\x27]$/g, "");
|
|
250
|
+
}
|
|
251
|
+
console.log(fields["policy.api_key"] || "");
|
|
252
|
+
' "$ENTERPRISE_CONFIG" 2>/dev/null || echo "")
|
|
253
|
+
|
|
224
254
|
# Telemetry
|
|
225
255
|
if [[ -n "$TELEMETRY_ENDPOINT" ]]; then
|
|
226
256
|
pass "Telemetry: endpoint configured"
|
|
227
|
-
|
|
228
|
-
-H "Content-Type: application/json"
|
|
229
|
-
|
|
230
|
-
|
|
257
|
+
TELEMETRY_CURL_ARGS=(-so /dev/null -w '%{http_code}' --max-time 5 -X POST \
|
|
258
|
+
-H "Content-Type: application/json" -d '{}')
|
|
259
|
+
if [[ -n "$TELEMETRY_API_KEY" ]]; then
|
|
260
|
+
TELEMETRY_CURL_ARGS+=(-H "Authorization: Bearer ${TELEMETRY_API_KEY}")
|
|
261
|
+
fi
|
|
262
|
+
HTTP_CODE=$(curl "${TELEMETRY_CURL_ARGS[@]}" "$TELEMETRY_ENDPOINT" 2>/dev/null | tail -c 3 || echo "000")
|
|
231
263
|
if [[ "$HTTP_CODE" == "000" ]]; then
|
|
232
264
|
fail "Telemetry: endpoint not reachable (timeout/DNS)"
|
|
233
265
|
elif [[ "$HTTP_CODE" =~ ^[23] ]]; then
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
266
|
+
if [[ -n "$TELEMETRY_API_KEY" ]]; then
|
|
267
|
+
pass "Telemetry: endpoint authenticated (HTTP ${HTTP_CODE})"
|
|
268
|
+
else
|
|
269
|
+
pass "Telemetry: endpoint reachable (HTTP ${HTTP_CODE})"
|
|
270
|
+
fi
|
|
271
|
+
elif [[ "$HTTP_CODE" == "401" || "$HTTP_CODE" == "403" ]]; then
|
|
272
|
+
if [[ -n "$TELEMETRY_API_KEY" ]]; then
|
|
273
|
+
fail "Telemetry: auth rejected (HTTP ${HTTP_CODE}) — check telemetry.api_key in enterprise.yaml"
|
|
274
|
+
else
|
|
275
|
+
pass "Telemetry: endpoint reachable (auth required — expected)"
|
|
276
|
+
fi
|
|
237
277
|
else
|
|
238
278
|
warn "Telemetry: endpoint returned HTTP ${HTTP_CODE}"
|
|
239
279
|
fi
|
|
@@ -258,11 +298,25 @@ if [[ -n "$ENTERPRISE_CONFIG" ]]; then
|
|
|
258
298
|
fi
|
|
259
299
|
|
|
260
300
|
if [[ -n "$POLICY_ENDPOINT" ]]; then
|
|
261
|
-
|
|
301
|
+
POLICY_CURL_ARGS=(-so /dev/null -w '%{http_code}' --max-time 5)
|
|
302
|
+
if [[ -n "$POLICY_API_KEY" ]]; then
|
|
303
|
+
POLICY_CURL_ARGS+=(-H "Authorization: Bearer ${POLICY_API_KEY}")
|
|
304
|
+
fi
|
|
305
|
+
POLICY_HTTP=$(curl "${POLICY_CURL_ARGS[@]}" "$POLICY_ENDPOINT" 2>/dev/null | tail -c 3 || echo "000")
|
|
262
306
|
if [[ "$POLICY_HTTP" == "000" ]]; then
|
|
263
307
|
fail "Policy: endpoint not reachable (timeout/DNS)"
|
|
264
|
-
elif [[ "$POLICY_HTTP" =~ ^[23] ]]
|
|
265
|
-
|
|
308
|
+
elif [[ "$POLICY_HTTP" =~ ^[23] ]]; then
|
|
309
|
+
if [[ -n "$POLICY_API_KEY" ]]; then
|
|
310
|
+
pass "Policy: endpoint authenticated (HTTP ${POLICY_HTTP})"
|
|
311
|
+
else
|
|
312
|
+
pass "Policy: endpoint reachable (HTTP ${POLICY_HTTP})"
|
|
313
|
+
fi
|
|
314
|
+
elif [[ "$POLICY_HTTP" == "401" || "$POLICY_HTTP" == "403" ]]; then
|
|
315
|
+
if [[ -n "$POLICY_API_KEY" ]]; then
|
|
316
|
+
fail "Policy: auth rejected (HTTP ${POLICY_HTTP}) — check policy.api_key in enterprise.yaml"
|
|
317
|
+
else
|
|
318
|
+
pass "Policy: endpoint reachable (auth required — expected)"
|
|
319
|
+
fi
|
|
266
320
|
else
|
|
267
321
|
warn "Policy: endpoint returned HTTP ${POLICY_HTTP}"
|
|
268
322
|
fi
|