md4ai 0.9.4 → 0.9.6

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.
Files changed (2) hide show
  1. package/dist/index.bundled.js +635 -267
  2. package/package.json +1 -1
@@ -89,6 +89,13 @@ async function loadCredentials() {
89
89
  return null;
90
90
  }
91
91
  }
92
+ async function mergeCredentials(partial) {
93
+ await ensureConfigDir();
94
+ const existing = await loadCredentials();
95
+ const merged = { ...existing, ...partial };
96
+ await writeFile(credentialsPath, JSON.stringify(merged, null, 2), "utf-8");
97
+ await chmod(credentialsPath, 384);
98
+ }
92
99
  async function clearCredentials() {
93
100
  try {
94
101
  await writeFile(credentialsPath, "{}", "utf-8");
@@ -226,6 +233,7 @@ import { resolve as resolve2, dirname, join as join2 } from "node:path";
226
233
  import { homedir as homedir2 } from "node:os";
227
234
  async function parseFileReferences(filePath, projectRoot) {
228
235
  const refs = [];
236
+ const brokenRefs = [];
229
237
  const content = await readFile2(filePath, "utf-8");
230
238
  const relPath = filePath.startsWith(projectRoot) ? filePath.slice(projectRoot.length + 1) : filePath;
231
239
  const markdownLinkPattern = /\[.*?\]\(([^)]+)\)/g;
@@ -247,20 +255,34 @@ async function parseFileReferences(filePath, projectRoot) {
247
255
  const resolved = resolveTarget(target, filePath, projectRoot);
248
256
  if (resolved && existsSync2(resolved)) {
249
257
  addRef(refs, relPath, resolved, projectRoot);
258
+ } else if (resolved) {
259
+ const targetRel = resolved.startsWith(projectRoot) ? resolved.slice(projectRoot.length + 1) : resolved;
260
+ if (targetRel !== relPath) {
261
+ brokenRefs.push({ from: relPath, to: targetRel, rawRef: target });
262
+ }
250
263
  }
251
264
  }
252
265
  }
253
266
  if (filePath.endsWith(".json")) {
254
- parseJsonPathReferences(content, relPath, projectRoot, refs);
267
+ parseJsonPathReferences(content, relPath, projectRoot, refs, brokenRefs);
255
268
  }
256
269
  const seen = /* @__PURE__ */ new Set();
257
- return refs.filter((r) => {
270
+ const dedupedRefs = refs.filter((r) => {
258
271
  const key = `${r.from}->${r.to}`;
259
272
  if (seen.has(key))
260
273
  return false;
261
274
  seen.add(key);
262
275
  return true;
263
276
  });
277
+ const brokenSeen = /* @__PURE__ */ new Set();
278
+ const dedupedBroken = brokenRefs.filter((r) => {
279
+ const key = `${r.from}->${r.to}`;
280
+ if (brokenSeen.has(key))
281
+ return false;
282
+ brokenSeen.add(key);
283
+ return true;
284
+ });
285
+ return { refs: dedupedRefs, brokenRefs: dedupedBroken };
264
286
  }
265
287
  function resolveTarget(target, sourceFilePath, projectRoot) {
266
288
  if (target.startsWith("~")) {
@@ -280,7 +302,7 @@ function addRef(refs, fromRel, resolvedAbsolute, projectRoot) {
280
302
  refs.push({ from: fromRel, to: targetRel });
281
303
  }
282
304
  }
283
- function parseJsonPathReferences(content, fromRel, projectRoot, refs) {
305
+ function parseJsonPathReferences(content, fromRel, projectRoot, refs, brokenRefs) {
284
306
  let parsed;
285
307
  try {
286
308
  parsed = JSON.parse(content);
@@ -297,6 +319,8 @@ function parseJsonPathReferences(content, fromRel, projectRoot, refs) {
297
319
  const resolved = join2(projectRoot, relTarget);
298
320
  if (existsSync2(resolved)) {
299
321
  addRef(refs, fromRel, resolved, projectRoot);
322
+ } else {
323
+ brokenRefs.push({ from: fromRel, to: relTarget, rawRef: relTarget });
300
324
  }
301
325
  }
302
326
  }
@@ -309,6 +333,8 @@ function parseJsonPathReferences(content, fromRel, projectRoot, refs) {
309
333
  const resolved = join2(projectRoot, target);
310
334
  if (existsSync2(resolved)) {
311
335
  addRef(refs, fromRel, resolved, projectRoot);
336
+ } else {
337
+ brokenRefs.push({ from: fromRel, to: target, rawRef: target });
312
338
  }
313
339
  }
314
340
  });
@@ -812,9 +838,9 @@ async function detectFromMcpSettings(projectRoot) {
812
838
  continue;
813
839
  seen.add(serverName);
814
840
  let version = null;
815
- const config = mcpServers[serverName];
816
- if (config?.command && typeof config.command === "string") {
817
- const args = config.args ?? [];
841
+ const config2 = mcpServers[serverName];
842
+ if (config2?.command && typeof config2.command === "string") {
843
+ const args = config2.args ?? [];
818
844
  for (const arg of args) {
819
845
  const versionMatch = arg.match(/@(\d+\.\d+[^\s]*?)$/);
820
846
  if (versionMatch) {
@@ -849,16 +875,110 @@ var init_tooling_detector = __esm({
849
875
  }
850
876
  });
851
877
 
878
+ // dist/vercel/auth.js
879
+ import { readFile as readFile5 } from "node:fs/promises";
880
+ import { join as join7 } from "node:path";
881
+ import { homedir as homedir5 } from "node:os";
882
+ async function resolveVercelToken() {
883
+ const creds = await loadCredentials();
884
+ if (creds?.vercelToken)
885
+ return creds.vercelToken;
886
+ const possiblePaths = [
887
+ join7(homedir5(), ".config", "com.vercel.cli", "auth.json"),
888
+ join7(homedir5(), ".local", "share", "com.vercel.cli", "auth.json")
889
+ ];
890
+ for (const authPath of possiblePaths) {
891
+ try {
892
+ const data = await readFile5(authPath, "utf-8");
893
+ const parsed = JSON.parse(data);
894
+ if (parsed.token)
895
+ return parsed.token;
896
+ } catch {
897
+ }
898
+ }
899
+ return null;
900
+ }
901
+ var init_auth2 = __esm({
902
+ "dist/vercel/auth.js"() {
903
+ "use strict";
904
+ init_config();
905
+ }
906
+ });
907
+
908
+ // dist/vercel/discover-projects.js
909
+ import { readFile as readFile6, glob } from "node:fs/promises";
910
+ import { join as join8, dirname as dirname2, relative } from "node:path";
911
+ async function discoverVercelProjects(projectRoot) {
912
+ const found = /* @__PURE__ */ new Map();
913
+ for await (const filePath of glob(join8(projectRoot, "**/.vercel/project.json"))) {
914
+ try {
915
+ const data = await readFile6(filePath, "utf-8");
916
+ const parsed = JSON.parse(data);
917
+ if (!parsed.projectId || !parsed.orgId)
918
+ continue;
919
+ const vercelDir = dirname2(filePath);
920
+ const parentDir = dirname2(vercelDir);
921
+ const relDir = relative(projectRoot, parentDir);
922
+ const label = parsed.projectName || (relDir === "" ? "root" : relDir.split("/").pop() || "unknown");
923
+ const existing = found.get(parsed.projectId);
924
+ if (!existing || relDir.length > 0) {
925
+ found.set(parsed.projectId, {
926
+ projectId: parsed.projectId,
927
+ orgId: parsed.orgId,
928
+ projectName: label
929
+ });
930
+ }
931
+ } catch {
932
+ }
933
+ }
934
+ return [...found.values()].sort((a, b) => a.projectName.localeCompare(b.projectName));
935
+ }
936
+ var init_discover_projects = __esm({
937
+ "dist/vercel/discover-projects.js"() {
938
+ "use strict";
939
+ }
940
+ });
941
+
942
+ // dist/vercel/fetch-env-vars.js
943
+ async function fetchVercelEnvVars(projectId, orgId, token) {
944
+ const url = new URL(`https://api.vercel.com/v9/projects/${projectId}/env`);
945
+ url.searchParams.set("teamId", orgId);
946
+ const res = await fetch(url.toString(), {
947
+ headers: { Authorization: `Bearer ${token}` }
948
+ });
949
+ if (!res.ok) {
950
+ const body = await res.text().catch(() => "");
951
+ throw new Error(`Vercel API ${res.status}: ${body.slice(0, 200)}`);
952
+ }
953
+ const data = await res.json();
954
+ const envs = data.envs ?? [];
955
+ return envs.map((e) => ({
956
+ key: e.key,
957
+ targets: e.target ?? ["production", "preview", "development"],
958
+ type: e.type ?? "plain",
959
+ inManifest: false
960
+ // populated later by the scanner
961
+ }));
962
+ }
963
+ var init_fetch_env_vars = __esm({
964
+ "dist/vercel/fetch-env-vars.js"() {
965
+ "use strict";
966
+ }
967
+ });
968
+
852
969
  // dist/scanner/env-manifest-scanner.js
853
- import { readFile as readFile5, glob } from "node:fs/promises";
854
- import { join as join7, relative } from "node:path";
970
+ import { readFile as readFile7, glob as glob2 } from "node:fs/promises";
971
+ import { execFile } from "node:child_process";
972
+ import { promisify } from "node:util";
973
+ import { join as join9, relative as relative2 } from "node:path";
855
974
  import { existsSync as existsSync5 } from "node:fs";
975
+ import chalk8 from "chalk";
856
976
  async function scanEnvManifest(projectRoot) {
857
- const manifestFullPath = join7(projectRoot, MANIFEST_PATH);
977
+ const manifestFullPath = join9(projectRoot, MANIFEST_PATH);
858
978
  if (!existsSync5(manifestFullPath)) {
859
979
  return null;
860
980
  }
861
- const manifestContent = await readFile5(manifestFullPath, "utf-8");
981
+ const manifestContent = await readFile7(manifestFullPath, "utf-8");
862
982
  const apps = parseApps(manifestContent);
863
983
  const variables = parseVariables(manifestContent);
864
984
  const localPresence = await checkLocalEnvFiles(projectRoot, apps);
@@ -870,6 +990,8 @@ async function scanEnvManifest(projectRoot) {
870
990
  }
871
991
  const workflowRefs = await parseWorkflowSecrets(projectRoot);
872
992
  const drift = computeDrift(variables, apps, localPresence, workflowRefs);
993
+ const vercelDiscovered = await checkVercelEnvVars(projectRoot, variables);
994
+ const githubRepoSlug = await checkGitHubSecrets(projectRoot, variables);
873
995
  return {
874
996
  manifestFound: true,
875
997
  manifestPath: MANIFEST_PATH,
@@ -877,9 +999,98 @@ async function scanEnvManifest(projectRoot) {
877
999
  variables,
878
1000
  drift,
879
1001
  workflowRefs,
1002
+ vercelDiscovered: vercelDiscovered ?? void 0,
1003
+ githubRepoSlug: githubRepoSlug ?? void 0,
880
1004
  checkedAt: (/* @__PURE__ */ new Date()).toISOString()
881
1005
  };
882
1006
  }
1007
+ async function checkVercelEnvVars(projectRoot, variables) {
1008
+ const token = await resolveVercelToken();
1009
+ if (!token) {
1010
+ console.log(chalk8.dim(" Vercel checks skipped (no token configured)"));
1011
+ return null;
1012
+ }
1013
+ const projects = await discoverVercelProjects(projectRoot);
1014
+ if (projects.length === 0)
1015
+ return null;
1016
+ console.log(chalk8.dim(` Checking ${projects.length} Vercel project(s)...`));
1017
+ const discovered = [];
1018
+ const manifestVarNames = new Set(variables.map((v) => v.name));
1019
+ for (const proj of projects) {
1020
+ try {
1021
+ const vars = await fetchVercelEnvVars(proj.projectId, proj.orgId, token);
1022
+ for (const v of vars) {
1023
+ v.inManifest = manifestVarNames.has(v.key);
1024
+ }
1025
+ discovered.push({
1026
+ projectId: proj.projectId,
1027
+ projectName: proj.projectName,
1028
+ orgId: proj.orgId,
1029
+ vars
1030
+ });
1031
+ const vercelKeySet = new Set(vars.map((v) => v.key));
1032
+ for (const mv of variables) {
1033
+ if (!mv.vercelStatus)
1034
+ mv.vercelStatus = {};
1035
+ mv.vercelStatus[proj.projectName] = vercelKeySet.has(mv.name) ? "present" : "missing";
1036
+ }
1037
+ console.log(chalk8.dim(` ${proj.projectName}: ${vars.length} var(s)`));
1038
+ } catch (err) {
1039
+ console.log(chalk8.yellow(` ${proj.projectName}: ${err instanceof Error ? err.message : "API error"}`));
1040
+ }
1041
+ }
1042
+ return discovered.length > 0 ? discovered : null;
1043
+ }
1044
+ async function detectGitHubRepoSlug(projectRoot) {
1045
+ try {
1046
+ const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], { cwd: projectRoot });
1047
+ const url = stdout.trim();
1048
+ const sshMatch = url.match(/github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/);
1049
+ if (sshMatch)
1050
+ return sshMatch[1];
1051
+ return null;
1052
+ } catch {
1053
+ return null;
1054
+ }
1055
+ }
1056
+ async function listGitHubSecretNames(repoSlug) {
1057
+ try {
1058
+ const { stdout } = await execFileAsync("gh", [
1059
+ "secret",
1060
+ "list",
1061
+ "--repo",
1062
+ repoSlug,
1063
+ "--json",
1064
+ "name",
1065
+ "-q",
1066
+ ".[].name"
1067
+ ], { timeout: 15e3 });
1068
+ const names = stdout.trim().split("\n").filter(Boolean);
1069
+ return names;
1070
+ } catch {
1071
+ return null;
1072
+ }
1073
+ }
1074
+ async function checkGitHubSecrets(projectRoot, variables) {
1075
+ const slug = await detectGitHubRepoSlug(projectRoot);
1076
+ if (!slug) {
1077
+ console.log(chalk8.dim(" GitHub checks skipped (no GitHub remote detected)"));
1078
+ return null;
1079
+ }
1080
+ const secretNames = await listGitHubSecretNames(slug);
1081
+ if (!secretNames) {
1082
+ console.log(chalk8.dim(" GitHub checks skipped (gh CLI not available or not authenticated)"));
1083
+ return slug;
1084
+ }
1085
+ const secretSet = new Set(secretNames);
1086
+ console.log(chalk8.dim(` GitHub secrets: ${secretNames.length} found in ${slug}`));
1087
+ for (const v of variables) {
1088
+ if (v.requiredIn.github) {
1089
+ v.githubStatus = secretSet.has(v.name) ? "present" : "missing";
1090
+ }
1091
+ }
1092
+ return slug;
1093
+ }
883
1094
  function parseApps(content) {
884
1095
  const apps = [];
885
1096
  const appsSection = extractSection(content, "Apps");
@@ -959,10 +1170,10 @@ function extractSection(content, heading) {
959
1170
  async function checkLocalEnvFiles(projectRoot, apps) {
960
1171
  const result = /* @__PURE__ */ new Map();
961
1172
  for (const app of apps) {
962
- const envPath = join7(projectRoot, app.envFilePath);
1173
+ const envPath = join9(projectRoot, app.envFilePath);
963
1174
  const varNames = /* @__PURE__ */ new Set();
964
1175
  if (existsSync5(envPath)) {
965
- const content = await readFile5(envPath, "utf-8");
1176
+ const content = await readFile7(envPath, "utf-8");
966
1177
  for (const line of content.split("\n")) {
967
1178
  const m = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=/);
968
1179
  if (m) {
@@ -975,14 +1186,14 @@ async function checkLocalEnvFiles(projectRoot, apps) {
975
1186
  return result;
976
1187
  }
977
1188
  async function parseWorkflowSecrets(projectRoot) {
978
- const workflowsDir = join7(projectRoot, ".github", "workflows");
1189
+ const workflowsDir = join9(projectRoot, ".github", "workflows");
979
1190
  if (!existsSync5(workflowsDir))
980
1191
  return [];
981
1192
  const refs = [];
982
1193
  const secretRe = /\$\{\{\s*secrets\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;
983
1194
  for (const ext of ["*.yml", "*.yaml"]) {
984
- for await (const filePath of glob(join7(workflowsDir, ext))) {
985
- const content = await readFile5(filePath, "utf-8");
1195
+ for await (const filePath of glob2(join9(workflowsDir, ext))) {
1196
+ const content = await readFile7(filePath, "utf-8");
986
1197
  const secrets = /* @__PURE__ */ new Set();
987
1198
  let m;
988
1199
  while ((m = secretRe.exec(content)) !== null) {
@@ -991,7 +1202,7 @@ async function parseWorkflowSecrets(projectRoot) {
991
1202
  secretRe.lastIndex = 0;
992
1203
  if (secrets.size > 0) {
993
1204
  refs.push({
994
- workflowFile: relative(projectRoot, filePath),
1205
+ workflowFile: relative2(projectRoot, filePath),
995
1206
  secretsUsed: [...secrets].sort()
996
1207
  });
997
1208
  }
@@ -1045,29 +1256,35 @@ function computeDrift(variables, apps, localPresence, workflowRefs) {
1045
1256
  workflowBrokenRefs: workflowBrokenRefs.sort()
1046
1257
  };
1047
1258
  }
1048
- var MANIFEST_PATH;
1259
+ var execFileAsync, MANIFEST_PATH;
1049
1260
  var init_env_manifest_scanner = __esm({
1050
1261
  "dist/scanner/env-manifest-scanner.js"() {
1051
1262
  "use strict";
1263
+ init_auth2();
1264
+ init_discover_projects();
1265
+ init_fetch_env_vars();
1266
+ execFileAsync = promisify(execFile);
1052
1267
  MANIFEST_PATH = "docs/reference/env-manifest.md";
1053
1268
  }
1054
1269
  });
1055
1270
 
1056
1271
  // dist/scanner/index.js
1057
1272
  import { readdir as readdir2 } from "node:fs/promises";
1058
- import { join as join8, relative as relative2 } from "node:path";
1273
+ import { join as join10, relative as relative3 } from "node:path";
1059
1274
  import { existsSync as existsSync6 } from "node:fs";
1060
- import { homedir as homedir5 } from "node:os";
1275
+ import { homedir as homedir6 } from "node:os";
1061
1276
  import { createHash } from "node:crypto";
1062
1277
  async function scanProject(projectRoot) {
1063
1278
  const allFiles = await discoverFiles(projectRoot);
1064
1279
  const rootFiles = identifyRoots(allFiles, projectRoot);
1065
1280
  const allRefs = [];
1281
+ const allBrokenRefs = [];
1066
1282
  for (const file of allFiles) {
1067
- const fullPath = file.startsWith("/") ? file : join8(projectRoot, file);
1283
+ const fullPath = file.startsWith("/") ? file : join10(projectRoot, file);
1068
1284
  try {
1069
- const refs = await parseFileReferences(fullPath, projectRoot);
1285
+ const { refs, brokenRefs: brokenRefs2 } = await parseFileReferences(fullPath, projectRoot);
1070
1286
  allRefs.push(...refs);
1287
+ allBrokenRefs.push(...brokenRefs2);
1071
1288
  } catch {
1072
1289
  }
1073
1290
  }
@@ -1077,11 +1294,31 @@ async function scanProject(projectRoot) {
1077
1294
  const skills = await parseSkills(projectRoot);
1078
1295
  const toolings = await detectToolings(projectRoot);
1079
1296
  const envManifest = await scanEnvManifest(projectRoot);
1080
- const scanData = JSON.stringify({ graph, orphans, skills, staleFiles, toolings, envManifest });
1297
+ const depthMap = /* @__PURE__ */ new Map();
1298
+ const queue = [...rootFiles];
1299
+ for (const r of queue)
1300
+ depthMap.set(r, 0);
1301
+ while (queue.length > 0) {
1302
+ const current = queue.shift();
1303
+ const currentDepth = depthMap.get(current);
1304
+ for (const ref of allRefs) {
1305
+ if (ref.from === current && !depthMap.has(ref.to)) {
1306
+ depthMap.set(ref.to, currentDepth + 1);
1307
+ queue.push(ref.to);
1308
+ }
1309
+ }
1310
+ }
1311
+ const brokenRefs = allBrokenRefs.map((br) => ({
1312
+ ...br,
1313
+ depth: depthMap.get(br.from) ?? 999
1314
+ }));
1315
+ brokenRefs.sort((a, b) => a.depth - b.depth || a.from.localeCompare(b.from));
1316
+ const scanData = JSON.stringify({ graph, orphans, brokenRefs, skills, staleFiles, toolings, envManifest });
1081
1317
  const dataHash = createHash("sha256").update(scanData).digest("hex");
1082
1318
  return {
1083
1319
  graph,
1084
1320
  orphans,
1321
+ brokenRefs,
1085
1322
  skills,
1086
1323
  staleFiles,
1087
1324
  toolings,
@@ -1092,17 +1329,17 @@ async function scanProject(projectRoot) {
1092
1329
  }
1093
1330
  async function discoverFiles(projectRoot) {
1094
1331
  const files = [];
1095
- const claudeDir = join8(projectRoot, ".claude");
1332
+ const claudeDir = join10(projectRoot, ".claude");
1096
1333
  if (existsSync6(claudeDir)) {
1097
1334
  await walkDir(claudeDir, projectRoot, files);
1098
1335
  }
1099
- if (existsSync6(join8(projectRoot, "CLAUDE.md"))) {
1336
+ if (existsSync6(join10(projectRoot, "CLAUDE.md"))) {
1100
1337
  files.push("CLAUDE.md");
1101
1338
  }
1102
- if (existsSync6(join8(projectRoot, "skills.md"))) {
1339
+ if (existsSync6(join10(projectRoot, "skills.md"))) {
1103
1340
  files.push("skills.md");
1104
1341
  }
1105
- const plansDir = join8(projectRoot, "docs", "plans");
1342
+ const plansDir = join10(projectRoot, "docs", "plans");
1106
1343
  if (existsSync6(plansDir)) {
1107
1344
  await walkDir(plansDir, projectRoot, files);
1108
1345
  }
@@ -1111,13 +1348,13 @@ async function discoverFiles(projectRoot) {
1111
1348
  async function walkDir(dir, projectRoot, files) {
1112
1349
  const entries = await readdir2(dir, { withFileTypes: true });
1113
1350
  for (const entry of entries) {
1114
- const fullPath = join8(dir, entry.name);
1351
+ const fullPath = join10(dir, entry.name);
1115
1352
  if (entry.isDirectory()) {
1116
1353
  if (["node_modules", ".git", ".turbo", "cache", "session-env"].includes(entry.name))
1117
1354
  continue;
1118
1355
  await walkDir(fullPath, projectRoot, files);
1119
1356
  } else {
1120
- const relPath = relative2(projectRoot, fullPath);
1357
+ const relPath = relative3(projectRoot, fullPath);
1121
1358
  files.push(relPath);
1122
1359
  }
1123
1360
  }
@@ -1130,7 +1367,7 @@ function identifyRoots(allFiles, projectRoot) {
1130
1367
  }
1131
1368
  }
1132
1369
  for (const globalFile of GLOBAL_ROOT_FILES) {
1133
- const expanded = globalFile.replace("~", homedir5());
1370
+ const expanded = globalFile.replace("~", homedir6());
1134
1371
  if (existsSync6(expanded)) {
1135
1372
  roots.push(globalFile);
1136
1373
  }
@@ -1138,8 +1375,8 @@ function identifyRoots(allFiles, projectRoot) {
1138
1375
  return roots;
1139
1376
  }
1140
1377
  async function readClaudeConfigFiles(projectRoot) {
1141
- const { readFile: readFile10, stat, glob: glob2 } = await import("node:fs/promises");
1142
- const { join: join14, relative: relative4 } = await import("node:path");
1378
+ const { readFile: readFile12, stat, glob: glob3 } = await import("node:fs/promises");
1379
+ const { join: join16, relative: relative5 } = await import("node:path");
1143
1380
  const { existsSync: existsSync13 } = await import("node:fs");
1144
1381
  const configPatterns = [
1145
1382
  "CLAUDE.md",
@@ -1151,12 +1388,12 @@ async function readClaudeConfigFiles(projectRoot) {
1151
1388
  const files = [];
1152
1389
  const seen = /* @__PURE__ */ new Set();
1153
1390
  async function addFile(fullPath) {
1154
- const relPath = relative4(projectRoot, fullPath);
1391
+ const relPath = relative5(projectRoot, fullPath);
1155
1392
  if (seen.has(relPath))
1156
1393
  return;
1157
1394
  seen.add(relPath);
1158
1395
  try {
1159
- const content = await readFile10(fullPath, "utf-8");
1396
+ const content = await readFile12(fullPath, "utf-8");
1160
1397
  const fileStat = await stat(fullPath);
1161
1398
  const lastMod = getGitLastModified(fullPath, projectRoot);
1162
1399
  files.push({ filePath: relPath, content, sizeBytes: fileStat.size, lastModified: lastMod });
@@ -1164,22 +1401,22 @@ async function readClaudeConfigFiles(projectRoot) {
1164
1401
  }
1165
1402
  }
1166
1403
  for (const pattern of configPatterns) {
1167
- for await (const fullPath of glob2(join14(projectRoot, pattern))) {
1404
+ for await (const fullPath of glob3(join16(projectRoot, pattern))) {
1168
1405
  if (!existsSync13(fullPath))
1169
1406
  continue;
1170
1407
  await addFile(fullPath);
1171
1408
  }
1172
1409
  }
1173
- const pkgPath = join14(projectRoot, "package.json");
1410
+ const pkgPath = join16(projectRoot, "package.json");
1174
1411
  if (existsSync13(pkgPath)) {
1175
1412
  try {
1176
- const pkgContent = await readFile10(pkgPath, "utf-8");
1413
+ const pkgContent = await readFile12(pkgPath, "utf-8");
1177
1414
  const pkg = JSON.parse(pkgContent);
1178
1415
  const preflightCmd = pkg.scripts?.preflight;
1179
1416
  if (preflightCmd) {
1180
1417
  const match = preflightCmd.match(/(?:bash\s+|sh\s+|\.\/)?(\S+\.(?:sh|bash|ts|js|mjs))/);
1181
1418
  if (match) {
1182
- const scriptPath = join14(projectRoot, match[1]);
1419
+ const scriptPath = join16(projectRoot, match[1]);
1183
1420
  if (existsSync13(scriptPath)) {
1184
1421
  await addFile(scriptPath);
1185
1422
  }
@@ -1194,7 +1431,7 @@ function detectStaleFiles(allFiles, projectRoot) {
1194
1431
  const stale = [];
1195
1432
  const now = Date.now();
1196
1433
  for (const file of allFiles) {
1197
- const fullPath = join8(projectRoot, file);
1434
+ const fullPath = join10(projectRoot, file);
1198
1435
  const lastMod = getGitLastModified(fullPath, projectRoot);
1199
1436
  if (lastMod) {
1200
1437
  const days = Math.floor((now - new Date(lastMod).getTime()) / (1e3 * 60 * 60 * 24));
@@ -1336,7 +1573,7 @@ var init_html_generator = __esm({
1336
1573
  });
1337
1574
 
1338
1575
  // dist/check-update.js
1339
- import chalk8 from "chalk";
1576
+ import chalk9 from "chalk";
1340
1577
  async function fetchLatest() {
1341
1578
  try {
1342
1579
  const controller = new AbortController();
@@ -1355,12 +1592,12 @@ async function fetchLatest() {
1355
1592
  }
1356
1593
  function printUpdateBanner(latest) {
1357
1594
  console.log("");
1358
- console.log(chalk8.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
1359
- console.log(chalk8.yellow("\u2502") + chalk8.bold(" Update available! ") + chalk8.dim(`${CURRENT_VERSION}`) + chalk8.white(" \u2192 ") + chalk8.green.bold(`${latest}`) + " " + chalk8.yellow("\u2502"));
1360
- console.log(chalk8.yellow("\u2502") + " " + chalk8.yellow("\u2502"));
1361
- console.log(chalk8.yellow("\u2502") + " Run: " + chalk8.cyan("md4ai update") + " " + chalk8.yellow("\u2502"));
1362
- console.log(chalk8.yellow("\u2502") + " " + chalk8.yellow("\u2502"));
1363
- console.log(chalk8.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
1595
+ console.log(chalk9.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
1596
+ console.log(chalk9.yellow("\u2502") + chalk9.bold(" Update available! ") + chalk9.dim(`${CURRENT_VERSION}`) + chalk9.white(" \u2192 ") + chalk9.green.bold(`${latest}`) + " " + chalk9.yellow("\u2502"));
1597
+ console.log(chalk9.yellow("\u2502") + " " + chalk9.yellow("\u2502"));
1598
+ console.log(chalk9.yellow("\u2502") + " Run: " + chalk9.cyan("md4ai update") + " " + chalk9.yellow("\u2502"));
1599
+ console.log(chalk9.yellow("\u2502") + " " + chalk9.yellow("\u2502"));
1600
+ console.log(chalk9.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
1364
1601
  console.log("");
1365
1602
  }
1366
1603
  async function checkForUpdate() {
@@ -1370,7 +1607,7 @@ async function checkForUpdate() {
1370
1607
  if (latest !== CURRENT_VERSION && isNewer(latest, CURRENT_VERSION)) {
1371
1608
  printUpdateBanner(latest);
1372
1609
  } else {
1373
- console.log(chalk8.green(`md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.`));
1610
+ console.log(chalk9.green(`md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.`));
1374
1611
  }
1375
1612
  }
1376
1613
  async function autoCheckForUpdate() {
@@ -1399,7 +1636,7 @@ var CURRENT_VERSION;
1399
1636
  var init_check_update = __esm({
1400
1637
  "dist/check-update.js"() {
1401
1638
  "use strict";
1402
- CURRENT_VERSION = true ? "0.9.4" : "0.0.0-dev";
1639
+ CURRENT_VERSION = true ? "0.9.6" : "0.0.0-dev";
1403
1640
  }
1404
1641
  });
1405
1642
 
@@ -1427,6 +1664,87 @@ var init_push_toolings = __esm({
1427
1664
  }
1428
1665
  });
1429
1666
 
1667
+ // dist/commands/push-health-results.js
1668
+ import chalk10 from "chalk";
1669
+ async function pushHealthResults(supabase, folderId, manifest) {
1670
+ const { data: checks } = await supabase.from("env_health_checks").select("id, check_type, check_config").eq("folder_id", folderId).eq("enabled", true);
1671
+ if (!checks?.length)
1672
+ return;
1673
+ const results = [];
1674
+ const ranAt = manifest.checkedAt;
1675
+ for (const chk of checks) {
1676
+ const config2 = chk.check_config;
1677
+ if (chk.check_type === "env_var_set") {
1678
+ const app = config2.app;
1679
+ const variable = config2.variable;
1680
+ const v = manifest.variables.find((mv) => mv.name === variable);
1681
+ if (v) {
1682
+ const localStatus = v.localStatus[app];
1683
+ results.push({
1684
+ check_id: chk.id,
1685
+ status: localStatus === "present" ? "pass" : "fail",
1686
+ message: localStatus === "present" ? "Set" : "Missing",
1687
+ ran_at: ranAt
1688
+ });
1689
+ }
1690
+ } else if (chk.check_type === "file_exists") {
1691
+ results.push({
1692
+ check_id: chk.id,
1693
+ status: "pass",
1694
+ message: "Found",
1695
+ ran_at: ranAt
1696
+ });
1697
+ } else if (chk.check_type === "gh_secret") {
1698
+ const variable = config2.secret;
1699
+ const v = manifest.variables.find((mv) => mv.name === variable);
1700
+ const ghStatus = v?.githubStatus;
1701
+ results.push({
1702
+ check_id: chk.id,
1703
+ status: ghStatus === "present" ? "pass" : ghStatus === "missing" ? "fail" : "warn",
1704
+ message: ghStatus === "present" ? "Exists" : ghStatus === "missing" ? "Missing" : "Unknown",
1705
+ ran_at: ranAt
1706
+ });
1707
+ } else if (chk.check_type === "vercel_env") {
1708
+ const variable = config2.variable;
1709
+ const projectName = config2.projectName;
1710
+ const target = config2.target;
1711
+ const proj = manifest.vercelDiscovered?.find((p) => p.projectName === projectName);
1712
+ if (proj) {
1713
+ const vercelVar = proj.vars.find((v) => v.key === variable);
1714
+ if (vercelVar) {
1715
+ const hasTarget = vercelVar.targets.includes(target);
1716
+ results.push({
1717
+ check_id: chk.id,
1718
+ status: hasTarget ? "pass" : "warn",
1719
+ message: hasTarget ? `Present (${target})` : `Present but not in ${target}`,
1720
+ ran_at: ranAt
1721
+ });
1722
+ } else {
1723
+ results.push({
1724
+ check_id: chk.id,
1725
+ status: "fail",
1726
+ message: `Not found in ${projectName}`,
1727
+ ran_at: ranAt
1728
+ });
1729
+ }
1730
+ }
1731
+ }
1732
+ }
1733
+ if (results.length > 0) {
1734
+ const { error } = await supabase.from("env_health_results").insert(results);
1735
+ if (error) {
1736
+ console.error(chalk10.yellow(`Health results warning: ${error.message}`));
1737
+ } else {
1738
+ console.log(chalk10.green(` Updated ${results.length} health check result(s).`));
1739
+ }
1740
+ }
1741
+ }
1742
+ var init_push_health_results = __esm({
1743
+ "dist/commands/push-health-results.js"() {
1744
+ "use strict";
1745
+ }
1746
+ });
1747
+
1430
1748
  // dist/device-utils.js
1431
1749
  import { hostname as hostname2, platform as platform2 } from "node:os";
1432
1750
  function detectOs2() {
@@ -1473,26 +1791,37 @@ __export(map_exports, {
1473
1791
  import { resolve as resolve3, basename } from "node:path";
1474
1792
  import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
1475
1793
  import { existsSync as existsSync7 } from "node:fs";
1476
- import chalk9 from "chalk";
1794
+ import chalk11 from "chalk";
1477
1795
  import { select as select3, input as input5, confirm as confirm2 } from "@inquirer/prompts";
1478
1796
  async function mapCommand(path, options) {
1479
1797
  await checkForUpdate();
1480
1798
  const projectRoot = resolve3(path ?? process.cwd());
1481
1799
  if (!existsSync7(projectRoot)) {
1482
- console.error(chalk9.red(`Path not found: ${projectRoot}`));
1800
+ console.error(chalk11.red(`Path not found: ${projectRoot}`));
1483
1801
  process.exit(1);
1484
1802
  }
1485
- console.log(chalk9.blue(`Scanning: ${projectRoot}
1803
+ console.log(chalk11.blue(`Scanning: ${projectRoot}
1486
1804
  `));
1487
1805
  const result = await scanProject(projectRoot);
1488
1806
  console.log(` Files found: ${result.graph.nodes.length}`);
1489
1807
  console.log(` References: ${result.graph.edges.length}`);
1808
+ console.log(` Broken refs: ${result.brokenRefs.length}`);
1490
1809
  console.log(` Orphans: ${result.orphans.length}`);
1491
1810
  console.log(` Stale files: ${result.staleFiles.length}`);
1492
1811
  console.log(` Skills: ${result.skills.length}`);
1493
1812
  console.log(` Toolings: ${result.toolings.length}`);
1494
1813
  console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
1495
1814
  console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
1815
+ if (result.brokenRefs.length > 0) {
1816
+ console.log(chalk11.red(`
1817
+ Warning: ${result.brokenRefs.length} broken reference(s) found:`));
1818
+ for (const br of result.brokenRefs.slice(0, 5)) {
1819
+ console.log(chalk11.red(` ${br.from} -> ${br.to}`));
1820
+ }
1821
+ if (result.brokenRefs.length > 5) {
1822
+ console.log(chalk11.red(` ... and ${result.brokenRefs.length - 5} more`));
1823
+ }
1824
+ }
1496
1825
  const outputDir = resolve3(projectRoot, "output");
1497
1826
  if (!existsSync7(outputDir)) {
1498
1827
  await mkdir2(outputDir, { recursive: true });
@@ -1500,7 +1829,7 @@ async function mapCommand(path, options) {
1500
1829
  const htmlPath = resolve3(outputDir, "index.html");
1501
1830
  const html = generateOfflineHtml(result, projectRoot);
1502
1831
  await writeFile2(htmlPath, html, "utf-8");
1503
- console.log(chalk9.green(`
1832
+ console.log(chalk11.green(`
1504
1833
  Local preview: ${htmlPath}`));
1505
1834
  if (!options.offline) {
1506
1835
  try {
@@ -1509,9 +1838,9 @@ Local preview: ${htmlPath}`));
1509
1838
  if (devicePaths?.length) {
1510
1839
  const { folder_id, device_name } = devicePaths[0];
1511
1840
  const { data: proposedFiles } = await supabase.from("folder_files").select("id, file_path, proposed_at").eq("folder_id", folder_id).eq("proposed_for_deletion", true);
1512
- if (proposedFiles?.length) {
1841
+ if (proposedFiles?.length && process.stdin.isTTY) {
1513
1842
  const { checkbox } = await import("@inquirer/prompts");
1514
- console.log(chalk9.yellow(`
1843
+ console.log(chalk11.yellow(`
1515
1844
  ${proposedFiles.length} file(s) proposed for deletion:
1516
1845
  `));
1517
1846
  const toDelete = await checkbox({
@@ -1527,9 +1856,9 @@ ${proposedFiles.length} file(s) proposed for deletion:
1527
1856
  const { unlink } = await import("node:fs/promises");
1528
1857
  await unlink(fullPath);
1529
1858
  await supabase.from("folder_files").delete().eq("id", file.id);
1530
- console.log(chalk9.green(` Deleted: ${file.file_path}`));
1859
+ console.log(chalk11.green(` Deleted: ${file.file_path}`));
1531
1860
  } catch (err) {
1532
- console.error(chalk9.red(` Failed to delete ${file.file_path}: ${err}`));
1861
+ console.error(chalk11.red(` Failed to delete ${file.file_path}: ${err}`));
1533
1862
  }
1534
1863
  }
1535
1864
  const keptIds = proposedFiles.filter((f) => !toDelete.some((d) => d.id === f.id)).map((f) => f.id);
@@ -1537,13 +1866,16 @@ ${proposedFiles.length} file(s) proposed for deletion:
1537
1866
  for (const id of keptIds) {
1538
1867
  await supabase.from("folder_files").update({ proposed_for_deletion: false, proposed_at: null, proposed_by: null }).eq("id", id);
1539
1868
  }
1540
- console.log(chalk9.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
1869
+ console.log(chalk11.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
1541
1870
  }
1542
1871
  console.log("");
1872
+ } else if (proposedFiles?.length) {
1873
+ console.log(chalk11.dim(` ${proposedFiles.length} file(s) proposed for deletion \u2014 skipped (non-interactive mode).`));
1543
1874
  }
1544
1875
  const { error } = await supabase.from("claude_folders").update({
1545
1876
  graph_json: result.graph,
1546
1877
  orphans_json: result.orphans,
1878
+ broken_refs_json: result.brokenRefs,
1547
1879
  skills_table_json: result.skills,
1548
1880
  stale_files_json: result.staleFiles,
1549
1881
  env_manifest_json: result.envManifest,
@@ -1551,17 +1883,20 @@ ${proposedFiles.length} file(s) proposed for deletion:
1551
1883
  data_hash: result.dataHash
1552
1884
  }).eq("id", folder_id);
1553
1885
  if (error) {
1554
- console.error(chalk9.yellow(`Sync warning: ${error.message}`));
1886
+ console.error(chalk11.yellow(`Sync warning: ${error.message}`));
1555
1887
  } else {
1556
1888
  await pushToolings(supabase, folder_id, result.toolings);
1889
+ if (result.envManifest) {
1890
+ await pushHealthResults(supabase, folder_id, result.envManifest);
1891
+ }
1557
1892
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
1558
1893
  await saveState({
1559
1894
  lastFolderId: folder_id,
1560
1895
  lastDeviceName: device_name,
1561
1896
  lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
1562
1897
  });
1563
- console.log(chalk9.green("Synced to Supabase."));
1564
- console.log(chalk9.cyan(`
1898
+ console.log(chalk11.green("Synced to Supabase."));
1899
+ console.log(chalk11.cyan(`
1565
1900
  https://www.md4ai.com/project/${folder_id}
1566
1901
  `));
1567
1902
  const configFiles = await readClaudeConfigFiles(projectRoot);
@@ -1575,14 +1910,18 @@ ${proposedFiles.length} file(s) proposed for deletion:
1575
1910
  last_modified: file.lastModified
1576
1911
  }, { onConflict: "folder_id,file_path" });
1577
1912
  }
1578
- console.log(chalk9.green(` Uploaded ${configFiles.length} config file(s).`));
1913
+ console.log(chalk11.green(` Uploaded ${configFiles.length} config file(s).`));
1579
1914
  }
1580
1915
  }
1581
1916
  } else {
1582
- console.log(chalk9.yellow("\nThis folder is not linked to a project on your dashboard."));
1917
+ console.log(chalk11.yellow("\nThis folder is not linked to a project on your dashboard."));
1918
+ if (!process.stdin.isTTY) {
1919
+ console.log(chalk11.dim("Non-interactive mode \u2014 skipping link prompt."));
1920
+ return;
1921
+ }
1583
1922
  const shouldLink = await confirm2({ message: "Would you like to link it now?" });
1584
1923
  if (!shouldLink) {
1585
- console.log(chalk9.dim("Skipped \u2014 local preview still generated."));
1924
+ console.log(chalk11.dim("Skipped \u2014 local preview still generated."));
1586
1925
  } else {
1587
1926
  const { supabase: sb, userId } = await getAuthenticatedClient();
1588
1927
  const { data: folders } = await sb.from("claude_folders").select("id, name").order("name");
@@ -1602,11 +1941,11 @@ ${proposedFiles.length} file(s) proposed for deletion:
1602
1941
  });
1603
1942
  const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId, name: projectName }).select("id").single();
1604
1943
  if (createErr || !newFolder) {
1605
- console.error(chalk9.red(`Failed to create project: ${createErr?.message}`));
1944
+ console.error(chalk11.red(`Failed to create project: ${createErr?.message}`));
1606
1945
  return;
1607
1946
  }
1608
1947
  folderId = newFolder.id;
1609
- console.log(chalk9.green(`Created project "${projectName}".`));
1948
+ console.log(chalk11.green(`Created project "${projectName}".`));
1610
1949
  } else {
1611
1950
  folderId = chosen;
1612
1951
  }
@@ -1628,6 +1967,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
1628
1967
  await sb.from("claude_folders").update({
1629
1968
  graph_json: result.graph,
1630
1969
  orphans_json: result.orphans,
1970
+ broken_refs_json: result.brokenRefs,
1631
1971
  skills_table_json: result.skills,
1632
1972
  stale_files_json: result.staleFiles,
1633
1973
  env_manifest_json: result.envManifest,
@@ -1635,6 +1975,9 @@ ${proposedFiles.length} file(s) proposed for deletion:
1635
1975
  data_hash: result.dataHash
1636
1976
  }).eq("id", folderId);
1637
1977
  await pushToolings(sb, folderId, result.toolings);
1978
+ if (result.envManifest) {
1979
+ await pushHealthResults(sb, folderId, result.envManifest);
1980
+ }
1638
1981
  const configFiles = await readClaudeConfigFiles(projectRoot);
1639
1982
  for (const file of configFiles) {
1640
1983
  await sb.from("folder_files").upsert({
@@ -1650,14 +1993,14 @@ ${proposedFiles.length} file(s) proposed for deletion:
1650
1993
  lastDeviceName: deviceName,
1651
1994
  lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
1652
1995
  });
1653
- console.log(chalk9.green("\nLinked and synced."));
1654
- console.log(chalk9.cyan(`
1996
+ console.log(chalk11.green("\nLinked and synced."));
1997
+ console.log(chalk11.cyan(`
1655
1998
  https://www.md4ai.com/project/${folderId}
1656
1999
  `));
1657
2000
  }
1658
2001
  }
1659
2002
  } catch {
1660
- console.log(chalk9.yellow("Not logged in \u2014 local preview only."));
2003
+ console.log(chalk11.yellow("Not logged in \u2014 local preview only."));
1661
2004
  }
1662
2005
  }
1663
2006
  }
@@ -1670,6 +2013,7 @@ var init_map = __esm({
1670
2013
  init_html_generator();
1671
2014
  init_check_update();
1672
2015
  init_push_toolings();
2016
+ init_push_health_results();
1673
2017
  init_device_utils();
1674
2018
  }
1675
2019
  });
@@ -1679,26 +2023,27 @@ var sync_exports = {};
1679
2023
  __export(sync_exports, {
1680
2024
  syncCommand: () => syncCommand
1681
2025
  });
1682
- import chalk12 from "chalk";
2026
+ import chalk14 from "chalk";
1683
2027
  async function syncCommand(options) {
1684
2028
  const { supabase } = await getAuthenticatedClient();
1685
2029
  if (options.all) {
1686
2030
  const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path");
1687
2031
  if (error || !devices?.length) {
1688
- console.error(chalk12.red("No devices found."));
2032
+ console.error(chalk14.red("No devices found."));
1689
2033
  process.exit(1);
1690
2034
  }
1691
2035
  for (const device of devices) {
1692
- console.log(chalk12.blue(`Syncing: ${device.path}`));
2036
+ console.log(chalk14.blue(`Syncing: ${device.path}`));
1693
2037
  const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
1694
2038
  if (proposedAll?.length) {
1695
- console.log(chalk12.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2039
+ console.log(chalk14.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
1696
2040
  }
1697
2041
  try {
1698
2042
  const result = await scanProject(device.path);
1699
2043
  await supabase.from("claude_folders").update({
1700
2044
  graph_json: result.graph,
1701
2045
  orphans_json: result.orphans,
2046
+ broken_refs_json: result.brokenRefs,
1702
2047
  skills_table_json: result.skills,
1703
2048
  stale_files_json: result.staleFiles,
1704
2049
  env_manifest_json: result.envManifest,
@@ -1707,33 +2052,34 @@ async function syncCommand(options) {
1707
2052
  }).eq("id", device.folder_id);
1708
2053
  await pushToolings(supabase, device.folder_id, result.toolings);
1709
2054
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
1710
- console.log(chalk12.green(` Done: ${device.device_name}`));
2055
+ console.log(chalk14.green(` Done: ${device.device_name}`));
1711
2056
  } catch (err) {
1712
- console.error(chalk12.red(` Failed: ${device.path}: ${err}`));
2057
+ console.error(chalk14.red(` Failed: ${device.path}: ${err}`));
1713
2058
  }
1714
2059
  }
1715
2060
  await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
1716
- console.log(chalk12.green("\nAll devices synced."));
2061
+ console.log(chalk14.green("\nAll devices synced."));
1717
2062
  } else {
1718
2063
  const state = await loadState();
1719
2064
  if (!state.lastFolderId) {
1720
- console.error(chalk12.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
2065
+ console.error(chalk14.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
1721
2066
  process.exit(1);
1722
2067
  }
1723
2068
  const { data: device } = await supabase.from("device_paths").select("folder_id, device_name, path").eq("folder_id", state.lastFolderId).eq("device_name", state.lastDeviceName).single();
1724
2069
  if (!device) {
1725
- console.error(chalk12.red("Could not find last synced device/folder."));
2070
+ console.error(chalk14.red("Could not find last synced device/folder."));
1726
2071
  process.exit(1);
1727
2072
  }
1728
- console.log(chalk12.blue(`Syncing: ${device.path}`));
2073
+ console.log(chalk14.blue(`Syncing: ${device.path}`));
1729
2074
  const { data: proposedSingle } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
1730
2075
  if (proposedSingle?.length) {
1731
- console.log(chalk12.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2076
+ console.log(chalk14.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
1732
2077
  }
1733
2078
  const result = await scanProject(device.path);
1734
2079
  await supabase.from("claude_folders").update({
1735
2080
  graph_json: result.graph,
1736
2081
  orphans_json: result.orphans,
2082
+ broken_refs_json: result.brokenRefs,
1737
2083
  skills_table_json: result.skills,
1738
2084
  stale_files_json: result.staleFiles,
1739
2085
  last_scanned: result.scannedAt,
@@ -1742,7 +2088,7 @@ async function syncCommand(options) {
1742
2088
  await pushToolings(supabase, device.folder_id, result.toolings);
1743
2089
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
1744
2090
  await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
1745
- console.log(chalk12.green("Synced."));
2091
+ console.log(chalk14.green("Synced."));
1746
2092
  }
1747
2093
  }
1748
2094
  var init_sync = __esm({
@@ -1756,16 +2102,16 @@ var init_sync = __esm({
1756
2102
  });
1757
2103
 
1758
2104
  // dist/mcp/read-configs.js
1759
- import { readFile as readFile8 } from "node:fs/promises";
1760
- import { join as join12 } from "node:path";
1761
- import { homedir as homedir7 } from "node:os";
2105
+ import { readFile as readFile10 } from "node:fs/promises";
2106
+ import { join as join14 } from "node:path";
2107
+ import { homedir as homedir8 } from "node:os";
1762
2108
  import { existsSync as existsSync11 } from "node:fs";
1763
2109
  import { readdir as readdir3 } from "node:fs/promises";
1764
2110
  async function readJsonSafe(path) {
1765
2111
  try {
1766
2112
  if (!existsSync11(path))
1767
2113
  return null;
1768
- const raw = await readFile8(path, "utf-8");
2114
+ const raw = await readFile10(path, "utf-8");
1769
2115
  return JSON.parse(raw);
1770
2116
  } catch {
1771
2117
  return null;
@@ -1825,52 +2171,52 @@ function parseFlatConfig(data, source) {
1825
2171
  return entries;
1826
2172
  }
1827
2173
  async function readAllMcpConfigs() {
1828
- const home = homedir7();
2174
+ const home = homedir8();
1829
2175
  const entries = [];
1830
- const userConfig = await readJsonSafe(join12(home, ".claude.json"));
2176
+ const userConfig = await readJsonSafe(join14(home, ".claude.json"));
1831
2177
  entries.push(...parseServers(userConfig, "global"));
1832
- const globalMcp = await readJsonSafe(join12(home, ".claude", "mcp.json"));
2178
+ const globalMcp = await readJsonSafe(join14(home, ".claude", "mcp.json"));
1833
2179
  entries.push(...parseServers(globalMcp, "global"));
1834
- const cwdMcp = await readJsonSafe(join12(process.cwd(), ".mcp.json"));
2180
+ const cwdMcp = await readJsonSafe(join14(process.cwd(), ".mcp.json"));
1835
2181
  entries.push(...parseServers(cwdMcp, "project"));
1836
- const pluginsBase = join12(home, ".claude", "plugins", "marketplaces");
2182
+ const pluginsBase = join14(home, ".claude", "plugins", "marketplaces");
1837
2183
  if (existsSync11(pluginsBase)) {
1838
2184
  try {
1839
2185
  const marketplaces = await readdir3(pluginsBase, { withFileTypes: true });
1840
2186
  for (const mp of marketplaces) {
1841
2187
  if (!mp.isDirectory())
1842
2188
  continue;
1843
- const extDir = join12(pluginsBase, mp.name, "external_plugins");
2189
+ const extDir = join14(pluginsBase, mp.name, "external_plugins");
1844
2190
  if (!existsSync11(extDir))
1845
2191
  continue;
1846
2192
  const plugins = await readdir3(extDir, { withFileTypes: true });
1847
2193
  for (const plugin of plugins) {
1848
2194
  if (!plugin.isDirectory())
1849
2195
  continue;
1850
- const pluginMcp = await readJsonSafe(join12(extDir, plugin.name, ".mcp.json"));
2196
+ const pluginMcp = await readJsonSafe(join14(extDir, plugin.name, ".mcp.json"));
1851
2197
  entries.push(...parseServers(pluginMcp, "plugin"));
1852
2198
  }
1853
2199
  }
1854
2200
  } catch {
1855
2201
  }
1856
2202
  }
1857
- const cacheBase = join12(home, ".claude", "plugins", "cache");
2203
+ const cacheBase = join14(home, ".claude", "plugins", "cache");
1858
2204
  if (existsSync11(cacheBase)) {
1859
2205
  try {
1860
2206
  const registries = await readdir3(cacheBase, { withFileTypes: true });
1861
2207
  for (const reg of registries) {
1862
2208
  if (!reg.isDirectory())
1863
2209
  continue;
1864
- const regDir = join12(cacheBase, reg.name);
2210
+ const regDir = join14(cacheBase, reg.name);
1865
2211
  const plugins = await readdir3(regDir, { withFileTypes: true });
1866
2212
  for (const plugin of plugins) {
1867
2213
  if (!plugin.isDirectory())
1868
2214
  continue;
1869
- const versionDirs = await readdir3(join12(regDir, plugin.name), { withFileTypes: true });
2215
+ const versionDirs = await readdir3(join14(regDir, plugin.name), { withFileTypes: true });
1870
2216
  for (const ver of versionDirs) {
1871
2217
  if (!ver.isDirectory())
1872
2218
  continue;
1873
- const mcpPath = join12(regDir, plugin.name, ver.name, ".mcp.json");
2219
+ const mcpPath = join14(regDir, plugin.name, ver.name, ".mcp.json");
1874
2220
  const flatData = await readJsonSafe(mcpPath);
1875
2221
  if (flatData) {
1876
2222
  entries.push(...parseFlatConfig(flatData, "plugin"));
@@ -1934,23 +2280,23 @@ function parseEtime(etime) {
1934
2280
  }
1935
2281
  return days * 86400 + (parts[0] ?? 0);
1936
2282
  }
1937
- function findProcessesForConfig(config, processes) {
1938
- if (config.type === "http")
2283
+ function findProcessesForConfig(config2, processes) {
2284
+ if (config2.type === "http")
1939
2285
  return [];
1940
- const packageName = config.args ? extractPackageName(config.args) : null;
2286
+ const packageName = config2.args ? extractPackageName(config2.args) : null;
1941
2287
  const matches = [];
1942
2288
  const searchTerms = [];
1943
2289
  if (packageName)
1944
2290
  searchTerms.push(packageName);
1945
- if (config.command === "node" && config.args?.[0]) {
1946
- searchTerms.push(config.args[0]);
2291
+ if (config2.command === "node" && config2.args?.[0]) {
2292
+ searchTerms.push(config2.args[0]);
1947
2293
  }
1948
- if (config.name) {
1949
- searchTerms.push(`mcp-server-${config.name}`);
1950
- searchTerms.push(`${config.name}-mcp`);
2294
+ if (config2.name) {
2295
+ searchTerms.push(`mcp-server-${config2.name}`);
2296
+ searchTerms.push(`${config2.name}-mcp`);
1951
2297
  }
1952
- if ((config.command === "uvx" || config.command === "pipx") && config.args) {
1953
- for (const arg of config.args) {
2298
+ if ((config2.command === "uvx" || config2.command === "pipx") && config2.args) {
2299
+ for (const arg of config2.args) {
1954
2300
  if (!arg.startsWith("-") && arg !== "run")
1955
2301
  searchTerms.push(arg);
1956
2302
  }
@@ -2000,7 +2346,7 @@ var mcp_watch_exports = {};
2000
2346
  __export(mcp_watch_exports, {
2001
2347
  mcpWatchCommand: () => mcpWatchCommand
2002
2348
  });
2003
- import chalk18 from "chalk";
2349
+ import chalk20 from "chalk";
2004
2350
  import { execFileSync as execFileSync5 } from "node:child_process";
2005
2351
  import { createHash as createHash2 } from "node:crypto";
2006
2352
  function detectTty() {
@@ -2014,10 +2360,10 @@ function detectTty() {
2014
2360
  return null;
2015
2361
  }
2016
2362
  }
2017
- function checkEnvVars(config) {
2018
- const required = config.env ? Object.keys(config.env) : [];
2363
+ function checkEnvVars(config2) {
2364
+ const required = config2.env ? Object.keys(config2.env) : [];
2019
2365
  const missing = required.filter((key) => {
2020
- const configValue = config.env?.[key];
2366
+ const configValue = config2.env?.[key];
2021
2367
  const hasConfigValue = configValue && !configValue.startsWith("${");
2022
2368
  return !hasConfigValue && !process.env[key];
2023
2369
  });
@@ -2052,20 +2398,20 @@ async function checkChromeCdp() {
2052
2398
  function buildRows(configs, httpResults, cdpStatus) {
2053
2399
  const processes = getProcessTable();
2054
2400
  const rows = [];
2055
- for (const config of configs) {
2056
- const { required, missing } = checkEnvVars(config);
2057
- const packageName = config.args ? extractPackageName(config.args) : null;
2058
- if (config.type === "http") {
2059
- const reachability = httpResults.get(config.name) ?? "unknown";
2401
+ for (const config2 of configs) {
2402
+ const { required, missing } = checkEnvVars(config2);
2403
+ const packageName = config2.args ? extractPackageName(config2.args) : null;
2404
+ if (config2.type === "http") {
2405
+ const reachability = httpResults.get(config2.name) ?? "unknown";
2060
2406
  const status = reachability === "reachable" ? "running" : "stopped";
2061
2407
  const detail = reachability === "reachable" ? "HTTP \u2014 remote service reachable" : reachability === "unreachable" ? "HTTP \u2014 remote service unreachable" : "HTTP \u2014 could not verify";
2062
2408
  rows.push({
2063
- server_name: config.name,
2064
- config_source: config.source,
2409
+ server_name: config2.name,
2410
+ config_source: config2.source,
2065
2411
  server_type: "http",
2066
2412
  command: null,
2067
2413
  package_name: null,
2068
- http_url: config.url ?? null,
2414
+ http_url: config2.url ?? null,
2069
2415
  status,
2070
2416
  pid: null,
2071
2417
  session_tty: null,
@@ -2079,10 +2425,10 @@ function buildRows(configs, httpResults, cdpStatus) {
2079
2425
  }
2080
2426
  if (missing.length > 0) {
2081
2427
  rows.push({
2082
- server_name: config.name,
2083
- config_source: config.source,
2428
+ server_name: config2.name,
2429
+ config_source: config2.source,
2084
2430
  server_type: "stdio",
2085
- command: config.command ?? null,
2431
+ command: config2.command ?? null,
2086
2432
  package_name: packageName,
2087
2433
  http_url: null,
2088
2434
  status: "error",
@@ -2096,15 +2442,15 @@ function buildRows(configs, httpResults, cdpStatus) {
2096
2442
  });
2097
2443
  continue;
2098
2444
  }
2099
- const isChromeDevtools = config.name === "chrome-devtools" || (config.args ?? []).some((a) => a.includes("chrome-devtools"));
2445
+ const isChromeDevtools = config2.name === "chrome-devtools" || (config2.args ?? []).some((a) => a.includes("chrome-devtools"));
2100
2446
  const cdpDetail = isChromeDevtools && cdpStatus ? cdpStatus.status === "reachable" ? "Chrome CDP reachable" : "Chrome not reachable on port 9222" : null;
2101
- const matches = findProcessesForConfig(config, processes);
2447
+ const matches = findProcessesForConfig(config2, processes);
2102
2448
  if (matches.length === 0) {
2103
2449
  rows.push({
2104
- server_name: config.name,
2105
- config_source: config.source,
2450
+ server_name: config2.name,
2451
+ config_source: config2.source,
2106
2452
  server_type: "stdio",
2107
- command: config.command ?? null,
2453
+ command: config2.command ?? null,
2108
2454
  package_name: packageName,
2109
2455
  http_url: null,
2110
2456
  status: "stopped",
@@ -2127,10 +2473,10 @@ function buildRows(configs, httpResults, cdpStatus) {
2127
2473
  }
2128
2474
  for (const match of byTty.values()) {
2129
2475
  rows.push({
2130
- server_name: config.name,
2131
- config_source: config.source,
2476
+ server_name: config2.name,
2477
+ config_source: config2.source,
2132
2478
  server_type: "stdio",
2133
- command: config.command ?? null,
2479
+ command: config2.command ?? null,
2134
2480
  package_name: packageName,
2135
2481
  http_url: null,
2136
2482
  status: "running",
@@ -2149,20 +2495,20 @@ function buildRows(configs, httpResults, cdpStatus) {
2149
2495
  }
2150
2496
  function printTable(rows, deviceName, watcherPid, cdpResult) {
2151
2497
  process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
2152
- console.log(chalk18.bold.cyan(`
2153
- MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) + chalk18.dim(` (PID ${watcherPid})`));
2154
- console.log(chalk18.dim(` ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \xB7 refreshes every 30s \xB7 Ctrl+C to stop
2498
+ console.log(chalk20.bold.cyan(`
2499
+ MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) + chalk20.dim(` (PID ${watcherPid})`));
2500
+ console.log(chalk20.dim(` ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \xB7 refreshes every 30s \xB7 Ctrl+C to stop
2155
2501
  `));
2156
2502
  if (cdpResult) {
2157
2503
  if (cdpResult.status === "reachable") {
2158
2504
  const browserInfo = cdpResult.browser ? ` (${cdpResult.browser})` : "";
2159
- console.log(chalk18.green(" \u2714 Chrome browser connected") + chalk18.dim(browserInfo));
2160
- console.log(chalk18.dim(" Claude Code can control Chrome via DevTools Protocol on localhost:9222"));
2505
+ console.log(chalk20.green(" \u2714 Chrome browser connected") + chalk20.dim(browserInfo));
2506
+ console.log(chalk20.dim(" Claude Code can control Chrome via DevTools Protocol on localhost:9222"));
2161
2507
  } else {
2162
- console.log(chalk18.red(" \u2717 Chrome browser not connected"));
2163
- console.log(chalk18.dim(" Claude Code cannot reach Chrome. To fix:"));
2164
- console.log(chalk18.dim(" 1. Open Chrome with: google-chrome --remote-debugging-port=9222"));
2165
- console.log(chalk18.dim(" 2. Or add --remote-debugging-port=9222 to your Chrome shortcut"));
2508
+ console.log(chalk20.red(" \u2717 Chrome browser not connected"));
2509
+ console.log(chalk20.dim(" Claude Code cannot reach Chrome. To fix:"));
2510
+ console.log(chalk20.dim(" 1. Open Chrome with: google-chrome --remote-debugging-port=9222"));
2511
+ console.log(chalk20.dim(" 2. Or add --remote-debugging-port=9222 to your Chrome shortcut"));
2166
2512
  }
2167
2513
  console.log("");
2168
2514
  }
@@ -2183,41 +2529,41 @@ function printTable(rows, deviceName, watcherPid, cdpResult) {
2183
2529
  sessionNum++;
2184
2530
  const totalMem = servers.reduce((sum, s) => sum + (s.memory_mb ?? 0), 0);
2185
2531
  const label = byTty.size === 1 ? "Claude Code session" : `Claude Code session ${sessionNum}`;
2186
- console.log(chalk18.green(` ${label}`) + chalk18.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB \xB7 terminal ${tty}`));
2532
+ console.log(chalk20.green(` ${label}`) + chalk20.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB \xB7 terminal ${tty}`));
2187
2533
  for (const s of servers) {
2188
2534
  const uptime = formatUptime(s.uptime_seconds ?? 0);
2189
- const source = chalk18.dim(`[${s.config_source}]`);
2190
- console.log(` ${chalk18.green("\u25CF")} ${s.server_name.padEnd(20)} ${source} ${chalk18.dim((s.package_name ?? "").padEnd(25))} ${String(s.memory_mb ?? 0).padStart(4)} MB ${uptime}`);
2535
+ const source = chalk20.dim(`[${s.config_source}]`);
2536
+ console.log(` ${chalk20.green("\u25CF")} ${s.server_name.padEnd(20)} ${source} ${chalk20.dim((s.package_name ?? "").padEnd(25))} ${String(s.memory_mb ?? 0).padStart(4)} MB ${uptime}`);
2191
2537
  }
2192
2538
  console.log("");
2193
2539
  }
2194
2540
  if (byTty.size > 1) {
2195
- console.log(chalk18.dim(" Each open Claude Code window runs its own set of MCP servers.\n"));
2541
+ console.log(chalk20.dim(" Each open Claude Code window runs its own set of MCP servers.\n"));
2196
2542
  }
2197
2543
  }
2198
2544
  if (runningHttp.length > 0) {
2199
- console.log(chalk18.blue(` Remote Services (${runningHttp.length})`) + chalk18.dim(" \u2014 HTTP endpoints reachable"));
2545
+ console.log(chalk20.blue(` Remote Services (${runningHttp.length})`) + chalk20.dim(" \u2014 HTTP endpoints reachable"));
2200
2546
  for (const s of runningHttp) {
2201
- console.log(` ${chalk18.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk18.dim((s.http_url ?? "").padEnd(30))}`);
2547
+ console.log(` ${chalk20.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk20.dim((s.http_url ?? "").padEnd(30))}`);
2202
2548
  }
2203
2549
  console.log("");
2204
2550
  }
2205
2551
  if (stopped.length > 0 || errored.length > 0) {
2206
2552
  const notRunning = [...stopped, ...errored];
2207
- console.log(chalk18.yellow(` Not Running (${notRunning.length})`) + chalk18.dim(" \u2014 configured but no process detected"));
2553
+ console.log(chalk20.yellow(` Not Running (${notRunning.length})`) + chalk20.dim(" \u2014 configured but no process detected"));
2208
2554
  for (const s of notRunning) {
2209
- const icon = s.status === "error" ? chalk18.red("\u2717") : chalk18.yellow("\u25CB");
2210
- const source = chalk18.dim(`[${s.config_source}]`);
2211
- const detail = s.error_detail ? chalk18.dim(` \u2014 ${s.error_detail}`) : "";
2555
+ const icon = s.status === "error" ? chalk20.red("\u2717") : chalk20.yellow("\u25CB");
2556
+ const source = chalk20.dim(`[${s.config_source}]`);
2557
+ const detail = s.error_detail ? chalk20.dim(` \u2014 ${s.error_detail}`) : "";
2212
2558
  console.log(` ${icon} ${s.server_name.padEnd(20)} ${source}${detail}`);
2213
2559
  }
2214
2560
  console.log("");
2215
2561
  }
2216
2562
  if (rows.length === 0) {
2217
- console.log(chalk18.yellow(" No MCP servers configured."));
2218
- console.log(chalk18.dim(" Configure servers in ~/.claude/mcp.json or .mcp.json\n"));
2563
+ console.log(chalk20.yellow(" No MCP servers configured."));
2564
+ console.log(chalk20.dim(" Configure servers in ~/.claude/mcp.json or .mcp.json\n"));
2219
2565
  }
2220
- console.log(chalk18.bgYellow.black.bold(" \u26A0 DO NOT CLOSE THIS WINDOW \u2014 it feeds live data to the dashboard \u26A0 "));
2566
+ console.log(chalk20.bgYellow.black.bold(" \u26A0 DO NOT CLOSE THIS WINDOW \u2014 it feeds live data to the dashboard \u26A0 "));
2221
2567
  console.log("");
2222
2568
  }
2223
2569
  function formatUptime(seconds) {
@@ -2240,7 +2586,7 @@ async function mcpWatchCommand() {
2240
2586
  const { data: existingWatchers } = await supabase.from("mcp_watchers").select("pid, tty, cli_version, started_at").eq("device_id", deviceId);
2241
2587
  if (existingWatchers && existingWatchers.length > 0) {
2242
2588
  console.log("");
2243
- console.log(chalk18.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
2589
+ console.log(chalk20.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
2244
2590
  for (const w of existingWatchers) {
2245
2591
  try {
2246
2592
  process.kill(w.pid, "SIGTERM");
@@ -2249,15 +2595,15 @@ async function mcpWatchCommand() {
2249
2595
  }
2250
2596
  await supabase.from("mcp_watchers").delete().eq("device_id", deviceId);
2251
2597
  await new Promise((r) => setTimeout(r, 1e3));
2252
- console.log(chalk18.dim(" Previous watcher stopped.\n"));
2598
+ console.log(chalk20.dim(" Previous watcher stopped.\n"));
2253
2599
  }
2254
2600
  process.stdout.write(`\x1B]0;MCP mon\x07`);
2255
- console.log(chalk18.blue(`Starting MCP monitor for ${deviceName}...`));
2601
+ console.log(chalk20.blue(`Starting MCP monitor for ${deviceName}...`));
2256
2602
  console.log("");
2257
- console.log(chalk18.dim(" View this in the online dashboard at:"));
2258
- console.log(chalk18.cyan(` https://www.md4ai.com/device/${deviceId}`));
2603
+ console.log(chalk20.dim(" View this in the online dashboard at:"));
2604
+ console.log(chalk20.cyan(` https://www.md4ai.com/device/${deviceId}`));
2259
2605
  console.log("");
2260
- console.log(chalk18.yellow(" Please note, closing this window will stop ALL watch reports."));
2606
+ console.log(chalk20.yellow(" Please note, closing this window will stop ALL watch reports."));
2261
2607
  console.log("");
2262
2608
  await supabase.from("mcp_watchers").upsert({
2263
2609
  device_id: deviceId,
@@ -2283,7 +2629,7 @@ async function mcpWatchCommand() {
2283
2629
  const now = (/* @__PURE__ */ new Date()).toISOString();
2284
2630
  const { error: deleteError } = await supabase.from("mcp_server_status").delete().eq("device_id", deviceId);
2285
2631
  if (deleteError) {
2286
- console.error(chalk18.red(` [debug] Failed to delete old status: ${deleteError.message}`));
2632
+ console.error(chalk20.red(` [debug] Failed to delete old status: ${deleteError.message}`));
2287
2633
  }
2288
2634
  if (rows.length > 0) {
2289
2635
  const { error: insertError } = await supabase.from("mcp_server_status").insert(rows.map((row) => ({
@@ -2292,7 +2638,7 @@ async function mcpWatchCommand() {
2292
2638
  checked_at: now
2293
2639
  })));
2294
2640
  if (insertError) {
2295
- console.error(chalk18.red(` [debug] Failed to write MCP status: ${insertError.message}`));
2641
+ console.error(chalk20.red(` [debug] Failed to write MCP status: ${insertError.message}`));
2296
2642
  }
2297
2643
  }
2298
2644
  await supabase.from("mcp_watchers").update({ last_heartbeat: now }).eq("device_id", deviceId).eq("pid", myPid);
@@ -2330,7 +2676,7 @@ async function mcpWatchCommand() {
2330
2676
  clearInterval(interval);
2331
2677
  clearInterval(envInterval);
2332
2678
  await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).eq("pid", myPid);
2333
- console.log(chalk18.dim("\nMCP monitor stopped."));
2679
+ console.log(chalk20.dim("\nMCP monitor stopped."));
2334
2680
  process.exit(0);
2335
2681
  };
2336
2682
  process.on("SIGINT", () => {
@@ -2361,6 +2707,7 @@ async function checkPendingRescans(supabase, deviceId, deviceName) {
2361
2707
  await supabase.from("claude_folders").update({
2362
2708
  graph_json: result.graph,
2363
2709
  orphans_json: result.orphans,
2710
+ broken_refs_json: result.brokenRefs,
2364
2711
  skills_table_json: result.skills,
2365
2712
  stale_files_json: result.staleFiles,
2366
2713
  env_manifest_json: result.envManifest,
@@ -2594,71 +2941,71 @@ init_map();
2594
2941
 
2595
2942
  // dist/commands/simulate.js
2596
2943
  init_dist();
2597
- import { join as join9 } from "node:path";
2944
+ import { join as join11 } from "node:path";
2598
2945
  import { existsSync as existsSync8 } from "node:fs";
2599
- import { homedir as homedir6 } from "node:os";
2600
- import chalk10 from "chalk";
2946
+ import { homedir as homedir7 } from "node:os";
2947
+ import chalk12 from "chalk";
2601
2948
  async function simulateCommand(prompt) {
2602
2949
  const projectRoot = process.cwd();
2603
- console.log(chalk10.blue(`Simulating prompt: "${prompt}"
2950
+ console.log(chalk12.blue(`Simulating prompt: "${prompt}"
2604
2951
  `));
2605
- console.log(chalk10.dim("Files Claude would load:\n"));
2606
- const globalClaude = join9(homedir6(), ".claude", "CLAUDE.md");
2952
+ console.log(chalk12.dim("Files Claude would load:\n"));
2953
+ const globalClaude = join11(homedir7(), ".claude", "CLAUDE.md");
2607
2954
  if (existsSync8(globalClaude)) {
2608
- console.log(chalk10.green(" \u2713 ~/.claude/CLAUDE.md (global)"));
2955
+ console.log(chalk12.green(" \u2713 ~/.claude/CLAUDE.md (global)"));
2609
2956
  }
2610
2957
  for (const rootFile of ROOT_FILES) {
2611
- const fullPath = join9(projectRoot, rootFile);
2958
+ const fullPath = join11(projectRoot, rootFile);
2612
2959
  if (existsSync8(fullPath)) {
2613
- console.log(chalk10.green(` \u2713 ${rootFile} (project root)`));
2960
+ console.log(chalk12.green(` \u2713 ${rootFile} (project root)`));
2614
2961
  }
2615
2962
  }
2616
2963
  const settingsFiles = [
2617
- join9(homedir6(), ".claude", "settings.json"),
2618
- join9(homedir6(), ".claude", "settings.local.json"),
2619
- join9(projectRoot, ".claude", "settings.json"),
2620
- join9(projectRoot, ".claude", "settings.local.json")
2964
+ join11(homedir7(), ".claude", "settings.json"),
2965
+ join11(homedir7(), ".claude", "settings.local.json"),
2966
+ join11(projectRoot, ".claude", "settings.json"),
2967
+ join11(projectRoot, ".claude", "settings.local.json")
2621
2968
  ];
2622
2969
  for (const sf of settingsFiles) {
2623
2970
  if (existsSync8(sf)) {
2624
- const display = sf.startsWith(homedir6()) ? sf.replace(homedir6(), "~") : sf.replace(projectRoot + "/", "");
2625
- console.log(chalk10.green(` \u2713 ${display} (settings)`));
2971
+ const display = sf.startsWith(homedir7()) ? sf.replace(homedir7(), "~") : sf.replace(projectRoot + "/", "");
2972
+ console.log(chalk12.green(` \u2713 ${display} (settings)`));
2626
2973
  }
2627
2974
  }
2628
2975
  const words = prompt.split(/\s+/);
2629
2976
  for (const word of words) {
2630
2977
  const cleaned = word.replace(/['"]/g, "");
2631
- const candidatePath = join9(projectRoot, cleaned);
2978
+ const candidatePath = join11(projectRoot, cleaned);
2632
2979
  if (existsSync8(candidatePath) && cleaned.includes("/")) {
2633
- console.log(chalk10.cyan(` \u2192 ${cleaned} (referenced in prompt)`));
2980
+ console.log(chalk12.cyan(` \u2192 ${cleaned} (referenced in prompt)`));
2634
2981
  }
2635
2982
  }
2636
- console.log(chalk10.dim("\nNote: This is an approximation. Actual file loading depends on Claude Code internals."));
2983
+ console.log(chalk12.dim("\nNote: This is an approximation. Actual file loading depends on Claude Code internals."));
2637
2984
  }
2638
2985
 
2639
2986
  // dist/commands/print.js
2640
- import { join as join10 } from "node:path";
2641
- import { readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
2987
+ import { join as join12 } from "node:path";
2988
+ import { readFile as readFile8, writeFile as writeFile3 } from "node:fs/promises";
2642
2989
  import { existsSync as existsSync9 } from "node:fs";
2643
- import chalk11 from "chalk";
2990
+ import chalk13 from "chalk";
2644
2991
  async function printCommand(title) {
2645
2992
  const projectRoot = process.cwd();
2646
- const scanDataPath = join10(projectRoot, "output", "index.html");
2993
+ const scanDataPath = join12(projectRoot, "output", "index.html");
2647
2994
  if (!existsSync9(scanDataPath)) {
2648
- console.error(chalk11.red("No scan data found. Run: md4ai scan"));
2995
+ console.error(chalk13.red("No scan data found. Run: md4ai scan"));
2649
2996
  process.exit(1);
2650
2997
  }
2651
- const html = await readFile6(scanDataPath, "utf-8");
2998
+ const html = await readFile8(scanDataPath, "utf-8");
2652
2999
  const match = html.match(/<script type="application\/json" id="scan-data">([\s\S]*?)<\/script>/);
2653
3000
  if (!match) {
2654
- console.error(chalk11.red("Could not extract scan data from output/index.html"));
3001
+ console.error(chalk13.red("Could not extract scan data from output/index.html"));
2655
3002
  process.exit(1);
2656
3003
  }
2657
3004
  const result = JSON.parse(match[1]);
2658
3005
  const printHtml = generatePrintHtml(result, title);
2659
- const outputPath = join10(projectRoot, "output", `print-${Date.now()}.html`);
3006
+ const outputPath = join12(projectRoot, "output", `print-${Date.now()}.html`);
2660
3007
  await writeFile3(outputPath, printHtml, "utf-8");
2661
- console.log(chalk11.green(`Print-ready wall sheet: ${outputPath}`));
3008
+ console.log(chalk13.green(`Print-ready wall sheet: ${outputPath}`));
2662
3009
  }
2663
3010
  function escapeHtml2(text) {
2664
3011
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -2724,7 +3071,7 @@ init_scanner();
2724
3071
  init_push_toolings();
2725
3072
  init_device_utils();
2726
3073
  import { resolve as resolve4 } from "node:path";
2727
- import chalk13 from "chalk";
3074
+ import chalk15 from "chalk";
2728
3075
  async function linkCommand(projectId) {
2729
3076
  const { supabase, userId } = await getAuthenticatedClient();
2730
3077
  const cwd = resolve4(process.cwd());
@@ -2732,11 +3079,11 @@ async function linkCommand(projectId) {
2732
3079
  const osType = detectOs2();
2733
3080
  const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
2734
3081
  if (folderErr || !folder) {
2735
- console.error(chalk13.red("Project not found, or you do not have access."));
2736
- console.error(chalk13.yellow("Check the project ID in the MD4AI web dashboard."));
3082
+ console.error(chalk15.red("Project not found, or you do not have access."));
3083
+ console.error(chalk15.yellow("Check the project ID in the MD4AI web dashboard."));
2737
3084
  process.exit(1);
2738
3085
  }
2739
- console.log(chalk13.blue(`
3086
+ console.log(chalk15.blue(`
2740
3087
  Linking "${folder.name}" to this device...
2741
3088
  `));
2742
3089
  console.log(` Project: ${folder.name}`);
@@ -2760,12 +3107,12 @@ Linking "${folder.name}" to this device...
2760
3107
  path: cwd
2761
3108
  });
2762
3109
  if (pathErr) {
2763
- console.error(chalk13.red(`Failed to link: ${pathErr.message}`));
3110
+ console.error(chalk15.red(`Failed to link: ${pathErr.message}`));
2764
3111
  process.exit(1);
2765
3112
  }
2766
3113
  }
2767
- console.log(chalk13.green("\nLinked successfully."));
2768
- console.log(chalk13.blue("\nRunning initial scan...\n"));
3114
+ console.log(chalk15.green("\nLinked successfully."));
3115
+ console.log(chalk15.blue("\nRunning initial scan...\n"));
2769
3116
  const result = await scanProject(cwd);
2770
3117
  console.log(` Files: ${result.graph.nodes.length}`);
2771
3118
  console.log(` References:${result.graph.edges.length}`);
@@ -2776,6 +3123,7 @@ Linking "${folder.name}" to this device...
2776
3123
  const { error: scanErr } = await supabase.from("claude_folders").update({
2777
3124
  graph_json: result.graph,
2778
3125
  orphans_json: result.orphans,
3126
+ broken_refs_json: result.brokenRefs,
2779
3127
  skills_table_json: result.skills,
2780
3128
  stale_files_json: result.staleFiles,
2781
3129
  env_manifest_json: result.envManifest,
@@ -2783,7 +3131,7 @@ Linking "${folder.name}" to this device...
2783
3131
  data_hash: result.dataHash
2784
3132
  }).eq("id", folder.id);
2785
3133
  if (scanErr) {
2786
- console.error(chalk13.yellow(`Scan upload warning: ${scanErr.message}`));
3134
+ console.error(chalk15.yellow(`Scan upload warning: ${scanErr.message}`));
2787
3135
  }
2788
3136
  await pushToolings(supabase, folder.id, result.toolings);
2789
3137
  const configFiles = await readClaudeConfigFiles(cwd);
@@ -2797,7 +3145,7 @@ Linking "${folder.name}" to this device...
2797
3145
  last_modified: file.lastModified
2798
3146
  }, { onConflict: "folder_id,file_path" });
2799
3147
  }
2800
- console.log(chalk13.green(` Uploaded ${configFiles.length} config file(s).`));
3148
+ console.log(chalk15.green(` Uploaded ${configFiles.length} config file(s).`));
2801
3149
  }
2802
3150
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
2803
3151
  await saveState({
@@ -2806,34 +3154,34 @@ Linking "${folder.name}" to this device...
2806
3154
  lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
2807
3155
  });
2808
3156
  const projectUrl = `https://www.md4ai.com/project/${folder.id}`;
2809
- console.log(chalk13.green("\nDone! Project linked and scanned."));
2810
- console.log(chalk13.cyan(`
3157
+ console.log(chalk15.green("\nDone! Project linked and scanned."));
3158
+ console.log(chalk15.cyan(`
2811
3159
  ${projectUrl}
2812
3160
  `));
2813
- console.log(chalk13.grey('Run "md4ai scan" to rescan at any time.'));
3161
+ console.log(chalk15.grey('Run "md4ai scan" to rescan at any time.'));
2814
3162
  }
2815
3163
 
2816
3164
  // dist/commands/import-bundle.js
2817
- import { readFile as readFile7, writeFile as writeFile4, mkdir as mkdir3 } from "node:fs/promises";
2818
- import { join as join11, dirname as dirname2 } from "node:path";
3165
+ import { readFile as readFile9, writeFile as writeFile4, mkdir as mkdir3 } from "node:fs/promises";
3166
+ import { join as join13, dirname as dirname3 } from "node:path";
2819
3167
  import { existsSync as existsSync10 } from "node:fs";
2820
- import chalk14 from "chalk";
3168
+ import chalk16 from "chalk";
2821
3169
  import { confirm as confirm3, input as input6 } from "@inquirer/prompts";
2822
3170
  async function importBundleCommand(zipPath) {
2823
3171
  if (!existsSync10(zipPath)) {
2824
- console.error(chalk14.red(`File not found: ${zipPath}`));
3172
+ console.error(chalk16.red(`File not found: ${zipPath}`));
2825
3173
  process.exit(1);
2826
3174
  }
2827
3175
  const JSZip = (await import("jszip")).default;
2828
- const zipData = await readFile7(zipPath);
3176
+ const zipData = await readFile9(zipPath);
2829
3177
  const zip = await JSZip.loadAsync(zipData);
2830
3178
  const manifestFile = zip.file("manifest.json");
2831
3179
  if (!manifestFile) {
2832
- console.error(chalk14.red("Invalid bundle: missing manifest.json"));
3180
+ console.error(chalk16.red("Invalid bundle: missing manifest.json"));
2833
3181
  process.exit(1);
2834
3182
  }
2835
3183
  const manifest = JSON.parse(await manifestFile.async("string"));
2836
- console.log(chalk14.blue(`
3184
+ console.log(chalk16.blue(`
2837
3185
  Bundle: ${manifest.folderName}`));
2838
3186
  console.log(` Exported by: ${manifest.ownerEmail}`);
2839
3187
  console.log(` Exported at: ${manifest.exportedAt}`);
@@ -2847,10 +3195,10 @@ Bundle: ${manifest.folderName}`));
2847
3195
  }
2848
3196
  }
2849
3197
  if (files.length === 0) {
2850
- console.log(chalk14.yellow("No Claude config files found in bundle."));
3198
+ console.log(chalk16.yellow("No Claude config files found in bundle."));
2851
3199
  return;
2852
3200
  }
2853
- console.log(chalk14.blue(`
3201
+ console.log(chalk16.blue(`
2854
3202
  Files to extract:`));
2855
3203
  for (const f of files) {
2856
3204
  console.log(` ${f.filePath}`);
@@ -2863,34 +3211,34 @@ Files to extract:`));
2863
3211
  message: `Extract ${files.length} file(s) to ${targetDir}?`
2864
3212
  });
2865
3213
  if (!proceed) {
2866
- console.log(chalk14.yellow("Cancelled."));
3214
+ console.log(chalk16.yellow("Cancelled."));
2867
3215
  return;
2868
3216
  }
2869
3217
  for (const file of files) {
2870
- const fullPath = join11(targetDir, file.filePath);
2871
- const dir = dirname2(fullPath);
3218
+ const fullPath = join13(targetDir, file.filePath);
3219
+ const dir = dirname3(fullPath);
2872
3220
  if (!existsSync10(dir)) {
2873
3221
  await mkdir3(dir, { recursive: true });
2874
3222
  }
2875
3223
  await writeFile4(fullPath, file.content, "utf-8");
2876
- console.log(chalk14.green(` \u2713 ${file.filePath}`));
3224
+ console.log(chalk16.green(` \u2713 ${file.filePath}`));
2877
3225
  }
2878
- console.log(chalk14.green(`
3226
+ console.log(chalk16.green(`
2879
3227
  Done! ${files.length} file(s) extracted to ${targetDir}`));
2880
3228
  }
2881
3229
 
2882
3230
  // dist/commands/admin-update-tool.js
2883
3231
  init_auth();
2884
- import chalk15 from "chalk";
3232
+ import chalk17 from "chalk";
2885
3233
  var VALID_CATEGORIES = ["framework", "runtime", "cli", "mcp", "package", "database", "other"];
2886
3234
  async function adminUpdateToolCommand(options) {
2887
3235
  const { supabase } = await getAuthenticatedClient();
2888
3236
  if (!options.name) {
2889
- console.error(chalk15.red("--name is required."));
3237
+ console.error(chalk17.red("--name is required."));
2890
3238
  process.exit(1);
2891
3239
  }
2892
3240
  if (options.category && !VALID_CATEGORIES.includes(options.category)) {
2893
- console.error(chalk15.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
3241
+ console.error(chalk17.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
2894
3242
  process.exit(1);
2895
3243
  }
2896
3244
  const { data: existing } = await supabase.from("tools_registry").select("id, name, display_name, category").eq("name", options.name).maybeSingle();
@@ -2912,13 +3260,13 @@ async function adminUpdateToolCommand(options) {
2912
3260
  updates.notes = options.notes;
2913
3261
  const { error } = await supabase.from("tools_registry").update(updates).eq("id", existing.id);
2914
3262
  if (error) {
2915
- console.error(chalk15.red(`Failed to update: ${error.message}`));
3263
+ console.error(chalk17.red(`Failed to update: ${error.message}`));
2916
3264
  process.exit(1);
2917
3265
  }
2918
- console.log(chalk15.green(`Updated "${existing.display_name}" in the registry.`));
3266
+ console.log(chalk17.green(`Updated "${existing.display_name}" in the registry.`));
2919
3267
  } else {
2920
3268
  if (!options.display || !options.category) {
2921
- console.error(chalk15.red("New tools require --display and --category."));
3269
+ console.error(chalk17.red("New tools require --display and --category."));
2922
3270
  process.exit(1);
2923
3271
  }
2924
3272
  const { error } = await supabase.from("tools_registry").insert({
@@ -2932,25 +3280,25 @@ async function adminUpdateToolCommand(options) {
2932
3280
  notes: options.notes ?? null
2933
3281
  });
2934
3282
  if (error) {
2935
- console.error(chalk15.red(`Failed to create: ${error.message}`));
3283
+ console.error(chalk17.red(`Failed to create: ${error.message}`));
2936
3284
  process.exit(1);
2937
3285
  }
2938
- console.log(chalk15.green(`Added "${options.display}" to the registry.`));
3286
+ console.log(chalk17.green(`Added "${options.display}" to the registry.`));
2939
3287
  }
2940
3288
  }
2941
3289
 
2942
3290
  // dist/commands/admin-list-tools.js
2943
3291
  init_auth();
2944
- import chalk16 from "chalk";
3292
+ import chalk18 from "chalk";
2945
3293
  async function adminListToolsCommand() {
2946
3294
  const { supabase } = await getAuthenticatedClient();
2947
3295
  const { data: tools, error } = await supabase.from("tools_registry").select("*").order("category").order("display_name");
2948
3296
  if (error) {
2949
- console.error(chalk16.red(`Failed to fetch tools: ${error.message}`));
3297
+ console.error(chalk18.red(`Failed to fetch tools: ${error.message}`));
2950
3298
  process.exit(1);
2951
3299
  }
2952
3300
  if (!tools?.length) {
2953
- console.log(chalk16.yellow("No tools in the registry."));
3301
+ console.log(chalk18.yellow("No tools in the registry."));
2954
3302
  return;
2955
3303
  }
2956
3304
  const nameW = Math.max(16, ...tools.map((t) => t.display_name.length));
@@ -2964,7 +3312,7 @@ async function adminListToolsCommand() {
2964
3312
  "Beta".padEnd(betaW),
2965
3313
  "Last Checked"
2966
3314
  ].join(" ");
2967
- console.log(chalk16.bold(header));
3315
+ console.log(chalk18.bold(header));
2968
3316
  console.log("\u2500".repeat(header.length));
2969
3317
  for (const tool of tools) {
2970
3318
  const lastChecked = tool.updated_at ? formatRelative(new Date(tool.updated_at)) : "\u2014";
@@ -2977,7 +3325,7 @@ async function adminListToolsCommand() {
2977
3325
  ].join(" ");
2978
3326
  console.log(row);
2979
3327
  }
2980
- console.log(chalk16.grey(`
3328
+ console.log(chalk18.grey(`
2981
3329
  ${tools.length} tool(s) in registry.`));
2982
3330
  }
2983
3331
  function formatRelative(date) {
@@ -2995,7 +3343,7 @@ function formatRelative(date) {
2995
3343
 
2996
3344
  // dist/commands/admin-fetch-versions.js
2997
3345
  init_auth();
2998
- import chalk17 from "chalk";
3346
+ import chalk19 from "chalk";
2999
3347
 
3000
3348
  // dist/commands/version-sources.js
3001
3349
  var VERSION_SOURCES = {
@@ -3024,11 +3372,11 @@ async function adminFetchVersionsCommand() {
3024
3372
  const { supabase } = await getAuthenticatedClient();
3025
3373
  const { data: tools, error } = await supabase.from("tools_registry").select("*").order("display_name");
3026
3374
  if (error) {
3027
- console.error(chalk17.red(`Failed to fetch tools: ${error.message}`));
3375
+ console.error(chalk19.red(`Failed to fetch tools: ${error.message}`));
3028
3376
  process.exit(1);
3029
3377
  }
3030
3378
  if (!tools?.length) {
3031
- console.log(chalk17.yellow("No tools in the registry."));
3379
+ console.log(chalk19.yellow("No tools in the registry."));
3032
3380
  }
3033
3381
  const { data: allProjectToolings } = await supabase.from("project_toolings").select("tool_name, detection_source").is("tool_id", null);
3034
3382
  const registeredNames = new Set((tools ?? []).map((t) => t.name));
@@ -3039,7 +3387,7 @@ async function adminFetchVersionsCommand() {
3039
3387
  }
3040
3388
  }
3041
3389
  if (unregisteredNames.size > 0) {
3042
- console.log(chalk17.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
3390
+ console.log(chalk19.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
3043
3391
  `));
3044
3392
  for (const name of unregisteredNames) {
3045
3393
  const displayName = name;
@@ -3057,10 +3405,10 @@ async function adminFetchVersionsCommand() {
3057
3405
  }
3058
3406
  }
3059
3407
  if (!tools?.length) {
3060
- console.log(chalk17.yellow("No tools to fetch."));
3408
+ console.log(chalk19.yellow("No tools to fetch."));
3061
3409
  return;
3062
3410
  }
3063
- console.log(chalk17.bold(`Fetching versions for ${tools.length} tool(s)...
3411
+ console.log(chalk19.bold(`Fetching versions for ${tools.length} tool(s)...
3064
3412
  `));
3065
3413
  const results = await Promise.all(tools.map(async (tool) => {
3066
3414
  const source = VERSION_SOURCES[tool.name] ?? { type: "npm", package: tool.name };
@@ -3099,10 +3447,10 @@ async function adminFetchVersionsCommand() {
3099
3447
  "Beta".padEnd(betaW),
3100
3448
  "Status".padEnd(statusW)
3101
3449
  ].join(" ");
3102
- console.log(chalk17.bold(header));
3450
+ console.log(chalk19.bold(header));
3103
3451
  console.log("\u2500".repeat(header.length));
3104
3452
  for (const result of results) {
3105
- const statusColour = result.status === "updated" ? chalk17.green : result.status === "unchanged" ? chalk17.grey : result.status === "failed" ? chalk17.red : chalk17.yellow;
3453
+ const statusColour = result.status === "updated" ? chalk19.green : result.status === "unchanged" ? chalk19.grey : result.status === "failed" ? chalk19.red : chalk19.yellow;
3106
3454
  const row = [
3107
3455
  result.displayName.padEnd(nameW),
3108
3456
  (result.stable ?? "\u2014").padEnd(stableW),
@@ -3115,8 +3463,8 @@ async function adminFetchVersionsCommand() {
3115
3463
  const unchanged = results.filter((r) => r.status === "unchanged").length;
3116
3464
  const failed = results.filter((r) => r.status === "failed").length;
3117
3465
  const noSource = results.filter((r) => r.status === "no source").length;
3118
- console.log(chalk17.grey(`
3119
- ${results.length} tool(s): `) + chalk17.green(`${updated} updated`) + ", " + chalk17.grey(`${unchanged} unchanged`) + ", " + chalk17.red(`${failed} failed`) + ", " + chalk17.yellow(`${noSource} no source`));
3466
+ console.log(chalk19.grey(`
3467
+ ${results.length} tool(s): `) + chalk19.green(`${updated} updated`) + ", " + chalk19.grey(`${unchanged} unchanged`) + ", " + chalk19.red(`${failed} failed`) + ", " + chalk19.yellow(`${noSource} no source`));
3120
3468
  }
3121
3469
  async function fetchVersions(source) {
3122
3470
  const controller = new AbortController();
@@ -3174,10 +3522,10 @@ async function fetchGitHubVersions(repo, signal) {
3174
3522
  init_mcp_watch();
3175
3523
 
3176
3524
  // dist/commands/init-manifest.js
3177
- import { resolve as resolve5, join as join13, relative as relative3, dirname as dirname3 } from "node:path";
3178
- import { readFile as readFile9, writeFile as writeFile5, mkdir as mkdir4 } from "node:fs/promises";
3525
+ import { resolve as resolve5, join as join15, relative as relative4, dirname as dirname4 } from "node:path";
3526
+ import { readFile as readFile11, writeFile as writeFile5, mkdir as mkdir4 } from "node:fs/promises";
3179
3527
  import { existsSync as existsSync12 } from "node:fs";
3180
- import chalk19 from "chalk";
3528
+ import chalk21 from "chalk";
3181
3529
  var SECRET_PATTERNS = [
3182
3530
  /_KEY$/i,
3183
3531
  /_SECRET$/i,
@@ -3193,7 +3541,7 @@ function isLikelySecret(name) {
3193
3541
  async function discoverEnvFiles(projectRoot) {
3194
3542
  const apps = [];
3195
3543
  for (const envName of [".env", ".env.local", ".env.example"]) {
3196
- const envPath = join13(projectRoot, envName);
3544
+ const envPath = join15(projectRoot, envName);
3197
3545
  if (existsSync12(envPath)) {
3198
3546
  const vars = await extractVarNames(envPath);
3199
3547
  if (vars.length > 0) {
@@ -3204,11 +3552,11 @@ async function discoverEnvFiles(projectRoot) {
3204
3552
  }
3205
3553
  const subdirs = ["web", "cli", "api", "app", "server", "packages"];
3206
3554
  for (const sub of subdirs) {
3207
- const subDir = join13(projectRoot, sub);
3555
+ const subDir = join15(projectRoot, sub);
3208
3556
  if (!existsSync12(subDir))
3209
3557
  continue;
3210
3558
  for (const envName of [".env.local", ".env", ".env.example"]) {
3211
- const envPath = join13(subDir, envName);
3559
+ const envPath = join15(subDir, envName);
3212
3560
  if (existsSync12(envPath)) {
3213
3561
  const vars = await extractVarNames(envPath);
3214
3562
  if (vars.length > 0) {
@@ -3225,7 +3573,7 @@ async function discoverEnvFiles(projectRoot) {
3225
3573
  return apps;
3226
3574
  }
3227
3575
  async function extractVarNames(envPath) {
3228
- const content = await readFile9(envPath, "utf-8");
3576
+ const content = await readFile11(envPath, "utf-8");
3229
3577
  const vars = [];
3230
3578
  for (const line of content.split("\n")) {
3231
3579
  const trimmed = line.trim();
@@ -3270,38 +3618,38 @@ function generateManifest(apps) {
3270
3618
  }
3271
3619
  async function initManifestCommand() {
3272
3620
  const projectRoot = resolve5(process.cwd());
3273
- const manifestPath = join13(projectRoot, "docs", "reference", "env-manifest.md");
3621
+ const manifestPath = join15(projectRoot, "docs", "reference", "env-manifest.md");
3274
3622
  if (existsSync12(manifestPath)) {
3275
- console.log(chalk19.yellow(`Manifest already exists: ${relative3(projectRoot, manifestPath)}`));
3276
- console.log(chalk19.dim("Edit it directly to make changes."));
3623
+ console.log(chalk21.yellow(`Manifest already exists: ${relative4(projectRoot, manifestPath)}`));
3624
+ console.log(chalk21.dim("Edit it directly to make changes."));
3277
3625
  return;
3278
3626
  }
3279
- console.log(chalk19.blue("Scanning for .env files...\n"));
3627
+ console.log(chalk21.blue("Scanning for .env files...\n"));
3280
3628
  const apps = await discoverEnvFiles(projectRoot);
3281
3629
  if (apps.length === 0) {
3282
- console.log(chalk19.yellow("No .env files found. Create a manifest manually at:"));
3283
- console.log(chalk19.cyan(` docs/reference/env-manifest.md`));
3630
+ console.log(chalk21.yellow("No .env files found. Create a manifest manually at:"));
3631
+ console.log(chalk21.cyan(` docs/reference/env-manifest.md`));
3284
3632
  return;
3285
3633
  }
3286
3634
  for (const app of apps) {
3287
- console.log(` ${chalk19.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
3635
+ console.log(` ${chalk21.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
3288
3636
  }
3289
3637
  const content = generateManifest(apps);
3290
- const dir = dirname3(manifestPath);
3638
+ const dir = dirname4(manifestPath);
3291
3639
  if (!existsSync12(dir)) {
3292
3640
  await mkdir4(dir, { recursive: true });
3293
3641
  }
3294
3642
  await writeFile5(manifestPath, content, "utf-8");
3295
- console.log(chalk19.green(`
3296
- Manifest created: ${relative3(projectRoot, manifestPath)}`));
3297
- console.log(chalk19.dim("Review and edit the file \u2014 it is your source of truth."));
3298
- console.log(chalk19.dim("Then run `md4ai scan` to verify against your environments."));
3643
+ console.log(chalk21.green(`
3644
+ Manifest created: ${relative4(projectRoot, manifestPath)}`));
3645
+ console.log(chalk21.dim("Review and edit the file \u2014 it is your source of truth."));
3646
+ console.log(chalk21.dim("Then run `md4ai scan` to verify against your environments."));
3299
3647
  }
3300
3648
 
3301
3649
  // dist/commands/update.js
3302
3650
  init_check_update();
3303
3651
  init_config();
3304
- import chalk20 from "chalk";
3652
+ import chalk22 from "chalk";
3305
3653
  import { execFileSync as execFileSync6, spawn } from "node:child_process";
3306
3654
  async function fetchLatestVersion() {
3307
3655
  try {
@@ -3350,13 +3698,13 @@ function spawnPostUpdate() {
3350
3698
  }
3351
3699
  async function postUpdateFlow() {
3352
3700
  const { confirm: confirm4 } = await import("@inquirer/prompts");
3353
- console.log(chalk20.green(`
3701
+ console.log(chalk22.green(`
3354
3702
  md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.
3355
3703
  `));
3356
3704
  const creds = await loadCredentials();
3357
3705
  const isLoggedIn = !!creds?.accessToken && Date.now() < creds.expiresAt;
3358
3706
  if (!isLoggedIn) {
3359
- console.log(chalk20.yellow(" You are not logged in.\n"));
3707
+ console.log(chalk22.yellow(" You are not logged in.\n"));
3360
3708
  const wantLogin = await confirm4({
3361
3709
  message: "Log in now?",
3362
3710
  default: true
@@ -3367,7 +3715,7 @@ async function postUpdateFlow() {
3367
3715
  await loginCommand2();
3368
3716
  console.log("");
3369
3717
  } else {
3370
- console.log(chalk20.dim("\nRun md4ai login when you're ready.\n"));
3718
+ console.log(chalk22.dim("\nRun md4ai login when you're ready.\n"));
3371
3719
  return;
3372
3720
  }
3373
3721
  }
@@ -3401,7 +3749,7 @@ async function postUpdateFlow() {
3401
3749
  const { mcpWatchCommand: mcpWatchCommand2 } = await Promise.resolve().then(() => (init_mcp_watch(), mcp_watch_exports));
3402
3750
  await mcpWatchCommand2();
3403
3751
  } else {
3404
- console.log(chalk20.dim("\nYou can start monitoring later with: md4ai mcp-watch\n"));
3752
+ console.log(chalk22.dim("\nYou can start monitoring later with: md4ai mcp-watch\n"));
3405
3753
  }
3406
3754
  }
3407
3755
  async function updateCommand(options) {
@@ -3409,19 +3757,19 @@ async function updateCommand(options) {
3409
3757
  await postUpdateFlow();
3410
3758
  return;
3411
3759
  }
3412
- console.log(chalk20.blue(`
3760
+ console.log(chalk22.blue(`
3413
3761
  Current version: v${CURRENT_VERSION}`));
3414
- console.log(chalk20.dim(" Checking for updates...\n"));
3762
+ console.log(chalk22.dim(" Checking for updates...\n"));
3415
3763
  const latest = await fetchLatestVersion();
3416
3764
  if (!latest) {
3417
- console.log(chalk20.yellow(" Could not reach the npm registry. Check your internet connection."));
3765
+ console.log(chalk22.yellow(" Could not reach the npm registry. Check your internet connection."));
3418
3766
  process.exit(1);
3419
3767
  }
3420
3768
  if (!isNewer2(latest, CURRENT_VERSION)) {
3421
3769
  await postUpdateFlow();
3422
3770
  return;
3423
3771
  }
3424
- console.log(chalk20.white(" Update available: ") + chalk20.dim(`v${CURRENT_VERSION}`) + chalk20.white(" \u2192 ") + chalk20.green.bold(`v${latest}
3772
+ console.log(chalk22.white(" Update available: ") + chalk22.dim(`v${CURRENT_VERSION}`) + chalk22.white(" \u2192 ") + chalk22.green.bold(`v${latest}
3425
3773
  `));
3426
3774
  const { confirm: confirm4 } = await import("@inquirer/prompts");
3427
3775
  const wantUpdate = await confirm4({
@@ -3429,20 +3777,38 @@ async function updateCommand(options) {
3429
3777
  default: true
3430
3778
  });
3431
3779
  if (!wantUpdate) {
3432
- console.log(chalk20.dim("\nUpdate skipped.\n"));
3780
+ console.log(chalk22.dim("\nUpdate skipped.\n"));
3433
3781
  return;
3434
3782
  }
3435
- console.log(chalk20.blue("\n Installing...\n"));
3783
+ console.log(chalk22.blue("\n Installing...\n"));
3436
3784
  const ok = runNpmInstall();
3437
3785
  if (!ok) {
3438
- console.log(chalk20.red("\n npm install failed. Try running manually:"));
3439
- console.log(chalk20.cyan(" npm install -g md4ai\n"));
3786
+ console.log(chalk22.red("\n npm install failed. Try running manually:"));
3787
+ console.log(chalk22.cyan(" npm install -g md4ai\n"));
3440
3788
  process.exit(1);
3441
3789
  }
3442
- console.log(chalk20.green("\n Updated successfully.\n"));
3790
+ console.log(chalk22.green("\n Updated successfully.\n"));
3443
3791
  spawnPostUpdate();
3444
3792
  }
3445
3793
 
3794
+ // dist/commands/config.js
3795
+ init_config();
3796
+ import chalk23 from "chalk";
3797
+ var ALLOWED_KEYS = ["vercel-token"];
3798
+ var KEY_MAP = {
3799
+ "vercel-token": "vercelToken"
3800
+ };
3801
+ async function configSetCommand(key, value) {
3802
+ if (!ALLOWED_KEYS.includes(key)) {
3803
+ console.error(chalk23.red(`Unknown config key: ${key}`));
3804
+ console.log(` Allowed keys: ${ALLOWED_KEYS.join(", ")}`);
3805
+ process.exit(1);
3806
+ }
3807
+ const credKey = KEY_MAP[key];
3808
+ await mergeCredentials({ [credKey]: value });
3809
+ console.log(chalk23.green(`Saved ${key}.`));
3810
+ }
3811
+
3446
3812
  // dist/index.js
3447
3813
  init_check_update();
3448
3814
  var program = new Command();
@@ -3465,6 +3831,8 @@ program.command("update").description("Check for updates, install, and optionall
3465
3831
  program.command("check-update").description("Check if a newer version of md4ai is available").action(checkForUpdate);
3466
3832
  program.command("mcp-watch").description("Monitor MCP server status on this device (runs until Ctrl+C)").action(mcpWatchCommand);
3467
3833
  program.command("init-manifest").description("Scaffold a starter env-manifest.md from detected .env files").action(initManifestCommand);
3834
+ var config = program.command("config").description("Manage CLI configuration");
3835
+ config.command("set <key> <value>").description("Set a configuration value (e.g. vercel-token)").action(configSetCommand);
3468
3836
  var admin = program.command("admin").description("Admin commands for managing the tools registry");
3469
3837
  admin.command("update-tool").description("Add or update a tool in the master registry").requiredOption("--name <name>", "Canonical tool name (e.g. next, playwright)").option("--display <display>", 'Human-friendly display name (e.g. "Next.js")').option("--category <category>", "Tool category (framework|runtime|cli|mcp|package|database|other)").option("--stable <version>", "Latest stable version").option("--beta <version>", "Latest beta/RC version").option("--source <url>", "Source of truth URL for checking versions").option("--install <url>", "Download/install link").option("--notes <text>", "Compatibility notes or warnings").action(adminUpdateToolCommand);
3470
3838
  admin.command("list-tools").description("List all tools in the master registry").action(adminListToolsCommand);