@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 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
- run().catch((error) => {
658
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
659
- process.exit(1);
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.0",
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
- HTTP_CODE=$(curl -so /dev/null -w '%{http_code}' --max-time 5 -X POST \
228
- -H "Content-Type: application/json" \
229
- -d '{}' \
230
- "$TELEMETRY_ENDPOINT" 2>/dev/null | tail -c 3 || echo "000")
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
- pass "Telemetry: endpoint reachable (HTTP ${HTTP_CODE})"
235
- elif [[ "$HTTP_CODE" == "401" ]]; then
236
- pass "Telemetry: endpoint reachable (auth required — expected)"
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
- POLICY_HTTP=$(curl -so /dev/null -w '%{http_code}' --max-time 5 "$POLICY_ENDPOINT" 2>/dev/null | tail -c 3 || echo "000")
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] ]] || [[ "$POLICY_HTTP" == "401" ]]; then
265
- pass "Policy: endpoint reachable (HTTP ${POLICY_HTTP})"
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