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.
- package/dist/index.bundled.js +635 -267
- package/package.json +1 -1
package/dist/index.bundled.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
816
|
-
if (
|
|
817
|
-
const 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
|
|
854
|
-
import {
|
|
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 =
|
|
977
|
+
const manifestFullPath = join9(projectRoot, MANIFEST_PATH);
|
|
858
978
|
if (!existsSync5(manifestFullPath)) {
|
|
859
979
|
return null;
|
|
860
980
|
}
|
|
861
|
-
const manifestContent = await
|
|
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 =
|
|
1173
|
+
const envPath = join9(projectRoot, app.envFilePath);
|
|
963
1174
|
const varNames = /* @__PURE__ */ new Set();
|
|
964
1175
|
if (existsSync5(envPath)) {
|
|
965
|
-
const content = await
|
|
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 =
|
|
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
|
|
985
|
-
const content = await
|
|
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:
|
|
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
|
|
1273
|
+
import { join as join10, relative as relative3 } from "node:path";
|
|
1059
1274
|
import { existsSync as existsSync6 } from "node:fs";
|
|
1060
|
-
import { homedir as
|
|
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 :
|
|
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
|
|
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 =
|
|
1332
|
+
const claudeDir = join10(projectRoot, ".claude");
|
|
1096
1333
|
if (existsSync6(claudeDir)) {
|
|
1097
1334
|
await walkDir(claudeDir, projectRoot, files);
|
|
1098
1335
|
}
|
|
1099
|
-
if (existsSync6(
|
|
1336
|
+
if (existsSync6(join10(projectRoot, "CLAUDE.md"))) {
|
|
1100
1337
|
files.push("CLAUDE.md");
|
|
1101
1338
|
}
|
|
1102
|
-
if (existsSync6(
|
|
1339
|
+
if (existsSync6(join10(projectRoot, "skills.md"))) {
|
|
1103
1340
|
files.push("skills.md");
|
|
1104
1341
|
}
|
|
1105
|
-
const plansDir =
|
|
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 =
|
|
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 =
|
|
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("~",
|
|
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:
|
|
1142
|
-
const { join:
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
1410
|
+
const pkgPath = join16(projectRoot, "package.json");
|
|
1174
1411
|
if (existsSync13(pkgPath)) {
|
|
1175
1412
|
try {
|
|
1176
|
-
const pkgContent = await
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
1359
|
-
console.log(
|
|
1360
|
-
console.log(
|
|
1361
|
-
console.log(
|
|
1362
|
-
console.log(
|
|
1363
|
-
console.log(
|
|
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(
|
|
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.
|
|
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
|
|
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(
|
|
1800
|
+
console.error(chalk11.red(`Path not found: ${projectRoot}`));
|
|
1483
1801
|
process.exit(1);
|
|
1484
1802
|
}
|
|
1485
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
1859
|
+
console.log(chalk11.green(` Deleted: ${file.file_path}`));
|
|
1531
1860
|
} catch (err) {
|
|
1532
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
1564
|
-
console.log(
|
|
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(
|
|
1913
|
+
console.log(chalk11.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
1579
1914
|
}
|
|
1580
1915
|
}
|
|
1581
1916
|
} else {
|
|
1582
|
-
console.log(
|
|
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(
|
|
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(
|
|
1944
|
+
console.error(chalk11.red(`Failed to create project: ${createErr?.message}`));
|
|
1606
1945
|
return;
|
|
1607
1946
|
}
|
|
1608
1947
|
folderId = newFolder.id;
|
|
1609
|
-
console.log(
|
|
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(
|
|
1654
|
-
console.log(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2055
|
+
console.log(chalk14.green(` Done: ${device.device_name}`));
|
|
1711
2056
|
} catch (err) {
|
|
1712
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
2070
|
+
console.error(chalk14.red("Could not find last synced device/folder."));
|
|
1726
2071
|
process.exit(1);
|
|
1727
2072
|
}
|
|
1728
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
|
1760
|
-
import { join as
|
|
1761
|
-
import { homedir as
|
|
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
|
|
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 =
|
|
2174
|
+
const home = homedir8();
|
|
1829
2175
|
const entries = [];
|
|
1830
|
-
const userConfig = await readJsonSafe(
|
|
2176
|
+
const userConfig = await readJsonSafe(join14(home, ".claude.json"));
|
|
1831
2177
|
entries.push(...parseServers(userConfig, "global"));
|
|
1832
|
-
const globalMcp = await readJsonSafe(
|
|
2178
|
+
const globalMcp = await readJsonSafe(join14(home, ".claude", "mcp.json"));
|
|
1833
2179
|
entries.push(...parseServers(globalMcp, "global"));
|
|
1834
|
-
const cwdMcp = await readJsonSafe(
|
|
2180
|
+
const cwdMcp = await readJsonSafe(join14(process.cwd(), ".mcp.json"));
|
|
1835
2181
|
entries.push(...parseServers(cwdMcp, "project"));
|
|
1836
|
-
const pluginsBase =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
1938
|
-
if (
|
|
2283
|
+
function findProcessesForConfig(config2, processes) {
|
|
2284
|
+
if (config2.type === "http")
|
|
1939
2285
|
return [];
|
|
1940
|
-
const packageName =
|
|
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 (
|
|
1946
|
-
searchTerms.push(
|
|
2291
|
+
if (config2.command === "node" && config2.args?.[0]) {
|
|
2292
|
+
searchTerms.push(config2.args[0]);
|
|
1947
2293
|
}
|
|
1948
|
-
if (
|
|
1949
|
-
searchTerms.push(`mcp-server-${
|
|
1950
|
-
searchTerms.push(`${
|
|
2294
|
+
if (config2.name) {
|
|
2295
|
+
searchTerms.push(`mcp-server-${config2.name}`);
|
|
2296
|
+
searchTerms.push(`${config2.name}-mcp`);
|
|
1951
2297
|
}
|
|
1952
|
-
if ((
|
|
1953
|
-
for (const arg of
|
|
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
|
|
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(
|
|
2018
|
-
const required =
|
|
2363
|
+
function checkEnvVars(config2) {
|
|
2364
|
+
const required = config2.env ? Object.keys(config2.env) : [];
|
|
2019
2365
|
const missing = required.filter((key) => {
|
|
2020
|
-
const configValue =
|
|
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
|
|
2056
|
-
const { required, missing } = checkEnvVars(
|
|
2057
|
-
const packageName =
|
|
2058
|
-
if (
|
|
2059
|
-
const reachability = httpResults.get(
|
|
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:
|
|
2064
|
-
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:
|
|
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:
|
|
2083
|
-
config_source:
|
|
2428
|
+
server_name: config2.name,
|
|
2429
|
+
config_source: config2.source,
|
|
2084
2430
|
server_type: "stdio",
|
|
2085
|
-
command:
|
|
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 =
|
|
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(
|
|
2447
|
+
const matches = findProcessesForConfig(config2, processes);
|
|
2102
2448
|
if (matches.length === 0) {
|
|
2103
2449
|
rows.push({
|
|
2104
|
-
server_name:
|
|
2105
|
-
config_source:
|
|
2450
|
+
server_name: config2.name,
|
|
2451
|
+
config_source: config2.source,
|
|
2106
2452
|
server_type: "stdio",
|
|
2107
|
-
command:
|
|
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:
|
|
2131
|
-
config_source:
|
|
2476
|
+
server_name: config2.name,
|
|
2477
|
+
config_source: config2.source,
|
|
2132
2478
|
server_type: "stdio",
|
|
2133
|
-
command:
|
|
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(
|
|
2153
|
-
MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) +
|
|
2154
|
-
console.log(
|
|
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(
|
|
2160
|
-
console.log(
|
|
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(
|
|
2163
|
-
console.log(
|
|
2164
|
-
console.log(
|
|
2165
|
-
console.log(
|
|
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(
|
|
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 =
|
|
2190
|
-
console.log(` ${
|
|
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(
|
|
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(
|
|
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(` ${
|
|
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(
|
|
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" ?
|
|
2210
|
-
const source =
|
|
2211
|
-
const detail = 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(
|
|
2218
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
2598
|
+
console.log(chalk20.dim(" Previous watcher stopped.\n"));
|
|
2253
2599
|
}
|
|
2254
2600
|
process.stdout.write(`\x1B]0;MCP mon\x07`);
|
|
2255
|
-
console.log(
|
|
2601
|
+
console.log(chalk20.blue(`Starting MCP monitor for ${deviceName}...`));
|
|
2256
2602
|
console.log("");
|
|
2257
|
-
console.log(
|
|
2258
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
2944
|
+
import { join as join11 } from "node:path";
|
|
2598
2945
|
import { existsSync as existsSync8 } from "node:fs";
|
|
2599
|
-
import { homedir as
|
|
2600
|
-
import
|
|
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(
|
|
2950
|
+
console.log(chalk12.blue(`Simulating prompt: "${prompt}"
|
|
2604
2951
|
`));
|
|
2605
|
-
console.log(
|
|
2606
|
-
const globalClaude =
|
|
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(
|
|
2955
|
+
console.log(chalk12.green(" \u2713 ~/.claude/CLAUDE.md (global)"));
|
|
2609
2956
|
}
|
|
2610
2957
|
for (const rootFile of ROOT_FILES) {
|
|
2611
|
-
const fullPath =
|
|
2958
|
+
const fullPath = join11(projectRoot, rootFile);
|
|
2612
2959
|
if (existsSync8(fullPath)) {
|
|
2613
|
-
console.log(
|
|
2960
|
+
console.log(chalk12.green(` \u2713 ${rootFile} (project root)`));
|
|
2614
2961
|
}
|
|
2615
2962
|
}
|
|
2616
2963
|
const settingsFiles = [
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
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(
|
|
2625
|
-
console.log(
|
|
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 =
|
|
2978
|
+
const candidatePath = join11(projectRoot, cleaned);
|
|
2632
2979
|
if (existsSync8(candidatePath) && cleaned.includes("/")) {
|
|
2633
|
-
console.log(
|
|
2980
|
+
console.log(chalk12.cyan(` \u2192 ${cleaned} (referenced in prompt)`));
|
|
2634
2981
|
}
|
|
2635
2982
|
}
|
|
2636
|
-
console.log(
|
|
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
|
|
2641
|
-
import { readFile as
|
|
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
|
|
2990
|
+
import chalk13 from "chalk";
|
|
2644
2991
|
async function printCommand(title) {
|
|
2645
2992
|
const projectRoot = process.cwd();
|
|
2646
|
-
const scanDataPath =
|
|
2993
|
+
const scanDataPath = join12(projectRoot, "output", "index.html");
|
|
2647
2994
|
if (!existsSync9(scanDataPath)) {
|
|
2648
|
-
console.error(
|
|
2995
|
+
console.error(chalk13.red("No scan data found. Run: md4ai scan"));
|
|
2649
2996
|
process.exit(1);
|
|
2650
2997
|
}
|
|
2651
|
-
const html = await
|
|
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(
|
|
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 =
|
|
3006
|
+
const outputPath = join12(projectRoot, "output", `print-${Date.now()}.html`);
|
|
2660
3007
|
await writeFile3(outputPath, printHtml, "utf-8");
|
|
2661
|
-
console.log(
|
|
3008
|
+
console.log(chalk13.green(`Print-ready wall sheet: ${outputPath}`));
|
|
2662
3009
|
}
|
|
2663
3010
|
function escapeHtml2(text) {
|
|
2664
3011
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -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
|
|
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(
|
|
2736
|
-
console.error(
|
|
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(
|
|
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(
|
|
3110
|
+
console.error(chalk15.red(`Failed to link: ${pathErr.message}`));
|
|
2764
3111
|
process.exit(1);
|
|
2765
3112
|
}
|
|
2766
3113
|
}
|
|
2767
|
-
console.log(
|
|
2768
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
2810
|
-
console.log(
|
|
3157
|
+
console.log(chalk15.green("\nDone! Project linked and scanned."));
|
|
3158
|
+
console.log(chalk15.cyan(`
|
|
2811
3159
|
${projectUrl}
|
|
2812
3160
|
`));
|
|
2813
|
-
console.log(
|
|
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
|
|
2818
|
-
import { join as
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
3198
|
+
console.log(chalk16.yellow("No Claude config files found in bundle."));
|
|
2851
3199
|
return;
|
|
2852
3200
|
}
|
|
2853
|
-
console.log(
|
|
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(
|
|
3214
|
+
console.log(chalk16.yellow("Cancelled."));
|
|
2867
3215
|
return;
|
|
2868
3216
|
}
|
|
2869
3217
|
for (const file of files) {
|
|
2870
|
-
const fullPath =
|
|
2871
|
-
const dir =
|
|
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(
|
|
3224
|
+
console.log(chalk16.green(` \u2713 ${file.filePath}`));
|
|
2877
3225
|
}
|
|
2878
|
-
console.log(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
3263
|
+
console.error(chalk17.red(`Failed to update: ${error.message}`));
|
|
2916
3264
|
process.exit(1);
|
|
2917
3265
|
}
|
|
2918
|
-
console.log(
|
|
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(
|
|
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(
|
|
3283
|
+
console.error(chalk17.red(`Failed to create: ${error.message}`));
|
|
2936
3284
|
process.exit(1);
|
|
2937
3285
|
}
|
|
2938
|
-
console.log(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3408
|
+
console.log(chalk19.yellow("No tools to fetch."));
|
|
3061
3409
|
return;
|
|
3062
3410
|
}
|
|
3063
|
-
console.log(
|
|
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(
|
|
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" ?
|
|
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(
|
|
3119
|
-
${results.length} tool(s): `) +
|
|
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
|
|
3178
|
-
import { readFile as
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
3621
|
+
const manifestPath = join15(projectRoot, "docs", "reference", "env-manifest.md");
|
|
3274
3622
|
if (existsSync12(manifestPath)) {
|
|
3275
|
-
console.log(
|
|
3276
|
-
console.log(
|
|
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(
|
|
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(
|
|
3283
|
-
console.log(
|
|
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(` ${
|
|
3635
|
+
console.log(` ${chalk21.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
|
|
3288
3636
|
}
|
|
3289
3637
|
const content = generateManifest(apps);
|
|
3290
|
-
const dir =
|
|
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(
|
|
3296
|
-
Manifest created: ${
|
|
3297
|
-
console.log(
|
|
3298
|
-
console.log(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3760
|
+
console.log(chalk22.blue(`
|
|
3413
3761
|
Current version: v${CURRENT_VERSION}`));
|
|
3414
|
-
console.log(
|
|
3762
|
+
console.log(chalk22.dim(" Checking for updates...\n"));
|
|
3415
3763
|
const latest = await fetchLatestVersion();
|
|
3416
3764
|
if (!latest) {
|
|
3417
|
-
console.log(
|
|
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(
|
|
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(
|
|
3780
|
+
console.log(chalk22.dim("\nUpdate skipped.\n"));
|
|
3433
3781
|
return;
|
|
3434
3782
|
}
|
|
3435
|
-
console.log(
|
|
3783
|
+
console.log(chalk22.blue("\n Installing...\n"));
|
|
3436
3784
|
const ok = runNpmInstall();
|
|
3437
3785
|
if (!ok) {
|
|
3438
|
-
console.log(
|
|
3439
|
-
console.log(
|
|
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(
|
|
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);
|