md4ai 0.9.3 → 0.9.5
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 +599 -268
- 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");
|
|
@@ -812,9 +819,9 @@ async function detectFromMcpSettings(projectRoot) {
|
|
|
812
819
|
continue;
|
|
813
820
|
seen.add(serverName);
|
|
814
821
|
let version = null;
|
|
815
|
-
const
|
|
816
|
-
if (
|
|
817
|
-
const args =
|
|
822
|
+
const config2 = mcpServers[serverName];
|
|
823
|
+
if (config2?.command && typeof config2.command === "string") {
|
|
824
|
+
const args = config2.args ?? [];
|
|
818
825
|
for (const arg of args) {
|
|
819
826
|
const versionMatch = arg.match(/@(\d+\.\d+[^\s]*?)$/);
|
|
820
827
|
if (versionMatch) {
|
|
@@ -849,16 +856,110 @@ var init_tooling_detector = __esm({
|
|
|
849
856
|
}
|
|
850
857
|
});
|
|
851
858
|
|
|
859
|
+
// dist/vercel/auth.js
|
|
860
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
861
|
+
import { join as join7 } from "node:path";
|
|
862
|
+
import { homedir as homedir5 } from "node:os";
|
|
863
|
+
async function resolveVercelToken() {
|
|
864
|
+
const creds = await loadCredentials();
|
|
865
|
+
if (creds?.vercelToken)
|
|
866
|
+
return creds.vercelToken;
|
|
867
|
+
const possiblePaths = [
|
|
868
|
+
join7(homedir5(), ".config", "com.vercel.cli", "auth.json"),
|
|
869
|
+
join7(homedir5(), ".local", "share", "com.vercel.cli", "auth.json")
|
|
870
|
+
];
|
|
871
|
+
for (const authPath of possiblePaths) {
|
|
872
|
+
try {
|
|
873
|
+
const data = await readFile5(authPath, "utf-8");
|
|
874
|
+
const parsed = JSON.parse(data);
|
|
875
|
+
if (parsed.token)
|
|
876
|
+
return parsed.token;
|
|
877
|
+
} catch {
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return null;
|
|
881
|
+
}
|
|
882
|
+
var init_auth2 = __esm({
|
|
883
|
+
"dist/vercel/auth.js"() {
|
|
884
|
+
"use strict";
|
|
885
|
+
init_config();
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
// dist/vercel/discover-projects.js
|
|
890
|
+
import { readFile as readFile6, glob } from "node:fs/promises";
|
|
891
|
+
import { join as join8, dirname as dirname2, relative } from "node:path";
|
|
892
|
+
async function discoverVercelProjects(projectRoot) {
|
|
893
|
+
const found = /* @__PURE__ */ new Map();
|
|
894
|
+
for await (const filePath of glob(join8(projectRoot, "**/.vercel/project.json"))) {
|
|
895
|
+
try {
|
|
896
|
+
const data = await readFile6(filePath, "utf-8");
|
|
897
|
+
const parsed = JSON.parse(data);
|
|
898
|
+
if (!parsed.projectId || !parsed.orgId)
|
|
899
|
+
continue;
|
|
900
|
+
const vercelDir = dirname2(filePath);
|
|
901
|
+
const parentDir = dirname2(vercelDir);
|
|
902
|
+
const relDir = relative(projectRoot, parentDir);
|
|
903
|
+
const label = parsed.projectName || (relDir === "" ? "root" : relDir.split("/").pop() || "unknown");
|
|
904
|
+
const existing = found.get(parsed.projectId);
|
|
905
|
+
if (!existing || relDir.length > 0) {
|
|
906
|
+
found.set(parsed.projectId, {
|
|
907
|
+
projectId: parsed.projectId,
|
|
908
|
+
orgId: parsed.orgId,
|
|
909
|
+
projectName: label
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
} catch {
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return [...found.values()].sort((a, b) => a.projectName.localeCompare(b.projectName));
|
|
916
|
+
}
|
|
917
|
+
var init_discover_projects = __esm({
|
|
918
|
+
"dist/vercel/discover-projects.js"() {
|
|
919
|
+
"use strict";
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
// dist/vercel/fetch-env-vars.js
|
|
924
|
+
async function fetchVercelEnvVars(projectId, orgId, token) {
|
|
925
|
+
const url = new URL(`https://api.vercel.com/v9/projects/${projectId}/env`);
|
|
926
|
+
url.searchParams.set("teamId", orgId);
|
|
927
|
+
const res = await fetch(url.toString(), {
|
|
928
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
929
|
+
});
|
|
930
|
+
if (!res.ok) {
|
|
931
|
+
const body = await res.text().catch(() => "");
|
|
932
|
+
throw new Error(`Vercel API ${res.status}: ${body.slice(0, 200)}`);
|
|
933
|
+
}
|
|
934
|
+
const data = await res.json();
|
|
935
|
+
const envs = data.envs ?? [];
|
|
936
|
+
return envs.map((e) => ({
|
|
937
|
+
key: e.key,
|
|
938
|
+
targets: e.target ?? ["production", "preview", "development"],
|
|
939
|
+
type: e.type ?? "plain",
|
|
940
|
+
inManifest: false
|
|
941
|
+
// populated later by the scanner
|
|
942
|
+
}));
|
|
943
|
+
}
|
|
944
|
+
var init_fetch_env_vars = __esm({
|
|
945
|
+
"dist/vercel/fetch-env-vars.js"() {
|
|
946
|
+
"use strict";
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
|
|
852
950
|
// dist/scanner/env-manifest-scanner.js
|
|
853
|
-
import { readFile as
|
|
854
|
-
import {
|
|
951
|
+
import { readFile as readFile7, glob as glob2 } from "node:fs/promises";
|
|
952
|
+
import { execFile } from "node:child_process";
|
|
953
|
+
import { promisify } from "node:util";
|
|
954
|
+
import { join as join9, relative as relative2 } from "node:path";
|
|
855
955
|
import { existsSync as existsSync5 } from "node:fs";
|
|
956
|
+
import chalk8 from "chalk";
|
|
856
957
|
async function scanEnvManifest(projectRoot) {
|
|
857
|
-
const manifestFullPath =
|
|
958
|
+
const manifestFullPath = join9(projectRoot, MANIFEST_PATH);
|
|
858
959
|
if (!existsSync5(manifestFullPath)) {
|
|
859
960
|
return null;
|
|
860
961
|
}
|
|
861
|
-
const manifestContent = await
|
|
962
|
+
const manifestContent = await readFile7(manifestFullPath, "utf-8");
|
|
862
963
|
const apps = parseApps(manifestContent);
|
|
863
964
|
const variables = parseVariables(manifestContent);
|
|
864
965
|
const localPresence = await checkLocalEnvFiles(projectRoot, apps);
|
|
@@ -870,6 +971,8 @@ async function scanEnvManifest(projectRoot) {
|
|
|
870
971
|
}
|
|
871
972
|
const workflowRefs = await parseWorkflowSecrets(projectRoot);
|
|
872
973
|
const drift = computeDrift(variables, apps, localPresence, workflowRefs);
|
|
974
|
+
const vercelDiscovered = await checkVercelEnvVars(projectRoot, variables);
|
|
975
|
+
const githubRepoSlug = await checkGitHubSecrets(projectRoot, variables);
|
|
873
976
|
return {
|
|
874
977
|
manifestFound: true,
|
|
875
978
|
manifestPath: MANIFEST_PATH,
|
|
@@ -877,9 +980,98 @@ async function scanEnvManifest(projectRoot) {
|
|
|
877
980
|
variables,
|
|
878
981
|
drift,
|
|
879
982
|
workflowRefs,
|
|
983
|
+
vercelDiscovered: vercelDiscovered ?? void 0,
|
|
984
|
+
githubRepoSlug: githubRepoSlug ?? void 0,
|
|
880
985
|
checkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
881
986
|
};
|
|
882
987
|
}
|
|
988
|
+
async function checkVercelEnvVars(projectRoot, variables) {
|
|
989
|
+
const token = await resolveVercelToken();
|
|
990
|
+
if (!token) {
|
|
991
|
+
console.log(chalk8.dim(" Vercel checks skipped (no token configured)"));
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
994
|
+
const projects = await discoverVercelProjects(projectRoot);
|
|
995
|
+
if (projects.length === 0)
|
|
996
|
+
return null;
|
|
997
|
+
console.log(chalk8.dim(` Checking ${projects.length} Vercel project(s)...`));
|
|
998
|
+
const discovered = [];
|
|
999
|
+
const manifestVarNames = new Set(variables.map((v) => v.name));
|
|
1000
|
+
for (const proj of projects) {
|
|
1001
|
+
try {
|
|
1002
|
+
const vars = await fetchVercelEnvVars(proj.projectId, proj.orgId, token);
|
|
1003
|
+
for (const v of vars) {
|
|
1004
|
+
v.inManifest = manifestVarNames.has(v.key);
|
|
1005
|
+
}
|
|
1006
|
+
discovered.push({
|
|
1007
|
+
projectId: proj.projectId,
|
|
1008
|
+
projectName: proj.projectName,
|
|
1009
|
+
orgId: proj.orgId,
|
|
1010
|
+
vars
|
|
1011
|
+
});
|
|
1012
|
+
const vercelKeySet = new Set(vars.map((v) => v.key));
|
|
1013
|
+
for (const mv of variables) {
|
|
1014
|
+
if (!mv.vercelStatus)
|
|
1015
|
+
mv.vercelStatus = {};
|
|
1016
|
+
mv.vercelStatus[proj.projectName] = vercelKeySet.has(mv.name) ? "present" : "missing";
|
|
1017
|
+
}
|
|
1018
|
+
console.log(chalk8.dim(` ${proj.projectName}: ${vars.length} var(s)`));
|
|
1019
|
+
} catch (err) {
|
|
1020
|
+
console.log(chalk8.yellow(` ${proj.projectName}: ${err instanceof Error ? err.message : "API error"}`));
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return discovered.length > 0 ? discovered : null;
|
|
1024
|
+
}
|
|
1025
|
+
async function detectGitHubRepoSlug(projectRoot) {
|
|
1026
|
+
try {
|
|
1027
|
+
const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], { cwd: projectRoot });
|
|
1028
|
+
const url = stdout.trim();
|
|
1029
|
+
const sshMatch = url.match(/github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
1030
|
+
if (sshMatch)
|
|
1031
|
+
return sshMatch[1];
|
|
1032
|
+
return null;
|
|
1033
|
+
} catch {
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
async function listGitHubSecretNames(repoSlug) {
|
|
1038
|
+
try {
|
|
1039
|
+
const { stdout } = await execFileAsync("gh", [
|
|
1040
|
+
"secret",
|
|
1041
|
+
"list",
|
|
1042
|
+
"--repo",
|
|
1043
|
+
repoSlug,
|
|
1044
|
+
"--json",
|
|
1045
|
+
"name",
|
|
1046
|
+
"-q",
|
|
1047
|
+
".[].name"
|
|
1048
|
+
], { timeout: 15e3 });
|
|
1049
|
+
const names = stdout.trim().split("\n").filter(Boolean);
|
|
1050
|
+
return names;
|
|
1051
|
+
} catch {
|
|
1052
|
+
return null;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
async function checkGitHubSecrets(projectRoot, variables) {
|
|
1056
|
+
const slug = await detectGitHubRepoSlug(projectRoot);
|
|
1057
|
+
if (!slug) {
|
|
1058
|
+
console.log(chalk8.dim(" GitHub checks skipped (no GitHub remote detected)"));
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1061
|
+
const secretNames = await listGitHubSecretNames(slug);
|
|
1062
|
+
if (!secretNames) {
|
|
1063
|
+
console.log(chalk8.dim(" GitHub checks skipped (gh CLI not available or not authenticated)"));
|
|
1064
|
+
return slug;
|
|
1065
|
+
}
|
|
1066
|
+
const secretSet = new Set(secretNames);
|
|
1067
|
+
console.log(chalk8.dim(` GitHub secrets: ${secretNames.length} found in ${slug}`));
|
|
1068
|
+
for (const v of variables) {
|
|
1069
|
+
if (v.requiredIn.github) {
|
|
1070
|
+
v.githubStatus = secretSet.has(v.name) ? "present" : "missing";
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return slug;
|
|
1074
|
+
}
|
|
883
1075
|
function parseApps(content) {
|
|
884
1076
|
const apps = [];
|
|
885
1077
|
const appsSection = extractSection(content, "Apps");
|
|
@@ -959,10 +1151,10 @@ function extractSection(content, heading) {
|
|
|
959
1151
|
async function checkLocalEnvFiles(projectRoot, apps) {
|
|
960
1152
|
const result = /* @__PURE__ */ new Map();
|
|
961
1153
|
for (const app of apps) {
|
|
962
|
-
const envPath =
|
|
1154
|
+
const envPath = join9(projectRoot, app.envFilePath);
|
|
963
1155
|
const varNames = /* @__PURE__ */ new Set();
|
|
964
1156
|
if (existsSync5(envPath)) {
|
|
965
|
-
const content = await
|
|
1157
|
+
const content = await readFile7(envPath, "utf-8");
|
|
966
1158
|
for (const line of content.split("\n")) {
|
|
967
1159
|
const m = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
968
1160
|
if (m) {
|
|
@@ -975,14 +1167,14 @@ async function checkLocalEnvFiles(projectRoot, apps) {
|
|
|
975
1167
|
return result;
|
|
976
1168
|
}
|
|
977
1169
|
async function parseWorkflowSecrets(projectRoot) {
|
|
978
|
-
const workflowsDir =
|
|
1170
|
+
const workflowsDir = join9(projectRoot, ".github", "workflows");
|
|
979
1171
|
if (!existsSync5(workflowsDir))
|
|
980
1172
|
return [];
|
|
981
1173
|
const refs = [];
|
|
982
1174
|
const secretRe = /\$\{\{\s*secrets\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;
|
|
983
1175
|
for (const ext of ["*.yml", "*.yaml"]) {
|
|
984
|
-
for await (const filePath of
|
|
985
|
-
const content = await
|
|
1176
|
+
for await (const filePath of glob2(join9(workflowsDir, ext))) {
|
|
1177
|
+
const content = await readFile7(filePath, "utf-8");
|
|
986
1178
|
const secrets = /* @__PURE__ */ new Set();
|
|
987
1179
|
let m;
|
|
988
1180
|
while ((m = secretRe.exec(content)) !== null) {
|
|
@@ -991,7 +1183,7 @@ async function parseWorkflowSecrets(projectRoot) {
|
|
|
991
1183
|
secretRe.lastIndex = 0;
|
|
992
1184
|
if (secrets.size > 0) {
|
|
993
1185
|
refs.push({
|
|
994
|
-
workflowFile:
|
|
1186
|
+
workflowFile: relative2(projectRoot, filePath),
|
|
995
1187
|
secretsUsed: [...secrets].sort()
|
|
996
1188
|
});
|
|
997
1189
|
}
|
|
@@ -1045,26 +1237,30 @@ function computeDrift(variables, apps, localPresence, workflowRefs) {
|
|
|
1045
1237
|
workflowBrokenRefs: workflowBrokenRefs.sort()
|
|
1046
1238
|
};
|
|
1047
1239
|
}
|
|
1048
|
-
var MANIFEST_PATH;
|
|
1240
|
+
var execFileAsync, MANIFEST_PATH;
|
|
1049
1241
|
var init_env_manifest_scanner = __esm({
|
|
1050
1242
|
"dist/scanner/env-manifest-scanner.js"() {
|
|
1051
1243
|
"use strict";
|
|
1244
|
+
init_auth2();
|
|
1245
|
+
init_discover_projects();
|
|
1246
|
+
init_fetch_env_vars();
|
|
1247
|
+
execFileAsync = promisify(execFile);
|
|
1052
1248
|
MANIFEST_PATH = "docs/reference/env-manifest.md";
|
|
1053
1249
|
}
|
|
1054
1250
|
});
|
|
1055
1251
|
|
|
1056
1252
|
// dist/scanner/index.js
|
|
1057
1253
|
import { readdir as readdir2 } from "node:fs/promises";
|
|
1058
|
-
import { join as
|
|
1254
|
+
import { join as join10, relative as relative3 } from "node:path";
|
|
1059
1255
|
import { existsSync as existsSync6 } from "node:fs";
|
|
1060
|
-
import { homedir as
|
|
1256
|
+
import { homedir as homedir6 } from "node:os";
|
|
1061
1257
|
import { createHash } from "node:crypto";
|
|
1062
1258
|
async function scanProject(projectRoot) {
|
|
1063
1259
|
const allFiles = await discoverFiles(projectRoot);
|
|
1064
1260
|
const rootFiles = identifyRoots(allFiles, projectRoot);
|
|
1065
1261
|
const allRefs = [];
|
|
1066
1262
|
for (const file of allFiles) {
|
|
1067
|
-
const fullPath = file.startsWith("/") ? file :
|
|
1263
|
+
const fullPath = file.startsWith("/") ? file : join10(projectRoot, file);
|
|
1068
1264
|
try {
|
|
1069
1265
|
const refs = await parseFileReferences(fullPath, projectRoot);
|
|
1070
1266
|
allRefs.push(...refs);
|
|
@@ -1092,17 +1288,17 @@ async function scanProject(projectRoot) {
|
|
|
1092
1288
|
}
|
|
1093
1289
|
async function discoverFiles(projectRoot) {
|
|
1094
1290
|
const files = [];
|
|
1095
|
-
const claudeDir =
|
|
1291
|
+
const claudeDir = join10(projectRoot, ".claude");
|
|
1096
1292
|
if (existsSync6(claudeDir)) {
|
|
1097
1293
|
await walkDir(claudeDir, projectRoot, files);
|
|
1098
1294
|
}
|
|
1099
|
-
if (existsSync6(
|
|
1295
|
+
if (existsSync6(join10(projectRoot, "CLAUDE.md"))) {
|
|
1100
1296
|
files.push("CLAUDE.md");
|
|
1101
1297
|
}
|
|
1102
|
-
if (existsSync6(
|
|
1298
|
+
if (existsSync6(join10(projectRoot, "skills.md"))) {
|
|
1103
1299
|
files.push("skills.md");
|
|
1104
1300
|
}
|
|
1105
|
-
const plansDir =
|
|
1301
|
+
const plansDir = join10(projectRoot, "docs", "plans");
|
|
1106
1302
|
if (existsSync6(plansDir)) {
|
|
1107
1303
|
await walkDir(plansDir, projectRoot, files);
|
|
1108
1304
|
}
|
|
@@ -1111,13 +1307,13 @@ async function discoverFiles(projectRoot) {
|
|
|
1111
1307
|
async function walkDir(dir, projectRoot, files) {
|
|
1112
1308
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
1113
1309
|
for (const entry of entries) {
|
|
1114
|
-
const fullPath =
|
|
1310
|
+
const fullPath = join10(dir, entry.name);
|
|
1115
1311
|
if (entry.isDirectory()) {
|
|
1116
1312
|
if (["node_modules", ".git", ".turbo", "cache", "session-env"].includes(entry.name))
|
|
1117
1313
|
continue;
|
|
1118
1314
|
await walkDir(fullPath, projectRoot, files);
|
|
1119
1315
|
} else {
|
|
1120
|
-
const relPath =
|
|
1316
|
+
const relPath = relative3(projectRoot, fullPath);
|
|
1121
1317
|
files.push(relPath);
|
|
1122
1318
|
}
|
|
1123
1319
|
}
|
|
@@ -1130,7 +1326,7 @@ function identifyRoots(allFiles, projectRoot) {
|
|
|
1130
1326
|
}
|
|
1131
1327
|
}
|
|
1132
1328
|
for (const globalFile of GLOBAL_ROOT_FILES) {
|
|
1133
|
-
const expanded = globalFile.replace("~",
|
|
1329
|
+
const expanded = globalFile.replace("~", homedir6());
|
|
1134
1330
|
if (existsSync6(expanded)) {
|
|
1135
1331
|
roots.push(globalFile);
|
|
1136
1332
|
}
|
|
@@ -1138,8 +1334,8 @@ function identifyRoots(allFiles, projectRoot) {
|
|
|
1138
1334
|
return roots;
|
|
1139
1335
|
}
|
|
1140
1336
|
async function readClaudeConfigFiles(projectRoot) {
|
|
1141
|
-
const { readFile:
|
|
1142
|
-
const { join:
|
|
1337
|
+
const { readFile: readFile12, stat, glob: glob3 } = await import("node:fs/promises");
|
|
1338
|
+
const { join: join16, relative: relative5 } = await import("node:path");
|
|
1143
1339
|
const { existsSync: existsSync13 } = await import("node:fs");
|
|
1144
1340
|
const configPatterns = [
|
|
1145
1341
|
"CLAUDE.md",
|
|
@@ -1149,22 +1345,43 @@ async function readClaudeConfigFiles(projectRoot) {
|
|
|
1149
1345
|
".claude/skills/*.md"
|
|
1150
1346
|
];
|
|
1151
1347
|
const files = [];
|
|
1348
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1349
|
+
async function addFile(fullPath) {
|
|
1350
|
+
const relPath = relative5(projectRoot, fullPath);
|
|
1351
|
+
if (seen.has(relPath))
|
|
1352
|
+
return;
|
|
1353
|
+
seen.add(relPath);
|
|
1354
|
+
try {
|
|
1355
|
+
const content = await readFile12(fullPath, "utf-8");
|
|
1356
|
+
const fileStat = await stat(fullPath);
|
|
1357
|
+
const lastMod = getGitLastModified(fullPath, projectRoot);
|
|
1358
|
+
files.push({ filePath: relPath, content, sizeBytes: fileStat.size, lastModified: lastMod });
|
|
1359
|
+
} catch {
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1152
1362
|
for (const pattern of configPatterns) {
|
|
1153
|
-
for await (const fullPath of
|
|
1363
|
+
for await (const fullPath of glob3(join16(projectRoot, pattern))) {
|
|
1154
1364
|
if (!existsSync13(fullPath))
|
|
1155
1365
|
continue;
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1366
|
+
await addFile(fullPath);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
const pkgPath = join16(projectRoot, "package.json");
|
|
1370
|
+
if (existsSync13(pkgPath)) {
|
|
1371
|
+
try {
|
|
1372
|
+
const pkgContent = await readFile12(pkgPath, "utf-8");
|
|
1373
|
+
const pkg = JSON.parse(pkgContent);
|
|
1374
|
+
const preflightCmd = pkg.scripts?.preflight;
|
|
1375
|
+
if (preflightCmd) {
|
|
1376
|
+
const match = preflightCmd.match(/(?:bash\s+|sh\s+|\.\/)?(\S+\.(?:sh|bash|ts|js|mjs))/);
|
|
1377
|
+
if (match) {
|
|
1378
|
+
const scriptPath = join16(projectRoot, match[1]);
|
|
1379
|
+
if (existsSync13(scriptPath)) {
|
|
1380
|
+
await addFile(scriptPath);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1167
1383
|
}
|
|
1384
|
+
} catch {
|
|
1168
1385
|
}
|
|
1169
1386
|
}
|
|
1170
1387
|
return files;
|
|
@@ -1173,7 +1390,7 @@ function detectStaleFiles(allFiles, projectRoot) {
|
|
|
1173
1390
|
const stale = [];
|
|
1174
1391
|
const now = Date.now();
|
|
1175
1392
|
for (const file of allFiles) {
|
|
1176
|
-
const fullPath =
|
|
1393
|
+
const fullPath = join10(projectRoot, file);
|
|
1177
1394
|
const lastMod = getGitLastModified(fullPath, projectRoot);
|
|
1178
1395
|
if (lastMod) {
|
|
1179
1396
|
const days = Math.floor((now - new Date(lastMod).getTime()) / (1e3 * 60 * 60 * 24));
|
|
@@ -1315,7 +1532,7 @@ var init_html_generator = __esm({
|
|
|
1315
1532
|
});
|
|
1316
1533
|
|
|
1317
1534
|
// dist/check-update.js
|
|
1318
|
-
import
|
|
1535
|
+
import chalk9 from "chalk";
|
|
1319
1536
|
async function fetchLatest() {
|
|
1320
1537
|
try {
|
|
1321
1538
|
const controller = new AbortController();
|
|
@@ -1334,12 +1551,12 @@ async function fetchLatest() {
|
|
|
1334
1551
|
}
|
|
1335
1552
|
function printUpdateBanner(latest) {
|
|
1336
1553
|
console.log("");
|
|
1337
|
-
console.log(
|
|
1338
|
-
console.log(
|
|
1339
|
-
console.log(
|
|
1340
|
-
console.log(
|
|
1341
|
-
console.log(
|
|
1342
|
-
console.log(
|
|
1554
|
+
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"));
|
|
1555
|
+
console.log(chalk9.yellow("\u2502") + chalk9.bold(" Update available! ") + chalk9.dim(`${CURRENT_VERSION}`) + chalk9.white(" \u2192 ") + chalk9.green.bold(`${latest}`) + " " + chalk9.yellow("\u2502"));
|
|
1556
|
+
console.log(chalk9.yellow("\u2502") + " " + chalk9.yellow("\u2502"));
|
|
1557
|
+
console.log(chalk9.yellow("\u2502") + " Run: " + chalk9.cyan("md4ai update") + " " + chalk9.yellow("\u2502"));
|
|
1558
|
+
console.log(chalk9.yellow("\u2502") + " " + chalk9.yellow("\u2502"));
|
|
1559
|
+
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"));
|
|
1343
1560
|
console.log("");
|
|
1344
1561
|
}
|
|
1345
1562
|
async function checkForUpdate() {
|
|
@@ -1349,7 +1566,7 @@ async function checkForUpdate() {
|
|
|
1349
1566
|
if (latest !== CURRENT_VERSION && isNewer(latest, CURRENT_VERSION)) {
|
|
1350
1567
|
printUpdateBanner(latest);
|
|
1351
1568
|
} else {
|
|
1352
|
-
console.log(
|
|
1569
|
+
console.log(chalk9.green(`md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.`));
|
|
1353
1570
|
}
|
|
1354
1571
|
}
|
|
1355
1572
|
async function autoCheckForUpdate() {
|
|
@@ -1378,7 +1595,7 @@ var CURRENT_VERSION;
|
|
|
1378
1595
|
var init_check_update = __esm({
|
|
1379
1596
|
"dist/check-update.js"() {
|
|
1380
1597
|
"use strict";
|
|
1381
|
-
CURRENT_VERSION = true ? "0.9.
|
|
1598
|
+
CURRENT_VERSION = true ? "0.9.5" : "0.0.0-dev";
|
|
1382
1599
|
}
|
|
1383
1600
|
});
|
|
1384
1601
|
|
|
@@ -1406,6 +1623,87 @@ var init_push_toolings = __esm({
|
|
|
1406
1623
|
}
|
|
1407
1624
|
});
|
|
1408
1625
|
|
|
1626
|
+
// dist/commands/push-health-results.js
|
|
1627
|
+
import chalk10 from "chalk";
|
|
1628
|
+
async function pushHealthResults(supabase, folderId, manifest) {
|
|
1629
|
+
const { data: checks } = await supabase.from("env_health_checks").select("id, check_type, check_config").eq("folder_id", folderId).eq("enabled", true);
|
|
1630
|
+
if (!checks?.length)
|
|
1631
|
+
return;
|
|
1632
|
+
const results = [];
|
|
1633
|
+
const ranAt = manifest.checkedAt;
|
|
1634
|
+
for (const chk of checks) {
|
|
1635
|
+
const config2 = chk.check_config;
|
|
1636
|
+
if (chk.check_type === "env_var_set") {
|
|
1637
|
+
const app = config2.app;
|
|
1638
|
+
const variable = config2.variable;
|
|
1639
|
+
const v = manifest.variables.find((mv) => mv.name === variable);
|
|
1640
|
+
if (v) {
|
|
1641
|
+
const localStatus = v.localStatus[app];
|
|
1642
|
+
results.push({
|
|
1643
|
+
check_id: chk.id,
|
|
1644
|
+
status: localStatus === "present" ? "pass" : "fail",
|
|
1645
|
+
message: localStatus === "present" ? "Set" : "Missing",
|
|
1646
|
+
ran_at: ranAt
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
} else if (chk.check_type === "file_exists") {
|
|
1650
|
+
results.push({
|
|
1651
|
+
check_id: chk.id,
|
|
1652
|
+
status: "pass",
|
|
1653
|
+
message: "Found",
|
|
1654
|
+
ran_at: ranAt
|
|
1655
|
+
});
|
|
1656
|
+
} else if (chk.check_type === "gh_secret") {
|
|
1657
|
+
const variable = config2.secret;
|
|
1658
|
+
const v = manifest.variables.find((mv) => mv.name === variable);
|
|
1659
|
+
const ghStatus = v?.githubStatus;
|
|
1660
|
+
results.push({
|
|
1661
|
+
check_id: chk.id,
|
|
1662
|
+
status: ghStatus === "present" ? "pass" : ghStatus === "missing" ? "fail" : "warn",
|
|
1663
|
+
message: ghStatus === "present" ? "Exists" : ghStatus === "missing" ? "Missing" : "Unknown",
|
|
1664
|
+
ran_at: ranAt
|
|
1665
|
+
});
|
|
1666
|
+
} else if (chk.check_type === "vercel_env") {
|
|
1667
|
+
const variable = config2.variable;
|
|
1668
|
+
const projectName = config2.projectName;
|
|
1669
|
+
const target = config2.target;
|
|
1670
|
+
const proj = manifest.vercelDiscovered?.find((p) => p.projectName === projectName);
|
|
1671
|
+
if (proj) {
|
|
1672
|
+
const vercelVar = proj.vars.find((v) => v.key === variable);
|
|
1673
|
+
if (vercelVar) {
|
|
1674
|
+
const hasTarget = vercelVar.targets.includes(target);
|
|
1675
|
+
results.push({
|
|
1676
|
+
check_id: chk.id,
|
|
1677
|
+
status: hasTarget ? "pass" : "warn",
|
|
1678
|
+
message: hasTarget ? `Present (${target})` : `Present but not in ${target}`,
|
|
1679
|
+
ran_at: ranAt
|
|
1680
|
+
});
|
|
1681
|
+
} else {
|
|
1682
|
+
results.push({
|
|
1683
|
+
check_id: chk.id,
|
|
1684
|
+
status: "fail",
|
|
1685
|
+
message: `Not found in ${projectName}`,
|
|
1686
|
+
ran_at: ranAt
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
if (results.length > 0) {
|
|
1693
|
+
const { error } = await supabase.from("env_health_results").insert(results);
|
|
1694
|
+
if (error) {
|
|
1695
|
+
console.error(chalk10.yellow(`Health results warning: ${error.message}`));
|
|
1696
|
+
} else {
|
|
1697
|
+
console.log(chalk10.green(` Updated ${results.length} health check result(s).`));
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
var init_push_health_results = __esm({
|
|
1702
|
+
"dist/commands/push-health-results.js"() {
|
|
1703
|
+
"use strict";
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1409
1707
|
// dist/device-utils.js
|
|
1410
1708
|
import { hostname as hostname2, platform as platform2 } from "node:os";
|
|
1411
1709
|
function detectOs2() {
|
|
@@ -1452,16 +1750,16 @@ __export(map_exports, {
|
|
|
1452
1750
|
import { resolve as resolve3, basename } from "node:path";
|
|
1453
1751
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
1454
1752
|
import { existsSync as existsSync7 } from "node:fs";
|
|
1455
|
-
import
|
|
1753
|
+
import chalk11 from "chalk";
|
|
1456
1754
|
import { select as select3, input as input5, confirm as confirm2 } from "@inquirer/prompts";
|
|
1457
1755
|
async function mapCommand(path, options) {
|
|
1458
1756
|
await checkForUpdate();
|
|
1459
1757
|
const projectRoot = resolve3(path ?? process.cwd());
|
|
1460
1758
|
if (!existsSync7(projectRoot)) {
|
|
1461
|
-
console.error(
|
|
1759
|
+
console.error(chalk11.red(`Path not found: ${projectRoot}`));
|
|
1462
1760
|
process.exit(1);
|
|
1463
1761
|
}
|
|
1464
|
-
console.log(
|
|
1762
|
+
console.log(chalk11.blue(`Scanning: ${projectRoot}
|
|
1465
1763
|
`));
|
|
1466
1764
|
const result = await scanProject(projectRoot);
|
|
1467
1765
|
console.log(` Files found: ${result.graph.nodes.length}`);
|
|
@@ -1479,7 +1777,7 @@ async function mapCommand(path, options) {
|
|
|
1479
1777
|
const htmlPath = resolve3(outputDir, "index.html");
|
|
1480
1778
|
const html = generateOfflineHtml(result, projectRoot);
|
|
1481
1779
|
await writeFile2(htmlPath, html, "utf-8");
|
|
1482
|
-
console.log(
|
|
1780
|
+
console.log(chalk11.green(`
|
|
1483
1781
|
Local preview: ${htmlPath}`));
|
|
1484
1782
|
if (!options.offline) {
|
|
1485
1783
|
try {
|
|
@@ -1488,9 +1786,9 @@ Local preview: ${htmlPath}`));
|
|
|
1488
1786
|
if (devicePaths?.length) {
|
|
1489
1787
|
const { folder_id, device_name } = devicePaths[0];
|
|
1490
1788
|
const { data: proposedFiles } = await supabase.from("folder_files").select("id, file_path, proposed_at").eq("folder_id", folder_id).eq("proposed_for_deletion", true);
|
|
1491
|
-
if (proposedFiles?.length) {
|
|
1789
|
+
if (proposedFiles?.length && process.stdin.isTTY) {
|
|
1492
1790
|
const { checkbox } = await import("@inquirer/prompts");
|
|
1493
|
-
console.log(
|
|
1791
|
+
console.log(chalk11.yellow(`
|
|
1494
1792
|
${proposedFiles.length} file(s) proposed for deletion:
|
|
1495
1793
|
`));
|
|
1496
1794
|
const toDelete = await checkbox({
|
|
@@ -1506,9 +1804,9 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1506
1804
|
const { unlink } = await import("node:fs/promises");
|
|
1507
1805
|
await unlink(fullPath);
|
|
1508
1806
|
await supabase.from("folder_files").delete().eq("id", file.id);
|
|
1509
|
-
console.log(
|
|
1807
|
+
console.log(chalk11.green(` Deleted: ${file.file_path}`));
|
|
1510
1808
|
} catch (err) {
|
|
1511
|
-
console.error(
|
|
1809
|
+
console.error(chalk11.red(` Failed to delete ${file.file_path}: ${err}`));
|
|
1512
1810
|
}
|
|
1513
1811
|
}
|
|
1514
1812
|
const keptIds = proposedFiles.filter((f) => !toDelete.some((d) => d.id === f.id)).map((f) => f.id);
|
|
@@ -1516,9 +1814,11 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1516
1814
|
for (const id of keptIds) {
|
|
1517
1815
|
await supabase.from("folder_files").update({ proposed_for_deletion: false, proposed_at: null, proposed_by: null }).eq("id", id);
|
|
1518
1816
|
}
|
|
1519
|
-
console.log(
|
|
1817
|
+
console.log(chalk11.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
|
|
1520
1818
|
}
|
|
1521
1819
|
console.log("");
|
|
1820
|
+
} else if (proposedFiles?.length) {
|
|
1821
|
+
console.log(chalk11.dim(` ${proposedFiles.length} file(s) proposed for deletion \u2014 skipped (non-interactive mode).`));
|
|
1522
1822
|
}
|
|
1523
1823
|
const { error } = await supabase.from("claude_folders").update({
|
|
1524
1824
|
graph_json: result.graph,
|
|
@@ -1530,17 +1830,20 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1530
1830
|
data_hash: result.dataHash
|
|
1531
1831
|
}).eq("id", folder_id);
|
|
1532
1832
|
if (error) {
|
|
1533
|
-
console.error(
|
|
1833
|
+
console.error(chalk11.yellow(`Sync warning: ${error.message}`));
|
|
1534
1834
|
} else {
|
|
1535
1835
|
await pushToolings(supabase, folder_id, result.toolings);
|
|
1836
|
+
if (result.envManifest) {
|
|
1837
|
+
await pushHealthResults(supabase, folder_id, result.envManifest);
|
|
1838
|
+
}
|
|
1536
1839
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
|
|
1537
1840
|
await saveState({
|
|
1538
1841
|
lastFolderId: folder_id,
|
|
1539
1842
|
lastDeviceName: device_name,
|
|
1540
1843
|
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1541
1844
|
});
|
|
1542
|
-
console.log(
|
|
1543
|
-
console.log(
|
|
1845
|
+
console.log(chalk11.green("Synced to Supabase."));
|
|
1846
|
+
console.log(chalk11.cyan(`
|
|
1544
1847
|
https://www.md4ai.com/project/${folder_id}
|
|
1545
1848
|
`));
|
|
1546
1849
|
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
@@ -1554,14 +1857,18 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1554
1857
|
last_modified: file.lastModified
|
|
1555
1858
|
}, { onConflict: "folder_id,file_path" });
|
|
1556
1859
|
}
|
|
1557
|
-
console.log(
|
|
1860
|
+
console.log(chalk11.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
1558
1861
|
}
|
|
1559
1862
|
}
|
|
1560
1863
|
} else {
|
|
1561
|
-
console.log(
|
|
1864
|
+
console.log(chalk11.yellow("\nThis folder is not linked to a project on your dashboard."));
|
|
1865
|
+
if (!process.stdin.isTTY) {
|
|
1866
|
+
console.log(chalk11.dim("Non-interactive mode \u2014 skipping link prompt."));
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1562
1869
|
const shouldLink = await confirm2({ message: "Would you like to link it now?" });
|
|
1563
1870
|
if (!shouldLink) {
|
|
1564
|
-
console.log(
|
|
1871
|
+
console.log(chalk11.dim("Skipped \u2014 local preview still generated."));
|
|
1565
1872
|
} else {
|
|
1566
1873
|
const { supabase: sb, userId } = await getAuthenticatedClient();
|
|
1567
1874
|
const { data: folders } = await sb.from("claude_folders").select("id, name").order("name");
|
|
@@ -1581,11 +1888,11 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1581
1888
|
});
|
|
1582
1889
|
const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId, name: projectName }).select("id").single();
|
|
1583
1890
|
if (createErr || !newFolder) {
|
|
1584
|
-
console.error(
|
|
1891
|
+
console.error(chalk11.red(`Failed to create project: ${createErr?.message}`));
|
|
1585
1892
|
return;
|
|
1586
1893
|
}
|
|
1587
1894
|
folderId = newFolder.id;
|
|
1588
|
-
console.log(
|
|
1895
|
+
console.log(chalk11.green(`Created project "${projectName}".`));
|
|
1589
1896
|
} else {
|
|
1590
1897
|
folderId = chosen;
|
|
1591
1898
|
}
|
|
@@ -1614,6 +1921,9 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1614
1921
|
data_hash: result.dataHash
|
|
1615
1922
|
}).eq("id", folderId);
|
|
1616
1923
|
await pushToolings(sb, folderId, result.toolings);
|
|
1924
|
+
if (result.envManifest) {
|
|
1925
|
+
await pushHealthResults(sb, folderId, result.envManifest);
|
|
1926
|
+
}
|
|
1617
1927
|
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
1618
1928
|
for (const file of configFiles) {
|
|
1619
1929
|
await sb.from("folder_files").upsert({
|
|
@@ -1629,14 +1939,14 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1629
1939
|
lastDeviceName: deviceName,
|
|
1630
1940
|
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1631
1941
|
});
|
|
1632
|
-
console.log(
|
|
1633
|
-
console.log(
|
|
1942
|
+
console.log(chalk11.green("\nLinked and synced."));
|
|
1943
|
+
console.log(chalk11.cyan(`
|
|
1634
1944
|
https://www.md4ai.com/project/${folderId}
|
|
1635
1945
|
`));
|
|
1636
1946
|
}
|
|
1637
1947
|
}
|
|
1638
1948
|
} catch {
|
|
1639
|
-
console.log(
|
|
1949
|
+
console.log(chalk11.yellow("Not logged in \u2014 local preview only."));
|
|
1640
1950
|
}
|
|
1641
1951
|
}
|
|
1642
1952
|
}
|
|
@@ -1649,6 +1959,7 @@ var init_map = __esm({
|
|
|
1649
1959
|
init_html_generator();
|
|
1650
1960
|
init_check_update();
|
|
1651
1961
|
init_push_toolings();
|
|
1962
|
+
init_push_health_results();
|
|
1652
1963
|
init_device_utils();
|
|
1653
1964
|
}
|
|
1654
1965
|
});
|
|
@@ -1658,20 +1969,20 @@ var sync_exports = {};
|
|
|
1658
1969
|
__export(sync_exports, {
|
|
1659
1970
|
syncCommand: () => syncCommand
|
|
1660
1971
|
});
|
|
1661
|
-
import
|
|
1972
|
+
import chalk14 from "chalk";
|
|
1662
1973
|
async function syncCommand(options) {
|
|
1663
1974
|
const { supabase } = await getAuthenticatedClient();
|
|
1664
1975
|
if (options.all) {
|
|
1665
1976
|
const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path");
|
|
1666
1977
|
if (error || !devices?.length) {
|
|
1667
|
-
console.error(
|
|
1978
|
+
console.error(chalk14.red("No devices found."));
|
|
1668
1979
|
process.exit(1);
|
|
1669
1980
|
}
|
|
1670
1981
|
for (const device of devices) {
|
|
1671
|
-
console.log(
|
|
1982
|
+
console.log(chalk14.blue(`Syncing: ${device.path}`));
|
|
1672
1983
|
const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
1673
1984
|
if (proposedAll?.length) {
|
|
1674
|
-
console.log(
|
|
1985
|
+
console.log(chalk14.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
1675
1986
|
}
|
|
1676
1987
|
try {
|
|
1677
1988
|
const result = await scanProject(device.path);
|
|
@@ -1686,28 +1997,28 @@ async function syncCommand(options) {
|
|
|
1686
1997
|
}).eq("id", device.folder_id);
|
|
1687
1998
|
await pushToolings(supabase, device.folder_id, result.toolings);
|
|
1688
1999
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
1689
|
-
console.log(
|
|
2000
|
+
console.log(chalk14.green(` Done: ${device.device_name}`));
|
|
1690
2001
|
} catch (err) {
|
|
1691
|
-
console.error(
|
|
2002
|
+
console.error(chalk14.red(` Failed: ${device.path}: ${err}`));
|
|
1692
2003
|
}
|
|
1693
2004
|
}
|
|
1694
2005
|
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1695
|
-
console.log(
|
|
2006
|
+
console.log(chalk14.green("\nAll devices synced."));
|
|
1696
2007
|
} else {
|
|
1697
2008
|
const state = await loadState();
|
|
1698
2009
|
if (!state.lastFolderId) {
|
|
1699
|
-
console.error(
|
|
2010
|
+
console.error(chalk14.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
|
|
1700
2011
|
process.exit(1);
|
|
1701
2012
|
}
|
|
1702
2013
|
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();
|
|
1703
2014
|
if (!device) {
|
|
1704
|
-
console.error(
|
|
2015
|
+
console.error(chalk14.red("Could not find last synced device/folder."));
|
|
1705
2016
|
process.exit(1);
|
|
1706
2017
|
}
|
|
1707
|
-
console.log(
|
|
2018
|
+
console.log(chalk14.blue(`Syncing: ${device.path}`));
|
|
1708
2019
|
const { data: proposedSingle } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
1709
2020
|
if (proposedSingle?.length) {
|
|
1710
|
-
console.log(
|
|
2021
|
+
console.log(chalk14.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
1711
2022
|
}
|
|
1712
2023
|
const result = await scanProject(device.path);
|
|
1713
2024
|
await supabase.from("claude_folders").update({
|
|
@@ -1721,7 +2032,7 @@ async function syncCommand(options) {
|
|
|
1721
2032
|
await pushToolings(supabase, device.folder_id, result.toolings);
|
|
1722
2033
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
1723
2034
|
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1724
|
-
console.log(
|
|
2035
|
+
console.log(chalk14.green("Synced."));
|
|
1725
2036
|
}
|
|
1726
2037
|
}
|
|
1727
2038
|
var init_sync = __esm({
|
|
@@ -1735,16 +2046,16 @@ var init_sync = __esm({
|
|
|
1735
2046
|
});
|
|
1736
2047
|
|
|
1737
2048
|
// dist/mcp/read-configs.js
|
|
1738
|
-
import { readFile as
|
|
1739
|
-
import { join as
|
|
1740
|
-
import { homedir as
|
|
2049
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
2050
|
+
import { join as join14 } from "node:path";
|
|
2051
|
+
import { homedir as homedir8 } from "node:os";
|
|
1741
2052
|
import { existsSync as existsSync11 } from "node:fs";
|
|
1742
2053
|
import { readdir as readdir3 } from "node:fs/promises";
|
|
1743
2054
|
async function readJsonSafe(path) {
|
|
1744
2055
|
try {
|
|
1745
2056
|
if (!existsSync11(path))
|
|
1746
2057
|
return null;
|
|
1747
|
-
const raw = await
|
|
2058
|
+
const raw = await readFile10(path, "utf-8");
|
|
1748
2059
|
return JSON.parse(raw);
|
|
1749
2060
|
} catch {
|
|
1750
2061
|
return null;
|
|
@@ -1804,52 +2115,52 @@ function parseFlatConfig(data, source) {
|
|
|
1804
2115
|
return entries;
|
|
1805
2116
|
}
|
|
1806
2117
|
async function readAllMcpConfigs() {
|
|
1807
|
-
const home =
|
|
2118
|
+
const home = homedir8();
|
|
1808
2119
|
const entries = [];
|
|
1809
|
-
const userConfig = await readJsonSafe(
|
|
2120
|
+
const userConfig = await readJsonSafe(join14(home, ".claude.json"));
|
|
1810
2121
|
entries.push(...parseServers(userConfig, "global"));
|
|
1811
|
-
const globalMcp = await readJsonSafe(
|
|
2122
|
+
const globalMcp = await readJsonSafe(join14(home, ".claude", "mcp.json"));
|
|
1812
2123
|
entries.push(...parseServers(globalMcp, "global"));
|
|
1813
|
-
const cwdMcp = await readJsonSafe(
|
|
2124
|
+
const cwdMcp = await readJsonSafe(join14(process.cwd(), ".mcp.json"));
|
|
1814
2125
|
entries.push(...parseServers(cwdMcp, "project"));
|
|
1815
|
-
const pluginsBase =
|
|
2126
|
+
const pluginsBase = join14(home, ".claude", "plugins", "marketplaces");
|
|
1816
2127
|
if (existsSync11(pluginsBase)) {
|
|
1817
2128
|
try {
|
|
1818
2129
|
const marketplaces = await readdir3(pluginsBase, { withFileTypes: true });
|
|
1819
2130
|
for (const mp of marketplaces) {
|
|
1820
2131
|
if (!mp.isDirectory())
|
|
1821
2132
|
continue;
|
|
1822
|
-
const extDir =
|
|
2133
|
+
const extDir = join14(pluginsBase, mp.name, "external_plugins");
|
|
1823
2134
|
if (!existsSync11(extDir))
|
|
1824
2135
|
continue;
|
|
1825
2136
|
const plugins = await readdir3(extDir, { withFileTypes: true });
|
|
1826
2137
|
for (const plugin of plugins) {
|
|
1827
2138
|
if (!plugin.isDirectory())
|
|
1828
2139
|
continue;
|
|
1829
|
-
const pluginMcp = await readJsonSafe(
|
|
2140
|
+
const pluginMcp = await readJsonSafe(join14(extDir, plugin.name, ".mcp.json"));
|
|
1830
2141
|
entries.push(...parseServers(pluginMcp, "plugin"));
|
|
1831
2142
|
}
|
|
1832
2143
|
}
|
|
1833
2144
|
} catch {
|
|
1834
2145
|
}
|
|
1835
2146
|
}
|
|
1836
|
-
const cacheBase =
|
|
2147
|
+
const cacheBase = join14(home, ".claude", "plugins", "cache");
|
|
1837
2148
|
if (existsSync11(cacheBase)) {
|
|
1838
2149
|
try {
|
|
1839
2150
|
const registries = await readdir3(cacheBase, { withFileTypes: true });
|
|
1840
2151
|
for (const reg of registries) {
|
|
1841
2152
|
if (!reg.isDirectory())
|
|
1842
2153
|
continue;
|
|
1843
|
-
const regDir =
|
|
2154
|
+
const regDir = join14(cacheBase, reg.name);
|
|
1844
2155
|
const plugins = await readdir3(regDir, { withFileTypes: true });
|
|
1845
2156
|
for (const plugin of plugins) {
|
|
1846
2157
|
if (!plugin.isDirectory())
|
|
1847
2158
|
continue;
|
|
1848
|
-
const versionDirs = await readdir3(
|
|
2159
|
+
const versionDirs = await readdir3(join14(regDir, plugin.name), { withFileTypes: true });
|
|
1849
2160
|
for (const ver of versionDirs) {
|
|
1850
2161
|
if (!ver.isDirectory())
|
|
1851
2162
|
continue;
|
|
1852
|
-
const mcpPath =
|
|
2163
|
+
const mcpPath = join14(regDir, plugin.name, ver.name, ".mcp.json");
|
|
1853
2164
|
const flatData = await readJsonSafe(mcpPath);
|
|
1854
2165
|
if (flatData) {
|
|
1855
2166
|
entries.push(...parseFlatConfig(flatData, "plugin"));
|
|
@@ -1913,23 +2224,23 @@ function parseEtime(etime) {
|
|
|
1913
2224
|
}
|
|
1914
2225
|
return days * 86400 + (parts[0] ?? 0);
|
|
1915
2226
|
}
|
|
1916
|
-
function findProcessesForConfig(
|
|
1917
|
-
if (
|
|
2227
|
+
function findProcessesForConfig(config2, processes) {
|
|
2228
|
+
if (config2.type === "http")
|
|
1918
2229
|
return [];
|
|
1919
|
-
const packageName =
|
|
2230
|
+
const packageName = config2.args ? extractPackageName(config2.args) : null;
|
|
1920
2231
|
const matches = [];
|
|
1921
2232
|
const searchTerms = [];
|
|
1922
2233
|
if (packageName)
|
|
1923
2234
|
searchTerms.push(packageName);
|
|
1924
|
-
if (
|
|
1925
|
-
searchTerms.push(
|
|
2235
|
+
if (config2.command === "node" && config2.args?.[0]) {
|
|
2236
|
+
searchTerms.push(config2.args[0]);
|
|
1926
2237
|
}
|
|
1927
|
-
if (
|
|
1928
|
-
searchTerms.push(`mcp-server-${
|
|
1929
|
-
searchTerms.push(`${
|
|
2238
|
+
if (config2.name) {
|
|
2239
|
+
searchTerms.push(`mcp-server-${config2.name}`);
|
|
2240
|
+
searchTerms.push(`${config2.name}-mcp`);
|
|
1930
2241
|
}
|
|
1931
|
-
if ((
|
|
1932
|
-
for (const arg of
|
|
2242
|
+
if ((config2.command === "uvx" || config2.command === "pipx") && config2.args) {
|
|
2243
|
+
for (const arg of config2.args) {
|
|
1933
2244
|
if (!arg.startsWith("-") && arg !== "run")
|
|
1934
2245
|
searchTerms.push(arg);
|
|
1935
2246
|
}
|
|
@@ -1979,7 +2290,7 @@ var mcp_watch_exports = {};
|
|
|
1979
2290
|
__export(mcp_watch_exports, {
|
|
1980
2291
|
mcpWatchCommand: () => mcpWatchCommand
|
|
1981
2292
|
});
|
|
1982
|
-
import
|
|
2293
|
+
import chalk20 from "chalk";
|
|
1983
2294
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
1984
2295
|
import { createHash as createHash2 } from "node:crypto";
|
|
1985
2296
|
function detectTty() {
|
|
@@ -1993,10 +2304,10 @@ function detectTty() {
|
|
|
1993
2304
|
return null;
|
|
1994
2305
|
}
|
|
1995
2306
|
}
|
|
1996
|
-
function checkEnvVars(
|
|
1997
|
-
const required =
|
|
2307
|
+
function checkEnvVars(config2) {
|
|
2308
|
+
const required = config2.env ? Object.keys(config2.env) : [];
|
|
1998
2309
|
const missing = required.filter((key) => {
|
|
1999
|
-
const configValue =
|
|
2310
|
+
const configValue = config2.env?.[key];
|
|
2000
2311
|
const hasConfigValue = configValue && !configValue.startsWith("${");
|
|
2001
2312
|
return !hasConfigValue && !process.env[key];
|
|
2002
2313
|
});
|
|
@@ -2031,20 +2342,20 @@ async function checkChromeCdp() {
|
|
|
2031
2342
|
function buildRows(configs, httpResults, cdpStatus) {
|
|
2032
2343
|
const processes = getProcessTable();
|
|
2033
2344
|
const rows = [];
|
|
2034
|
-
for (const
|
|
2035
|
-
const { required, missing } = checkEnvVars(
|
|
2036
|
-
const packageName =
|
|
2037
|
-
if (
|
|
2038
|
-
const reachability = httpResults.get(
|
|
2345
|
+
for (const config2 of configs) {
|
|
2346
|
+
const { required, missing } = checkEnvVars(config2);
|
|
2347
|
+
const packageName = config2.args ? extractPackageName(config2.args) : null;
|
|
2348
|
+
if (config2.type === "http") {
|
|
2349
|
+
const reachability = httpResults.get(config2.name) ?? "unknown";
|
|
2039
2350
|
const status = reachability === "reachable" ? "running" : "stopped";
|
|
2040
2351
|
const detail = reachability === "reachable" ? "HTTP \u2014 remote service reachable" : reachability === "unreachable" ? "HTTP \u2014 remote service unreachable" : "HTTP \u2014 could not verify";
|
|
2041
2352
|
rows.push({
|
|
2042
|
-
server_name:
|
|
2043
|
-
config_source:
|
|
2353
|
+
server_name: config2.name,
|
|
2354
|
+
config_source: config2.source,
|
|
2044
2355
|
server_type: "http",
|
|
2045
2356
|
command: null,
|
|
2046
2357
|
package_name: null,
|
|
2047
|
-
http_url:
|
|
2358
|
+
http_url: config2.url ?? null,
|
|
2048
2359
|
status,
|
|
2049
2360
|
pid: null,
|
|
2050
2361
|
session_tty: null,
|
|
@@ -2058,10 +2369,10 @@ function buildRows(configs, httpResults, cdpStatus) {
|
|
|
2058
2369
|
}
|
|
2059
2370
|
if (missing.length > 0) {
|
|
2060
2371
|
rows.push({
|
|
2061
|
-
server_name:
|
|
2062
|
-
config_source:
|
|
2372
|
+
server_name: config2.name,
|
|
2373
|
+
config_source: config2.source,
|
|
2063
2374
|
server_type: "stdio",
|
|
2064
|
-
command:
|
|
2375
|
+
command: config2.command ?? null,
|
|
2065
2376
|
package_name: packageName,
|
|
2066
2377
|
http_url: null,
|
|
2067
2378
|
status: "error",
|
|
@@ -2075,15 +2386,15 @@ function buildRows(configs, httpResults, cdpStatus) {
|
|
|
2075
2386
|
});
|
|
2076
2387
|
continue;
|
|
2077
2388
|
}
|
|
2078
|
-
const isChromeDevtools =
|
|
2389
|
+
const isChromeDevtools = config2.name === "chrome-devtools" || (config2.args ?? []).some((a) => a.includes("chrome-devtools"));
|
|
2079
2390
|
const cdpDetail = isChromeDevtools && cdpStatus ? cdpStatus.status === "reachable" ? "Chrome CDP reachable" : "Chrome not reachable on port 9222" : null;
|
|
2080
|
-
const matches = findProcessesForConfig(
|
|
2391
|
+
const matches = findProcessesForConfig(config2, processes);
|
|
2081
2392
|
if (matches.length === 0) {
|
|
2082
2393
|
rows.push({
|
|
2083
|
-
server_name:
|
|
2084
|
-
config_source:
|
|
2394
|
+
server_name: config2.name,
|
|
2395
|
+
config_source: config2.source,
|
|
2085
2396
|
server_type: "stdio",
|
|
2086
|
-
command:
|
|
2397
|
+
command: config2.command ?? null,
|
|
2087
2398
|
package_name: packageName,
|
|
2088
2399
|
http_url: null,
|
|
2089
2400
|
status: "stopped",
|
|
@@ -2106,10 +2417,10 @@ function buildRows(configs, httpResults, cdpStatus) {
|
|
|
2106
2417
|
}
|
|
2107
2418
|
for (const match of byTty.values()) {
|
|
2108
2419
|
rows.push({
|
|
2109
|
-
server_name:
|
|
2110
|
-
config_source:
|
|
2420
|
+
server_name: config2.name,
|
|
2421
|
+
config_source: config2.source,
|
|
2111
2422
|
server_type: "stdio",
|
|
2112
|
-
command:
|
|
2423
|
+
command: config2.command ?? null,
|
|
2113
2424
|
package_name: packageName,
|
|
2114
2425
|
http_url: null,
|
|
2115
2426
|
status: "running",
|
|
@@ -2128,20 +2439,20 @@ function buildRows(configs, httpResults, cdpStatus) {
|
|
|
2128
2439
|
}
|
|
2129
2440
|
function printTable(rows, deviceName, watcherPid, cdpResult) {
|
|
2130
2441
|
process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
|
|
2131
|
-
console.log(
|
|
2132
|
-
MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) +
|
|
2133
|
-
console.log(
|
|
2442
|
+
console.log(chalk20.bold.cyan(`
|
|
2443
|
+
MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) + chalk20.dim(` (PID ${watcherPid})`));
|
|
2444
|
+
console.log(chalk20.dim(` ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \xB7 refreshes every 30s \xB7 Ctrl+C to stop
|
|
2134
2445
|
`));
|
|
2135
2446
|
if (cdpResult) {
|
|
2136
2447
|
if (cdpResult.status === "reachable") {
|
|
2137
2448
|
const browserInfo = cdpResult.browser ? ` (${cdpResult.browser})` : "";
|
|
2138
|
-
console.log(
|
|
2139
|
-
console.log(
|
|
2449
|
+
console.log(chalk20.green(" \u2714 Chrome browser connected") + chalk20.dim(browserInfo));
|
|
2450
|
+
console.log(chalk20.dim(" Claude Code can control Chrome via DevTools Protocol on localhost:9222"));
|
|
2140
2451
|
} else {
|
|
2141
|
-
console.log(
|
|
2142
|
-
console.log(
|
|
2143
|
-
console.log(
|
|
2144
|
-
console.log(
|
|
2452
|
+
console.log(chalk20.red(" \u2717 Chrome browser not connected"));
|
|
2453
|
+
console.log(chalk20.dim(" Claude Code cannot reach Chrome. To fix:"));
|
|
2454
|
+
console.log(chalk20.dim(" 1. Open Chrome with: google-chrome --remote-debugging-port=9222"));
|
|
2455
|
+
console.log(chalk20.dim(" 2. Or add --remote-debugging-port=9222 to your Chrome shortcut"));
|
|
2145
2456
|
}
|
|
2146
2457
|
console.log("");
|
|
2147
2458
|
}
|
|
@@ -2162,41 +2473,41 @@ function printTable(rows, deviceName, watcherPid, cdpResult) {
|
|
|
2162
2473
|
sessionNum++;
|
|
2163
2474
|
const totalMem = servers.reduce((sum, s) => sum + (s.memory_mb ?? 0), 0);
|
|
2164
2475
|
const label = byTty.size === 1 ? "Claude Code session" : `Claude Code session ${sessionNum}`;
|
|
2165
|
-
console.log(
|
|
2476
|
+
console.log(chalk20.green(` ${label}`) + chalk20.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB \xB7 terminal ${tty}`));
|
|
2166
2477
|
for (const s of servers) {
|
|
2167
2478
|
const uptime = formatUptime(s.uptime_seconds ?? 0);
|
|
2168
|
-
const source =
|
|
2169
|
-
console.log(` ${
|
|
2479
|
+
const source = chalk20.dim(`[${s.config_source}]`);
|
|
2480
|
+
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}`);
|
|
2170
2481
|
}
|
|
2171
2482
|
console.log("");
|
|
2172
2483
|
}
|
|
2173
2484
|
if (byTty.size > 1) {
|
|
2174
|
-
console.log(
|
|
2485
|
+
console.log(chalk20.dim(" Each open Claude Code window runs its own set of MCP servers.\n"));
|
|
2175
2486
|
}
|
|
2176
2487
|
}
|
|
2177
2488
|
if (runningHttp.length > 0) {
|
|
2178
|
-
console.log(
|
|
2489
|
+
console.log(chalk20.blue(` Remote Services (${runningHttp.length})`) + chalk20.dim(" \u2014 HTTP endpoints reachable"));
|
|
2179
2490
|
for (const s of runningHttp) {
|
|
2180
|
-
console.log(` ${
|
|
2491
|
+
console.log(` ${chalk20.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk20.dim((s.http_url ?? "").padEnd(30))}`);
|
|
2181
2492
|
}
|
|
2182
2493
|
console.log("");
|
|
2183
2494
|
}
|
|
2184
2495
|
if (stopped.length > 0 || errored.length > 0) {
|
|
2185
2496
|
const notRunning = [...stopped, ...errored];
|
|
2186
|
-
console.log(
|
|
2497
|
+
console.log(chalk20.yellow(` Not Running (${notRunning.length})`) + chalk20.dim(" \u2014 configured but no process detected"));
|
|
2187
2498
|
for (const s of notRunning) {
|
|
2188
|
-
const icon = s.status === "error" ?
|
|
2189
|
-
const source =
|
|
2190
|
-
const detail = s.error_detail ?
|
|
2499
|
+
const icon = s.status === "error" ? chalk20.red("\u2717") : chalk20.yellow("\u25CB");
|
|
2500
|
+
const source = chalk20.dim(`[${s.config_source}]`);
|
|
2501
|
+
const detail = s.error_detail ? chalk20.dim(` \u2014 ${s.error_detail}`) : "";
|
|
2191
2502
|
console.log(` ${icon} ${s.server_name.padEnd(20)} ${source}${detail}`);
|
|
2192
2503
|
}
|
|
2193
2504
|
console.log("");
|
|
2194
2505
|
}
|
|
2195
2506
|
if (rows.length === 0) {
|
|
2196
|
-
console.log(
|
|
2197
|
-
console.log(
|
|
2507
|
+
console.log(chalk20.yellow(" No MCP servers configured."));
|
|
2508
|
+
console.log(chalk20.dim(" Configure servers in ~/.claude/mcp.json or .mcp.json\n"));
|
|
2198
2509
|
}
|
|
2199
|
-
console.log(
|
|
2510
|
+
console.log(chalk20.bgYellow.black.bold(" \u26A0 DO NOT CLOSE THIS WINDOW \u2014 it feeds live data to the dashboard \u26A0 "));
|
|
2200
2511
|
console.log("");
|
|
2201
2512
|
}
|
|
2202
2513
|
function formatUptime(seconds) {
|
|
@@ -2219,7 +2530,7 @@ async function mcpWatchCommand() {
|
|
|
2219
2530
|
const { data: existingWatchers } = await supabase.from("mcp_watchers").select("pid, tty, cli_version, started_at").eq("device_id", deviceId);
|
|
2220
2531
|
if (existingWatchers && existingWatchers.length > 0) {
|
|
2221
2532
|
console.log("");
|
|
2222
|
-
console.log(
|
|
2533
|
+
console.log(chalk20.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
|
|
2223
2534
|
for (const w of existingWatchers) {
|
|
2224
2535
|
try {
|
|
2225
2536
|
process.kill(w.pid, "SIGTERM");
|
|
@@ -2228,15 +2539,15 @@ async function mcpWatchCommand() {
|
|
|
2228
2539
|
}
|
|
2229
2540
|
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId);
|
|
2230
2541
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
2231
|
-
console.log(
|
|
2542
|
+
console.log(chalk20.dim(" Previous watcher stopped.\n"));
|
|
2232
2543
|
}
|
|
2233
2544
|
process.stdout.write(`\x1B]0;MCP mon\x07`);
|
|
2234
|
-
console.log(
|
|
2545
|
+
console.log(chalk20.blue(`Starting MCP monitor for ${deviceName}...`));
|
|
2235
2546
|
console.log("");
|
|
2236
|
-
console.log(
|
|
2237
|
-
console.log(
|
|
2547
|
+
console.log(chalk20.dim(" View this in the online dashboard at:"));
|
|
2548
|
+
console.log(chalk20.cyan(` https://www.md4ai.com/device/${deviceId}`));
|
|
2238
2549
|
console.log("");
|
|
2239
|
-
console.log(
|
|
2550
|
+
console.log(chalk20.yellow(" Please note, closing this window will stop ALL watch reports."));
|
|
2240
2551
|
console.log("");
|
|
2241
2552
|
await supabase.from("mcp_watchers").upsert({
|
|
2242
2553
|
device_id: deviceId,
|
|
@@ -2262,7 +2573,7 @@ async function mcpWatchCommand() {
|
|
|
2262
2573
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2263
2574
|
const { error: deleteError } = await supabase.from("mcp_server_status").delete().eq("device_id", deviceId);
|
|
2264
2575
|
if (deleteError) {
|
|
2265
|
-
console.error(
|
|
2576
|
+
console.error(chalk20.red(` [debug] Failed to delete old status: ${deleteError.message}`));
|
|
2266
2577
|
}
|
|
2267
2578
|
if (rows.length > 0) {
|
|
2268
2579
|
const { error: insertError } = await supabase.from("mcp_server_status").insert(rows.map((row) => ({
|
|
@@ -2271,7 +2582,7 @@ async function mcpWatchCommand() {
|
|
|
2271
2582
|
checked_at: now
|
|
2272
2583
|
})));
|
|
2273
2584
|
if (insertError) {
|
|
2274
|
-
console.error(
|
|
2585
|
+
console.error(chalk20.red(` [debug] Failed to write MCP status: ${insertError.message}`));
|
|
2275
2586
|
}
|
|
2276
2587
|
}
|
|
2277
2588
|
await supabase.from("mcp_watchers").update({ last_heartbeat: now }).eq("device_id", deviceId).eq("pid", myPid);
|
|
@@ -2309,7 +2620,7 @@ async function mcpWatchCommand() {
|
|
|
2309
2620
|
clearInterval(interval);
|
|
2310
2621
|
clearInterval(envInterval);
|
|
2311
2622
|
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).eq("pid", myPid);
|
|
2312
|
-
console.log(
|
|
2623
|
+
console.log(chalk20.dim("\nMCP monitor stopped."));
|
|
2313
2624
|
process.exit(0);
|
|
2314
2625
|
};
|
|
2315
2626
|
process.on("SIGINT", () => {
|
|
@@ -2573,71 +2884,71 @@ init_map();
|
|
|
2573
2884
|
|
|
2574
2885
|
// dist/commands/simulate.js
|
|
2575
2886
|
init_dist();
|
|
2576
|
-
import { join as
|
|
2887
|
+
import { join as join11 } from "node:path";
|
|
2577
2888
|
import { existsSync as existsSync8 } from "node:fs";
|
|
2578
|
-
import { homedir as
|
|
2579
|
-
import
|
|
2889
|
+
import { homedir as homedir7 } from "node:os";
|
|
2890
|
+
import chalk12 from "chalk";
|
|
2580
2891
|
async function simulateCommand(prompt) {
|
|
2581
2892
|
const projectRoot = process.cwd();
|
|
2582
|
-
console.log(
|
|
2893
|
+
console.log(chalk12.blue(`Simulating prompt: "${prompt}"
|
|
2583
2894
|
`));
|
|
2584
|
-
console.log(
|
|
2585
|
-
const globalClaude =
|
|
2895
|
+
console.log(chalk12.dim("Files Claude would load:\n"));
|
|
2896
|
+
const globalClaude = join11(homedir7(), ".claude", "CLAUDE.md");
|
|
2586
2897
|
if (existsSync8(globalClaude)) {
|
|
2587
|
-
console.log(
|
|
2898
|
+
console.log(chalk12.green(" \u2713 ~/.claude/CLAUDE.md (global)"));
|
|
2588
2899
|
}
|
|
2589
2900
|
for (const rootFile of ROOT_FILES) {
|
|
2590
|
-
const fullPath =
|
|
2901
|
+
const fullPath = join11(projectRoot, rootFile);
|
|
2591
2902
|
if (existsSync8(fullPath)) {
|
|
2592
|
-
console.log(
|
|
2903
|
+
console.log(chalk12.green(` \u2713 ${rootFile} (project root)`));
|
|
2593
2904
|
}
|
|
2594
2905
|
}
|
|
2595
2906
|
const settingsFiles = [
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2907
|
+
join11(homedir7(), ".claude", "settings.json"),
|
|
2908
|
+
join11(homedir7(), ".claude", "settings.local.json"),
|
|
2909
|
+
join11(projectRoot, ".claude", "settings.json"),
|
|
2910
|
+
join11(projectRoot, ".claude", "settings.local.json")
|
|
2600
2911
|
];
|
|
2601
2912
|
for (const sf of settingsFiles) {
|
|
2602
2913
|
if (existsSync8(sf)) {
|
|
2603
|
-
const display = sf.startsWith(
|
|
2604
|
-
console.log(
|
|
2914
|
+
const display = sf.startsWith(homedir7()) ? sf.replace(homedir7(), "~") : sf.replace(projectRoot + "/", "");
|
|
2915
|
+
console.log(chalk12.green(` \u2713 ${display} (settings)`));
|
|
2605
2916
|
}
|
|
2606
2917
|
}
|
|
2607
2918
|
const words = prompt.split(/\s+/);
|
|
2608
2919
|
for (const word of words) {
|
|
2609
2920
|
const cleaned = word.replace(/['"]/g, "");
|
|
2610
|
-
const candidatePath =
|
|
2921
|
+
const candidatePath = join11(projectRoot, cleaned);
|
|
2611
2922
|
if (existsSync8(candidatePath) && cleaned.includes("/")) {
|
|
2612
|
-
console.log(
|
|
2923
|
+
console.log(chalk12.cyan(` \u2192 ${cleaned} (referenced in prompt)`));
|
|
2613
2924
|
}
|
|
2614
2925
|
}
|
|
2615
|
-
console.log(
|
|
2926
|
+
console.log(chalk12.dim("\nNote: This is an approximation. Actual file loading depends on Claude Code internals."));
|
|
2616
2927
|
}
|
|
2617
2928
|
|
|
2618
2929
|
// dist/commands/print.js
|
|
2619
|
-
import { join as
|
|
2620
|
-
import { readFile as
|
|
2930
|
+
import { join as join12 } from "node:path";
|
|
2931
|
+
import { readFile as readFile8, writeFile as writeFile3 } from "node:fs/promises";
|
|
2621
2932
|
import { existsSync as existsSync9 } from "node:fs";
|
|
2622
|
-
import
|
|
2933
|
+
import chalk13 from "chalk";
|
|
2623
2934
|
async function printCommand(title) {
|
|
2624
2935
|
const projectRoot = process.cwd();
|
|
2625
|
-
const scanDataPath =
|
|
2936
|
+
const scanDataPath = join12(projectRoot, "output", "index.html");
|
|
2626
2937
|
if (!existsSync9(scanDataPath)) {
|
|
2627
|
-
console.error(
|
|
2938
|
+
console.error(chalk13.red("No scan data found. Run: md4ai scan"));
|
|
2628
2939
|
process.exit(1);
|
|
2629
2940
|
}
|
|
2630
|
-
const html = await
|
|
2941
|
+
const html = await readFile8(scanDataPath, "utf-8");
|
|
2631
2942
|
const match = html.match(/<script type="application\/json" id="scan-data">([\s\S]*?)<\/script>/);
|
|
2632
2943
|
if (!match) {
|
|
2633
|
-
console.error(
|
|
2944
|
+
console.error(chalk13.red("Could not extract scan data from output/index.html"));
|
|
2634
2945
|
process.exit(1);
|
|
2635
2946
|
}
|
|
2636
2947
|
const result = JSON.parse(match[1]);
|
|
2637
2948
|
const printHtml = generatePrintHtml(result, title);
|
|
2638
|
-
const outputPath =
|
|
2949
|
+
const outputPath = join12(projectRoot, "output", `print-${Date.now()}.html`);
|
|
2639
2950
|
await writeFile3(outputPath, printHtml, "utf-8");
|
|
2640
|
-
console.log(
|
|
2951
|
+
console.log(chalk13.green(`Print-ready wall sheet: ${outputPath}`));
|
|
2641
2952
|
}
|
|
2642
2953
|
function escapeHtml2(text) {
|
|
2643
2954
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -2703,7 +3014,7 @@ init_scanner();
|
|
|
2703
3014
|
init_push_toolings();
|
|
2704
3015
|
init_device_utils();
|
|
2705
3016
|
import { resolve as resolve4 } from "node:path";
|
|
2706
|
-
import
|
|
3017
|
+
import chalk15 from "chalk";
|
|
2707
3018
|
async function linkCommand(projectId) {
|
|
2708
3019
|
const { supabase, userId } = await getAuthenticatedClient();
|
|
2709
3020
|
const cwd = resolve4(process.cwd());
|
|
@@ -2711,11 +3022,11 @@ async function linkCommand(projectId) {
|
|
|
2711
3022
|
const osType = detectOs2();
|
|
2712
3023
|
const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
|
|
2713
3024
|
if (folderErr || !folder) {
|
|
2714
|
-
console.error(
|
|
2715
|
-
console.error(
|
|
3025
|
+
console.error(chalk15.red("Project not found, or you do not have access."));
|
|
3026
|
+
console.error(chalk15.yellow("Check the project ID in the MD4AI web dashboard."));
|
|
2716
3027
|
process.exit(1);
|
|
2717
3028
|
}
|
|
2718
|
-
console.log(
|
|
3029
|
+
console.log(chalk15.blue(`
|
|
2719
3030
|
Linking "${folder.name}" to this device...
|
|
2720
3031
|
`));
|
|
2721
3032
|
console.log(` Project: ${folder.name}`);
|
|
@@ -2739,12 +3050,12 @@ Linking "${folder.name}" to this device...
|
|
|
2739
3050
|
path: cwd
|
|
2740
3051
|
});
|
|
2741
3052
|
if (pathErr) {
|
|
2742
|
-
console.error(
|
|
3053
|
+
console.error(chalk15.red(`Failed to link: ${pathErr.message}`));
|
|
2743
3054
|
process.exit(1);
|
|
2744
3055
|
}
|
|
2745
3056
|
}
|
|
2746
|
-
console.log(
|
|
2747
|
-
console.log(
|
|
3057
|
+
console.log(chalk15.green("\nLinked successfully."));
|
|
3058
|
+
console.log(chalk15.blue("\nRunning initial scan...\n"));
|
|
2748
3059
|
const result = await scanProject(cwd);
|
|
2749
3060
|
console.log(` Files: ${result.graph.nodes.length}`);
|
|
2750
3061
|
console.log(` References:${result.graph.edges.length}`);
|
|
@@ -2762,7 +3073,7 @@ Linking "${folder.name}" to this device...
|
|
|
2762
3073
|
data_hash: result.dataHash
|
|
2763
3074
|
}).eq("id", folder.id);
|
|
2764
3075
|
if (scanErr) {
|
|
2765
|
-
console.error(
|
|
3076
|
+
console.error(chalk15.yellow(`Scan upload warning: ${scanErr.message}`));
|
|
2766
3077
|
}
|
|
2767
3078
|
await pushToolings(supabase, folder.id, result.toolings);
|
|
2768
3079
|
const configFiles = await readClaudeConfigFiles(cwd);
|
|
@@ -2776,7 +3087,7 @@ Linking "${folder.name}" to this device...
|
|
|
2776
3087
|
last_modified: file.lastModified
|
|
2777
3088
|
}, { onConflict: "folder_id,file_path" });
|
|
2778
3089
|
}
|
|
2779
|
-
console.log(
|
|
3090
|
+
console.log(chalk15.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
2780
3091
|
}
|
|
2781
3092
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
|
|
2782
3093
|
await saveState({
|
|
@@ -2785,34 +3096,34 @@ Linking "${folder.name}" to this device...
|
|
|
2785
3096
|
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2786
3097
|
});
|
|
2787
3098
|
const projectUrl = `https://www.md4ai.com/project/${folder.id}`;
|
|
2788
|
-
console.log(
|
|
2789
|
-
console.log(
|
|
3099
|
+
console.log(chalk15.green("\nDone! Project linked and scanned."));
|
|
3100
|
+
console.log(chalk15.cyan(`
|
|
2790
3101
|
${projectUrl}
|
|
2791
3102
|
`));
|
|
2792
|
-
console.log(
|
|
3103
|
+
console.log(chalk15.grey('Run "md4ai scan" to rescan at any time.'));
|
|
2793
3104
|
}
|
|
2794
3105
|
|
|
2795
3106
|
// dist/commands/import-bundle.js
|
|
2796
|
-
import { readFile as
|
|
2797
|
-
import { join as
|
|
3107
|
+
import { readFile as readFile9, writeFile as writeFile4, mkdir as mkdir3 } from "node:fs/promises";
|
|
3108
|
+
import { join as join13, dirname as dirname3 } from "node:path";
|
|
2798
3109
|
import { existsSync as existsSync10 } from "node:fs";
|
|
2799
|
-
import
|
|
3110
|
+
import chalk16 from "chalk";
|
|
2800
3111
|
import { confirm as confirm3, input as input6 } from "@inquirer/prompts";
|
|
2801
3112
|
async function importBundleCommand(zipPath) {
|
|
2802
3113
|
if (!existsSync10(zipPath)) {
|
|
2803
|
-
console.error(
|
|
3114
|
+
console.error(chalk16.red(`File not found: ${zipPath}`));
|
|
2804
3115
|
process.exit(1);
|
|
2805
3116
|
}
|
|
2806
3117
|
const JSZip = (await import("jszip")).default;
|
|
2807
|
-
const zipData = await
|
|
3118
|
+
const zipData = await readFile9(zipPath);
|
|
2808
3119
|
const zip = await JSZip.loadAsync(zipData);
|
|
2809
3120
|
const manifestFile = zip.file("manifest.json");
|
|
2810
3121
|
if (!manifestFile) {
|
|
2811
|
-
console.error(
|
|
3122
|
+
console.error(chalk16.red("Invalid bundle: missing manifest.json"));
|
|
2812
3123
|
process.exit(1);
|
|
2813
3124
|
}
|
|
2814
3125
|
const manifest = JSON.parse(await manifestFile.async("string"));
|
|
2815
|
-
console.log(
|
|
3126
|
+
console.log(chalk16.blue(`
|
|
2816
3127
|
Bundle: ${manifest.folderName}`));
|
|
2817
3128
|
console.log(` Exported by: ${manifest.ownerEmail}`);
|
|
2818
3129
|
console.log(` Exported at: ${manifest.exportedAt}`);
|
|
@@ -2826,10 +3137,10 @@ Bundle: ${manifest.folderName}`));
|
|
|
2826
3137
|
}
|
|
2827
3138
|
}
|
|
2828
3139
|
if (files.length === 0) {
|
|
2829
|
-
console.log(
|
|
3140
|
+
console.log(chalk16.yellow("No Claude config files found in bundle."));
|
|
2830
3141
|
return;
|
|
2831
3142
|
}
|
|
2832
|
-
console.log(
|
|
3143
|
+
console.log(chalk16.blue(`
|
|
2833
3144
|
Files to extract:`));
|
|
2834
3145
|
for (const f of files) {
|
|
2835
3146
|
console.log(` ${f.filePath}`);
|
|
@@ -2842,34 +3153,34 @@ Files to extract:`));
|
|
|
2842
3153
|
message: `Extract ${files.length} file(s) to ${targetDir}?`
|
|
2843
3154
|
});
|
|
2844
3155
|
if (!proceed) {
|
|
2845
|
-
console.log(
|
|
3156
|
+
console.log(chalk16.yellow("Cancelled."));
|
|
2846
3157
|
return;
|
|
2847
3158
|
}
|
|
2848
3159
|
for (const file of files) {
|
|
2849
|
-
const fullPath =
|
|
2850
|
-
const dir =
|
|
3160
|
+
const fullPath = join13(targetDir, file.filePath);
|
|
3161
|
+
const dir = dirname3(fullPath);
|
|
2851
3162
|
if (!existsSync10(dir)) {
|
|
2852
3163
|
await mkdir3(dir, { recursive: true });
|
|
2853
3164
|
}
|
|
2854
3165
|
await writeFile4(fullPath, file.content, "utf-8");
|
|
2855
|
-
console.log(
|
|
3166
|
+
console.log(chalk16.green(` \u2713 ${file.filePath}`));
|
|
2856
3167
|
}
|
|
2857
|
-
console.log(
|
|
3168
|
+
console.log(chalk16.green(`
|
|
2858
3169
|
Done! ${files.length} file(s) extracted to ${targetDir}`));
|
|
2859
3170
|
}
|
|
2860
3171
|
|
|
2861
3172
|
// dist/commands/admin-update-tool.js
|
|
2862
3173
|
init_auth();
|
|
2863
|
-
import
|
|
3174
|
+
import chalk17 from "chalk";
|
|
2864
3175
|
var VALID_CATEGORIES = ["framework", "runtime", "cli", "mcp", "package", "database", "other"];
|
|
2865
3176
|
async function adminUpdateToolCommand(options) {
|
|
2866
3177
|
const { supabase } = await getAuthenticatedClient();
|
|
2867
3178
|
if (!options.name) {
|
|
2868
|
-
console.error(
|
|
3179
|
+
console.error(chalk17.red("--name is required."));
|
|
2869
3180
|
process.exit(1);
|
|
2870
3181
|
}
|
|
2871
3182
|
if (options.category && !VALID_CATEGORIES.includes(options.category)) {
|
|
2872
|
-
console.error(
|
|
3183
|
+
console.error(chalk17.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
|
|
2873
3184
|
process.exit(1);
|
|
2874
3185
|
}
|
|
2875
3186
|
const { data: existing } = await supabase.from("tools_registry").select("id, name, display_name, category").eq("name", options.name).maybeSingle();
|
|
@@ -2891,13 +3202,13 @@ async function adminUpdateToolCommand(options) {
|
|
|
2891
3202
|
updates.notes = options.notes;
|
|
2892
3203
|
const { error } = await supabase.from("tools_registry").update(updates).eq("id", existing.id);
|
|
2893
3204
|
if (error) {
|
|
2894
|
-
console.error(
|
|
3205
|
+
console.error(chalk17.red(`Failed to update: ${error.message}`));
|
|
2895
3206
|
process.exit(1);
|
|
2896
3207
|
}
|
|
2897
|
-
console.log(
|
|
3208
|
+
console.log(chalk17.green(`Updated "${existing.display_name}" in the registry.`));
|
|
2898
3209
|
} else {
|
|
2899
3210
|
if (!options.display || !options.category) {
|
|
2900
|
-
console.error(
|
|
3211
|
+
console.error(chalk17.red("New tools require --display and --category."));
|
|
2901
3212
|
process.exit(1);
|
|
2902
3213
|
}
|
|
2903
3214
|
const { error } = await supabase.from("tools_registry").insert({
|
|
@@ -2911,25 +3222,25 @@ async function adminUpdateToolCommand(options) {
|
|
|
2911
3222
|
notes: options.notes ?? null
|
|
2912
3223
|
});
|
|
2913
3224
|
if (error) {
|
|
2914
|
-
console.error(
|
|
3225
|
+
console.error(chalk17.red(`Failed to create: ${error.message}`));
|
|
2915
3226
|
process.exit(1);
|
|
2916
3227
|
}
|
|
2917
|
-
console.log(
|
|
3228
|
+
console.log(chalk17.green(`Added "${options.display}" to the registry.`));
|
|
2918
3229
|
}
|
|
2919
3230
|
}
|
|
2920
3231
|
|
|
2921
3232
|
// dist/commands/admin-list-tools.js
|
|
2922
3233
|
init_auth();
|
|
2923
|
-
import
|
|
3234
|
+
import chalk18 from "chalk";
|
|
2924
3235
|
async function adminListToolsCommand() {
|
|
2925
3236
|
const { supabase } = await getAuthenticatedClient();
|
|
2926
3237
|
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("category").order("display_name");
|
|
2927
3238
|
if (error) {
|
|
2928
|
-
console.error(
|
|
3239
|
+
console.error(chalk18.red(`Failed to fetch tools: ${error.message}`));
|
|
2929
3240
|
process.exit(1);
|
|
2930
3241
|
}
|
|
2931
3242
|
if (!tools?.length) {
|
|
2932
|
-
console.log(
|
|
3243
|
+
console.log(chalk18.yellow("No tools in the registry."));
|
|
2933
3244
|
return;
|
|
2934
3245
|
}
|
|
2935
3246
|
const nameW = Math.max(16, ...tools.map((t) => t.display_name.length));
|
|
@@ -2943,7 +3254,7 @@ async function adminListToolsCommand() {
|
|
|
2943
3254
|
"Beta".padEnd(betaW),
|
|
2944
3255
|
"Last Checked"
|
|
2945
3256
|
].join(" ");
|
|
2946
|
-
console.log(
|
|
3257
|
+
console.log(chalk18.bold(header));
|
|
2947
3258
|
console.log("\u2500".repeat(header.length));
|
|
2948
3259
|
for (const tool of tools) {
|
|
2949
3260
|
const lastChecked = tool.updated_at ? formatRelative(new Date(tool.updated_at)) : "\u2014";
|
|
@@ -2956,7 +3267,7 @@ async function adminListToolsCommand() {
|
|
|
2956
3267
|
].join(" ");
|
|
2957
3268
|
console.log(row);
|
|
2958
3269
|
}
|
|
2959
|
-
console.log(
|
|
3270
|
+
console.log(chalk18.grey(`
|
|
2960
3271
|
${tools.length} tool(s) in registry.`));
|
|
2961
3272
|
}
|
|
2962
3273
|
function formatRelative(date) {
|
|
@@ -2974,7 +3285,7 @@ function formatRelative(date) {
|
|
|
2974
3285
|
|
|
2975
3286
|
// dist/commands/admin-fetch-versions.js
|
|
2976
3287
|
init_auth();
|
|
2977
|
-
import
|
|
3288
|
+
import chalk19 from "chalk";
|
|
2978
3289
|
|
|
2979
3290
|
// dist/commands/version-sources.js
|
|
2980
3291
|
var VERSION_SOURCES = {
|
|
@@ -3003,11 +3314,11 @@ async function adminFetchVersionsCommand() {
|
|
|
3003
3314
|
const { supabase } = await getAuthenticatedClient();
|
|
3004
3315
|
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("display_name");
|
|
3005
3316
|
if (error) {
|
|
3006
|
-
console.error(
|
|
3317
|
+
console.error(chalk19.red(`Failed to fetch tools: ${error.message}`));
|
|
3007
3318
|
process.exit(1);
|
|
3008
3319
|
}
|
|
3009
3320
|
if (!tools?.length) {
|
|
3010
|
-
console.log(
|
|
3321
|
+
console.log(chalk19.yellow("No tools in the registry."));
|
|
3011
3322
|
}
|
|
3012
3323
|
const { data: allProjectToolings } = await supabase.from("project_toolings").select("tool_name, detection_source").is("tool_id", null);
|
|
3013
3324
|
const registeredNames = new Set((tools ?? []).map((t) => t.name));
|
|
@@ -3018,7 +3329,7 @@ async function adminFetchVersionsCommand() {
|
|
|
3018
3329
|
}
|
|
3019
3330
|
}
|
|
3020
3331
|
if (unregisteredNames.size > 0) {
|
|
3021
|
-
console.log(
|
|
3332
|
+
console.log(chalk19.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
|
|
3022
3333
|
`));
|
|
3023
3334
|
for (const name of unregisteredNames) {
|
|
3024
3335
|
const displayName = name;
|
|
@@ -3036,10 +3347,10 @@ async function adminFetchVersionsCommand() {
|
|
|
3036
3347
|
}
|
|
3037
3348
|
}
|
|
3038
3349
|
if (!tools?.length) {
|
|
3039
|
-
console.log(
|
|
3350
|
+
console.log(chalk19.yellow("No tools to fetch."));
|
|
3040
3351
|
return;
|
|
3041
3352
|
}
|
|
3042
|
-
console.log(
|
|
3353
|
+
console.log(chalk19.bold(`Fetching versions for ${tools.length} tool(s)...
|
|
3043
3354
|
`));
|
|
3044
3355
|
const results = await Promise.all(tools.map(async (tool) => {
|
|
3045
3356
|
const source = VERSION_SOURCES[tool.name] ?? { type: "npm", package: tool.name };
|
|
@@ -3078,10 +3389,10 @@ async function adminFetchVersionsCommand() {
|
|
|
3078
3389
|
"Beta".padEnd(betaW),
|
|
3079
3390
|
"Status".padEnd(statusW)
|
|
3080
3391
|
].join(" ");
|
|
3081
|
-
console.log(
|
|
3392
|
+
console.log(chalk19.bold(header));
|
|
3082
3393
|
console.log("\u2500".repeat(header.length));
|
|
3083
3394
|
for (const result of results) {
|
|
3084
|
-
const statusColour = result.status === "updated" ?
|
|
3395
|
+
const statusColour = result.status === "updated" ? chalk19.green : result.status === "unchanged" ? chalk19.grey : result.status === "failed" ? chalk19.red : chalk19.yellow;
|
|
3085
3396
|
const row = [
|
|
3086
3397
|
result.displayName.padEnd(nameW),
|
|
3087
3398
|
(result.stable ?? "\u2014").padEnd(stableW),
|
|
@@ -3094,8 +3405,8 @@ async function adminFetchVersionsCommand() {
|
|
|
3094
3405
|
const unchanged = results.filter((r) => r.status === "unchanged").length;
|
|
3095
3406
|
const failed = results.filter((r) => r.status === "failed").length;
|
|
3096
3407
|
const noSource = results.filter((r) => r.status === "no source").length;
|
|
3097
|
-
console.log(
|
|
3098
|
-
${results.length} tool(s): `) +
|
|
3408
|
+
console.log(chalk19.grey(`
|
|
3409
|
+
${results.length} tool(s): `) + chalk19.green(`${updated} updated`) + ", " + chalk19.grey(`${unchanged} unchanged`) + ", " + chalk19.red(`${failed} failed`) + ", " + chalk19.yellow(`${noSource} no source`));
|
|
3099
3410
|
}
|
|
3100
3411
|
async function fetchVersions(source) {
|
|
3101
3412
|
const controller = new AbortController();
|
|
@@ -3153,10 +3464,10 @@ async function fetchGitHubVersions(repo, signal) {
|
|
|
3153
3464
|
init_mcp_watch();
|
|
3154
3465
|
|
|
3155
3466
|
// dist/commands/init-manifest.js
|
|
3156
|
-
import { resolve as resolve5, join as
|
|
3157
|
-
import { readFile as
|
|
3467
|
+
import { resolve as resolve5, join as join15, relative as relative4, dirname as dirname4 } from "node:path";
|
|
3468
|
+
import { readFile as readFile11, writeFile as writeFile5, mkdir as mkdir4 } from "node:fs/promises";
|
|
3158
3469
|
import { existsSync as existsSync12 } from "node:fs";
|
|
3159
|
-
import
|
|
3470
|
+
import chalk21 from "chalk";
|
|
3160
3471
|
var SECRET_PATTERNS = [
|
|
3161
3472
|
/_KEY$/i,
|
|
3162
3473
|
/_SECRET$/i,
|
|
@@ -3172,7 +3483,7 @@ function isLikelySecret(name) {
|
|
|
3172
3483
|
async function discoverEnvFiles(projectRoot) {
|
|
3173
3484
|
const apps = [];
|
|
3174
3485
|
for (const envName of [".env", ".env.local", ".env.example"]) {
|
|
3175
|
-
const envPath =
|
|
3486
|
+
const envPath = join15(projectRoot, envName);
|
|
3176
3487
|
if (existsSync12(envPath)) {
|
|
3177
3488
|
const vars = await extractVarNames(envPath);
|
|
3178
3489
|
if (vars.length > 0) {
|
|
@@ -3183,11 +3494,11 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
3183
3494
|
}
|
|
3184
3495
|
const subdirs = ["web", "cli", "api", "app", "server", "packages"];
|
|
3185
3496
|
for (const sub of subdirs) {
|
|
3186
|
-
const subDir =
|
|
3497
|
+
const subDir = join15(projectRoot, sub);
|
|
3187
3498
|
if (!existsSync12(subDir))
|
|
3188
3499
|
continue;
|
|
3189
3500
|
for (const envName of [".env.local", ".env", ".env.example"]) {
|
|
3190
|
-
const envPath =
|
|
3501
|
+
const envPath = join15(subDir, envName);
|
|
3191
3502
|
if (existsSync12(envPath)) {
|
|
3192
3503
|
const vars = await extractVarNames(envPath);
|
|
3193
3504
|
if (vars.length > 0) {
|
|
@@ -3204,7 +3515,7 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
3204
3515
|
return apps;
|
|
3205
3516
|
}
|
|
3206
3517
|
async function extractVarNames(envPath) {
|
|
3207
|
-
const content = await
|
|
3518
|
+
const content = await readFile11(envPath, "utf-8");
|
|
3208
3519
|
const vars = [];
|
|
3209
3520
|
for (const line of content.split("\n")) {
|
|
3210
3521
|
const trimmed = line.trim();
|
|
@@ -3249,38 +3560,38 @@ function generateManifest(apps) {
|
|
|
3249
3560
|
}
|
|
3250
3561
|
async function initManifestCommand() {
|
|
3251
3562
|
const projectRoot = resolve5(process.cwd());
|
|
3252
|
-
const manifestPath =
|
|
3563
|
+
const manifestPath = join15(projectRoot, "docs", "reference", "env-manifest.md");
|
|
3253
3564
|
if (existsSync12(manifestPath)) {
|
|
3254
|
-
console.log(
|
|
3255
|
-
console.log(
|
|
3565
|
+
console.log(chalk21.yellow(`Manifest already exists: ${relative4(projectRoot, manifestPath)}`));
|
|
3566
|
+
console.log(chalk21.dim("Edit it directly to make changes."));
|
|
3256
3567
|
return;
|
|
3257
3568
|
}
|
|
3258
|
-
console.log(
|
|
3569
|
+
console.log(chalk21.blue("Scanning for .env files...\n"));
|
|
3259
3570
|
const apps = await discoverEnvFiles(projectRoot);
|
|
3260
3571
|
if (apps.length === 0) {
|
|
3261
|
-
console.log(
|
|
3262
|
-
console.log(
|
|
3572
|
+
console.log(chalk21.yellow("No .env files found. Create a manifest manually at:"));
|
|
3573
|
+
console.log(chalk21.cyan(` docs/reference/env-manifest.md`));
|
|
3263
3574
|
return;
|
|
3264
3575
|
}
|
|
3265
3576
|
for (const app of apps) {
|
|
3266
|
-
console.log(` ${
|
|
3577
|
+
console.log(` ${chalk21.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
|
|
3267
3578
|
}
|
|
3268
3579
|
const content = generateManifest(apps);
|
|
3269
|
-
const dir =
|
|
3580
|
+
const dir = dirname4(manifestPath);
|
|
3270
3581
|
if (!existsSync12(dir)) {
|
|
3271
3582
|
await mkdir4(dir, { recursive: true });
|
|
3272
3583
|
}
|
|
3273
3584
|
await writeFile5(manifestPath, content, "utf-8");
|
|
3274
|
-
console.log(
|
|
3275
|
-
Manifest created: ${
|
|
3276
|
-
console.log(
|
|
3277
|
-
console.log(
|
|
3585
|
+
console.log(chalk21.green(`
|
|
3586
|
+
Manifest created: ${relative4(projectRoot, manifestPath)}`));
|
|
3587
|
+
console.log(chalk21.dim("Review and edit the file \u2014 it is your source of truth."));
|
|
3588
|
+
console.log(chalk21.dim("Then run `md4ai scan` to verify against your environments."));
|
|
3278
3589
|
}
|
|
3279
3590
|
|
|
3280
3591
|
// dist/commands/update.js
|
|
3281
3592
|
init_check_update();
|
|
3282
3593
|
init_config();
|
|
3283
|
-
import
|
|
3594
|
+
import chalk22 from "chalk";
|
|
3284
3595
|
import { execFileSync as execFileSync6, spawn } from "node:child_process";
|
|
3285
3596
|
async function fetchLatestVersion() {
|
|
3286
3597
|
try {
|
|
@@ -3329,13 +3640,13 @@ function spawnPostUpdate() {
|
|
|
3329
3640
|
}
|
|
3330
3641
|
async function postUpdateFlow() {
|
|
3331
3642
|
const { confirm: confirm4 } = await import("@inquirer/prompts");
|
|
3332
|
-
console.log(
|
|
3643
|
+
console.log(chalk22.green(`
|
|
3333
3644
|
md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.
|
|
3334
3645
|
`));
|
|
3335
3646
|
const creds = await loadCredentials();
|
|
3336
3647
|
const isLoggedIn = !!creds?.accessToken && Date.now() < creds.expiresAt;
|
|
3337
3648
|
if (!isLoggedIn) {
|
|
3338
|
-
console.log(
|
|
3649
|
+
console.log(chalk22.yellow(" You are not logged in.\n"));
|
|
3339
3650
|
const wantLogin = await confirm4({
|
|
3340
3651
|
message: "Log in now?",
|
|
3341
3652
|
default: true
|
|
@@ -3346,7 +3657,7 @@ async function postUpdateFlow() {
|
|
|
3346
3657
|
await loginCommand2();
|
|
3347
3658
|
console.log("");
|
|
3348
3659
|
} else {
|
|
3349
|
-
console.log(
|
|
3660
|
+
console.log(chalk22.dim("\nRun md4ai login when you're ready.\n"));
|
|
3350
3661
|
return;
|
|
3351
3662
|
}
|
|
3352
3663
|
}
|
|
@@ -3380,7 +3691,7 @@ async function postUpdateFlow() {
|
|
|
3380
3691
|
const { mcpWatchCommand: mcpWatchCommand2 } = await Promise.resolve().then(() => (init_mcp_watch(), mcp_watch_exports));
|
|
3381
3692
|
await mcpWatchCommand2();
|
|
3382
3693
|
} else {
|
|
3383
|
-
console.log(
|
|
3694
|
+
console.log(chalk22.dim("\nYou can start monitoring later with: md4ai mcp-watch\n"));
|
|
3384
3695
|
}
|
|
3385
3696
|
}
|
|
3386
3697
|
async function updateCommand(options) {
|
|
@@ -3388,19 +3699,19 @@ async function updateCommand(options) {
|
|
|
3388
3699
|
await postUpdateFlow();
|
|
3389
3700
|
return;
|
|
3390
3701
|
}
|
|
3391
|
-
console.log(
|
|
3702
|
+
console.log(chalk22.blue(`
|
|
3392
3703
|
Current version: v${CURRENT_VERSION}`));
|
|
3393
|
-
console.log(
|
|
3704
|
+
console.log(chalk22.dim(" Checking for updates...\n"));
|
|
3394
3705
|
const latest = await fetchLatestVersion();
|
|
3395
3706
|
if (!latest) {
|
|
3396
|
-
console.log(
|
|
3707
|
+
console.log(chalk22.yellow(" Could not reach the npm registry. Check your internet connection."));
|
|
3397
3708
|
process.exit(1);
|
|
3398
3709
|
}
|
|
3399
3710
|
if (!isNewer2(latest, CURRENT_VERSION)) {
|
|
3400
3711
|
await postUpdateFlow();
|
|
3401
3712
|
return;
|
|
3402
3713
|
}
|
|
3403
|
-
console.log(
|
|
3714
|
+
console.log(chalk22.white(" Update available: ") + chalk22.dim(`v${CURRENT_VERSION}`) + chalk22.white(" \u2192 ") + chalk22.green.bold(`v${latest}
|
|
3404
3715
|
`));
|
|
3405
3716
|
const { confirm: confirm4 } = await import("@inquirer/prompts");
|
|
3406
3717
|
const wantUpdate = await confirm4({
|
|
@@ -3408,20 +3719,38 @@ async function updateCommand(options) {
|
|
|
3408
3719
|
default: true
|
|
3409
3720
|
});
|
|
3410
3721
|
if (!wantUpdate) {
|
|
3411
|
-
console.log(
|
|
3722
|
+
console.log(chalk22.dim("\nUpdate skipped.\n"));
|
|
3412
3723
|
return;
|
|
3413
3724
|
}
|
|
3414
|
-
console.log(
|
|
3725
|
+
console.log(chalk22.blue("\n Installing...\n"));
|
|
3415
3726
|
const ok = runNpmInstall();
|
|
3416
3727
|
if (!ok) {
|
|
3417
|
-
console.log(
|
|
3418
|
-
console.log(
|
|
3728
|
+
console.log(chalk22.red("\n npm install failed. Try running manually:"));
|
|
3729
|
+
console.log(chalk22.cyan(" npm install -g md4ai\n"));
|
|
3419
3730
|
process.exit(1);
|
|
3420
3731
|
}
|
|
3421
|
-
console.log(
|
|
3732
|
+
console.log(chalk22.green("\n Updated successfully.\n"));
|
|
3422
3733
|
spawnPostUpdate();
|
|
3423
3734
|
}
|
|
3424
3735
|
|
|
3736
|
+
// dist/commands/config.js
|
|
3737
|
+
init_config();
|
|
3738
|
+
import chalk23 from "chalk";
|
|
3739
|
+
var ALLOWED_KEYS = ["vercel-token"];
|
|
3740
|
+
var KEY_MAP = {
|
|
3741
|
+
"vercel-token": "vercelToken"
|
|
3742
|
+
};
|
|
3743
|
+
async function configSetCommand(key, value) {
|
|
3744
|
+
if (!ALLOWED_KEYS.includes(key)) {
|
|
3745
|
+
console.error(chalk23.red(`Unknown config key: ${key}`));
|
|
3746
|
+
console.log(` Allowed keys: ${ALLOWED_KEYS.join(", ")}`);
|
|
3747
|
+
process.exit(1);
|
|
3748
|
+
}
|
|
3749
|
+
const credKey = KEY_MAP[key];
|
|
3750
|
+
await mergeCredentials({ [credKey]: value });
|
|
3751
|
+
console.log(chalk23.green(`Saved ${key}.`));
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3425
3754
|
// dist/index.js
|
|
3426
3755
|
init_check_update();
|
|
3427
3756
|
var program = new Command();
|
|
@@ -3444,6 +3773,8 @@ program.command("update").description("Check for updates, install, and optionall
|
|
|
3444
3773
|
program.command("check-update").description("Check if a newer version of md4ai is available").action(checkForUpdate);
|
|
3445
3774
|
program.command("mcp-watch").description("Monitor MCP server status on this device (runs until Ctrl+C)").action(mcpWatchCommand);
|
|
3446
3775
|
program.command("init-manifest").description("Scaffold a starter env-manifest.md from detected .env files").action(initManifestCommand);
|
|
3776
|
+
var config = program.command("config").description("Manage CLI configuration");
|
|
3777
|
+
config.command("set <key> <value>").description("Set a configuration value (e.g. vercel-token)").action(configSetCommand);
|
|
3447
3778
|
var admin = program.command("admin").description("Admin commands for managing the tools registry");
|
|
3448
3779
|
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);
|
|
3449
3780
|
admin.command("list-tools").description("List all tools in the master registry").action(adminListToolsCommand);
|