md4ai 0.9.4 → 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 +572 -262
- 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",
|
|
@@ -1151,12 +1347,12 @@ async function readClaudeConfigFiles(projectRoot) {
|
|
|
1151
1347
|
const files = [];
|
|
1152
1348
|
const seen = /* @__PURE__ */ new Set();
|
|
1153
1349
|
async function addFile(fullPath) {
|
|
1154
|
-
const relPath =
|
|
1350
|
+
const relPath = relative5(projectRoot, fullPath);
|
|
1155
1351
|
if (seen.has(relPath))
|
|
1156
1352
|
return;
|
|
1157
1353
|
seen.add(relPath);
|
|
1158
1354
|
try {
|
|
1159
|
-
const content = await
|
|
1355
|
+
const content = await readFile12(fullPath, "utf-8");
|
|
1160
1356
|
const fileStat = await stat(fullPath);
|
|
1161
1357
|
const lastMod = getGitLastModified(fullPath, projectRoot);
|
|
1162
1358
|
files.push({ filePath: relPath, content, sizeBytes: fileStat.size, lastModified: lastMod });
|
|
@@ -1164,22 +1360,22 @@ async function readClaudeConfigFiles(projectRoot) {
|
|
|
1164
1360
|
}
|
|
1165
1361
|
}
|
|
1166
1362
|
for (const pattern of configPatterns) {
|
|
1167
|
-
for await (const fullPath of
|
|
1363
|
+
for await (const fullPath of glob3(join16(projectRoot, pattern))) {
|
|
1168
1364
|
if (!existsSync13(fullPath))
|
|
1169
1365
|
continue;
|
|
1170
1366
|
await addFile(fullPath);
|
|
1171
1367
|
}
|
|
1172
1368
|
}
|
|
1173
|
-
const pkgPath =
|
|
1369
|
+
const pkgPath = join16(projectRoot, "package.json");
|
|
1174
1370
|
if (existsSync13(pkgPath)) {
|
|
1175
1371
|
try {
|
|
1176
|
-
const pkgContent = await
|
|
1372
|
+
const pkgContent = await readFile12(pkgPath, "utf-8");
|
|
1177
1373
|
const pkg = JSON.parse(pkgContent);
|
|
1178
1374
|
const preflightCmd = pkg.scripts?.preflight;
|
|
1179
1375
|
if (preflightCmd) {
|
|
1180
1376
|
const match = preflightCmd.match(/(?:bash\s+|sh\s+|\.\/)?(\S+\.(?:sh|bash|ts|js|mjs))/);
|
|
1181
1377
|
if (match) {
|
|
1182
|
-
const scriptPath =
|
|
1378
|
+
const scriptPath = join16(projectRoot, match[1]);
|
|
1183
1379
|
if (existsSync13(scriptPath)) {
|
|
1184
1380
|
await addFile(scriptPath);
|
|
1185
1381
|
}
|
|
@@ -1194,7 +1390,7 @@ function detectStaleFiles(allFiles, projectRoot) {
|
|
|
1194
1390
|
const stale = [];
|
|
1195
1391
|
const now = Date.now();
|
|
1196
1392
|
for (const file of allFiles) {
|
|
1197
|
-
const fullPath =
|
|
1393
|
+
const fullPath = join10(projectRoot, file);
|
|
1198
1394
|
const lastMod = getGitLastModified(fullPath, projectRoot);
|
|
1199
1395
|
if (lastMod) {
|
|
1200
1396
|
const days = Math.floor((now - new Date(lastMod).getTime()) / (1e3 * 60 * 60 * 24));
|
|
@@ -1336,7 +1532,7 @@ var init_html_generator = __esm({
|
|
|
1336
1532
|
});
|
|
1337
1533
|
|
|
1338
1534
|
// dist/check-update.js
|
|
1339
|
-
import
|
|
1535
|
+
import chalk9 from "chalk";
|
|
1340
1536
|
async function fetchLatest() {
|
|
1341
1537
|
try {
|
|
1342
1538
|
const controller = new AbortController();
|
|
@@ -1355,12 +1551,12 @@ async function fetchLatest() {
|
|
|
1355
1551
|
}
|
|
1356
1552
|
function printUpdateBanner(latest) {
|
|
1357
1553
|
console.log("");
|
|
1358
|
-
console.log(
|
|
1359
|
-
console.log(
|
|
1360
|
-
console.log(
|
|
1361
|
-
console.log(
|
|
1362
|
-
console.log(
|
|
1363
|
-
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"));
|
|
1364
1560
|
console.log("");
|
|
1365
1561
|
}
|
|
1366
1562
|
async function checkForUpdate() {
|
|
@@ -1370,7 +1566,7 @@ async function checkForUpdate() {
|
|
|
1370
1566
|
if (latest !== CURRENT_VERSION && isNewer(latest, CURRENT_VERSION)) {
|
|
1371
1567
|
printUpdateBanner(latest);
|
|
1372
1568
|
} else {
|
|
1373
|
-
console.log(
|
|
1569
|
+
console.log(chalk9.green(`md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.`));
|
|
1374
1570
|
}
|
|
1375
1571
|
}
|
|
1376
1572
|
async function autoCheckForUpdate() {
|
|
@@ -1399,7 +1595,7 @@ var CURRENT_VERSION;
|
|
|
1399
1595
|
var init_check_update = __esm({
|
|
1400
1596
|
"dist/check-update.js"() {
|
|
1401
1597
|
"use strict";
|
|
1402
|
-
CURRENT_VERSION = true ? "0.9.
|
|
1598
|
+
CURRENT_VERSION = true ? "0.9.5" : "0.0.0-dev";
|
|
1403
1599
|
}
|
|
1404
1600
|
});
|
|
1405
1601
|
|
|
@@ -1427,6 +1623,87 @@ var init_push_toolings = __esm({
|
|
|
1427
1623
|
}
|
|
1428
1624
|
});
|
|
1429
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
|
+
|
|
1430
1707
|
// dist/device-utils.js
|
|
1431
1708
|
import { hostname as hostname2, platform as platform2 } from "node:os";
|
|
1432
1709
|
function detectOs2() {
|
|
@@ -1473,16 +1750,16 @@ __export(map_exports, {
|
|
|
1473
1750
|
import { resolve as resolve3, basename } from "node:path";
|
|
1474
1751
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
1475
1752
|
import { existsSync as existsSync7 } from "node:fs";
|
|
1476
|
-
import
|
|
1753
|
+
import chalk11 from "chalk";
|
|
1477
1754
|
import { select as select3, input as input5, confirm as confirm2 } from "@inquirer/prompts";
|
|
1478
1755
|
async function mapCommand(path, options) {
|
|
1479
1756
|
await checkForUpdate();
|
|
1480
1757
|
const projectRoot = resolve3(path ?? process.cwd());
|
|
1481
1758
|
if (!existsSync7(projectRoot)) {
|
|
1482
|
-
console.error(
|
|
1759
|
+
console.error(chalk11.red(`Path not found: ${projectRoot}`));
|
|
1483
1760
|
process.exit(1);
|
|
1484
1761
|
}
|
|
1485
|
-
console.log(
|
|
1762
|
+
console.log(chalk11.blue(`Scanning: ${projectRoot}
|
|
1486
1763
|
`));
|
|
1487
1764
|
const result = await scanProject(projectRoot);
|
|
1488
1765
|
console.log(` Files found: ${result.graph.nodes.length}`);
|
|
@@ -1500,7 +1777,7 @@ async function mapCommand(path, options) {
|
|
|
1500
1777
|
const htmlPath = resolve3(outputDir, "index.html");
|
|
1501
1778
|
const html = generateOfflineHtml(result, projectRoot);
|
|
1502
1779
|
await writeFile2(htmlPath, html, "utf-8");
|
|
1503
|
-
console.log(
|
|
1780
|
+
console.log(chalk11.green(`
|
|
1504
1781
|
Local preview: ${htmlPath}`));
|
|
1505
1782
|
if (!options.offline) {
|
|
1506
1783
|
try {
|
|
@@ -1509,9 +1786,9 @@ Local preview: ${htmlPath}`));
|
|
|
1509
1786
|
if (devicePaths?.length) {
|
|
1510
1787
|
const { folder_id, device_name } = devicePaths[0];
|
|
1511
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);
|
|
1512
|
-
if (proposedFiles?.length) {
|
|
1789
|
+
if (proposedFiles?.length && process.stdin.isTTY) {
|
|
1513
1790
|
const { checkbox } = await import("@inquirer/prompts");
|
|
1514
|
-
console.log(
|
|
1791
|
+
console.log(chalk11.yellow(`
|
|
1515
1792
|
${proposedFiles.length} file(s) proposed for deletion:
|
|
1516
1793
|
`));
|
|
1517
1794
|
const toDelete = await checkbox({
|
|
@@ -1527,9 +1804,9 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1527
1804
|
const { unlink } = await import("node:fs/promises");
|
|
1528
1805
|
await unlink(fullPath);
|
|
1529
1806
|
await supabase.from("folder_files").delete().eq("id", file.id);
|
|
1530
|
-
console.log(
|
|
1807
|
+
console.log(chalk11.green(` Deleted: ${file.file_path}`));
|
|
1531
1808
|
} catch (err) {
|
|
1532
|
-
console.error(
|
|
1809
|
+
console.error(chalk11.red(` Failed to delete ${file.file_path}: ${err}`));
|
|
1533
1810
|
}
|
|
1534
1811
|
}
|
|
1535
1812
|
const keptIds = proposedFiles.filter((f) => !toDelete.some((d) => d.id === f.id)).map((f) => f.id);
|
|
@@ -1537,9 +1814,11 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1537
1814
|
for (const id of keptIds) {
|
|
1538
1815
|
await supabase.from("folder_files").update({ proposed_for_deletion: false, proposed_at: null, proposed_by: null }).eq("id", id);
|
|
1539
1816
|
}
|
|
1540
|
-
console.log(
|
|
1817
|
+
console.log(chalk11.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
|
|
1541
1818
|
}
|
|
1542
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).`));
|
|
1543
1822
|
}
|
|
1544
1823
|
const { error } = await supabase.from("claude_folders").update({
|
|
1545
1824
|
graph_json: result.graph,
|
|
@@ -1551,17 +1830,20 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1551
1830
|
data_hash: result.dataHash
|
|
1552
1831
|
}).eq("id", folder_id);
|
|
1553
1832
|
if (error) {
|
|
1554
|
-
console.error(
|
|
1833
|
+
console.error(chalk11.yellow(`Sync warning: ${error.message}`));
|
|
1555
1834
|
} else {
|
|
1556
1835
|
await pushToolings(supabase, folder_id, result.toolings);
|
|
1836
|
+
if (result.envManifest) {
|
|
1837
|
+
await pushHealthResults(supabase, folder_id, result.envManifest);
|
|
1838
|
+
}
|
|
1557
1839
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
|
|
1558
1840
|
await saveState({
|
|
1559
1841
|
lastFolderId: folder_id,
|
|
1560
1842
|
lastDeviceName: device_name,
|
|
1561
1843
|
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1562
1844
|
});
|
|
1563
|
-
console.log(
|
|
1564
|
-
console.log(
|
|
1845
|
+
console.log(chalk11.green("Synced to Supabase."));
|
|
1846
|
+
console.log(chalk11.cyan(`
|
|
1565
1847
|
https://www.md4ai.com/project/${folder_id}
|
|
1566
1848
|
`));
|
|
1567
1849
|
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
@@ -1575,14 +1857,18 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1575
1857
|
last_modified: file.lastModified
|
|
1576
1858
|
}, { onConflict: "folder_id,file_path" });
|
|
1577
1859
|
}
|
|
1578
|
-
console.log(
|
|
1860
|
+
console.log(chalk11.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
1579
1861
|
}
|
|
1580
1862
|
}
|
|
1581
1863
|
} else {
|
|
1582
|
-
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
|
+
}
|
|
1583
1869
|
const shouldLink = await confirm2({ message: "Would you like to link it now?" });
|
|
1584
1870
|
if (!shouldLink) {
|
|
1585
|
-
console.log(
|
|
1871
|
+
console.log(chalk11.dim("Skipped \u2014 local preview still generated."));
|
|
1586
1872
|
} else {
|
|
1587
1873
|
const { supabase: sb, userId } = await getAuthenticatedClient();
|
|
1588
1874
|
const { data: folders } = await sb.from("claude_folders").select("id, name").order("name");
|
|
@@ -1602,11 +1888,11 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1602
1888
|
});
|
|
1603
1889
|
const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId, name: projectName }).select("id").single();
|
|
1604
1890
|
if (createErr || !newFolder) {
|
|
1605
|
-
console.error(
|
|
1891
|
+
console.error(chalk11.red(`Failed to create project: ${createErr?.message}`));
|
|
1606
1892
|
return;
|
|
1607
1893
|
}
|
|
1608
1894
|
folderId = newFolder.id;
|
|
1609
|
-
console.log(
|
|
1895
|
+
console.log(chalk11.green(`Created project "${projectName}".`));
|
|
1610
1896
|
} else {
|
|
1611
1897
|
folderId = chosen;
|
|
1612
1898
|
}
|
|
@@ -1635,6 +1921,9 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1635
1921
|
data_hash: result.dataHash
|
|
1636
1922
|
}).eq("id", folderId);
|
|
1637
1923
|
await pushToolings(sb, folderId, result.toolings);
|
|
1924
|
+
if (result.envManifest) {
|
|
1925
|
+
await pushHealthResults(sb, folderId, result.envManifest);
|
|
1926
|
+
}
|
|
1638
1927
|
const configFiles = await readClaudeConfigFiles(projectRoot);
|
|
1639
1928
|
for (const file of configFiles) {
|
|
1640
1929
|
await sb.from("folder_files").upsert({
|
|
@@ -1650,14 +1939,14 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1650
1939
|
lastDeviceName: deviceName,
|
|
1651
1940
|
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1652
1941
|
});
|
|
1653
|
-
console.log(
|
|
1654
|
-
console.log(
|
|
1942
|
+
console.log(chalk11.green("\nLinked and synced."));
|
|
1943
|
+
console.log(chalk11.cyan(`
|
|
1655
1944
|
https://www.md4ai.com/project/${folderId}
|
|
1656
1945
|
`));
|
|
1657
1946
|
}
|
|
1658
1947
|
}
|
|
1659
1948
|
} catch {
|
|
1660
|
-
console.log(
|
|
1949
|
+
console.log(chalk11.yellow("Not logged in \u2014 local preview only."));
|
|
1661
1950
|
}
|
|
1662
1951
|
}
|
|
1663
1952
|
}
|
|
@@ -1670,6 +1959,7 @@ var init_map = __esm({
|
|
|
1670
1959
|
init_html_generator();
|
|
1671
1960
|
init_check_update();
|
|
1672
1961
|
init_push_toolings();
|
|
1962
|
+
init_push_health_results();
|
|
1673
1963
|
init_device_utils();
|
|
1674
1964
|
}
|
|
1675
1965
|
});
|
|
@@ -1679,20 +1969,20 @@ var sync_exports = {};
|
|
|
1679
1969
|
__export(sync_exports, {
|
|
1680
1970
|
syncCommand: () => syncCommand
|
|
1681
1971
|
});
|
|
1682
|
-
import
|
|
1972
|
+
import chalk14 from "chalk";
|
|
1683
1973
|
async function syncCommand(options) {
|
|
1684
1974
|
const { supabase } = await getAuthenticatedClient();
|
|
1685
1975
|
if (options.all) {
|
|
1686
1976
|
const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path");
|
|
1687
1977
|
if (error || !devices?.length) {
|
|
1688
|
-
console.error(
|
|
1978
|
+
console.error(chalk14.red("No devices found."));
|
|
1689
1979
|
process.exit(1);
|
|
1690
1980
|
}
|
|
1691
1981
|
for (const device of devices) {
|
|
1692
|
-
console.log(
|
|
1982
|
+
console.log(chalk14.blue(`Syncing: ${device.path}`));
|
|
1693
1983
|
const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
1694
1984
|
if (proposedAll?.length) {
|
|
1695
|
-
console.log(
|
|
1985
|
+
console.log(chalk14.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
1696
1986
|
}
|
|
1697
1987
|
try {
|
|
1698
1988
|
const result = await scanProject(device.path);
|
|
@@ -1707,28 +1997,28 @@ async function syncCommand(options) {
|
|
|
1707
1997
|
}).eq("id", device.folder_id);
|
|
1708
1998
|
await pushToolings(supabase, device.folder_id, result.toolings);
|
|
1709
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);
|
|
1710
|
-
console.log(
|
|
2000
|
+
console.log(chalk14.green(` Done: ${device.device_name}`));
|
|
1711
2001
|
} catch (err) {
|
|
1712
|
-
console.error(
|
|
2002
|
+
console.error(chalk14.red(` Failed: ${device.path}: ${err}`));
|
|
1713
2003
|
}
|
|
1714
2004
|
}
|
|
1715
2005
|
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1716
|
-
console.log(
|
|
2006
|
+
console.log(chalk14.green("\nAll devices synced."));
|
|
1717
2007
|
} else {
|
|
1718
2008
|
const state = await loadState();
|
|
1719
2009
|
if (!state.lastFolderId) {
|
|
1720
|
-
console.error(
|
|
2010
|
+
console.error(chalk14.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
|
|
1721
2011
|
process.exit(1);
|
|
1722
2012
|
}
|
|
1723
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();
|
|
1724
2014
|
if (!device) {
|
|
1725
|
-
console.error(
|
|
2015
|
+
console.error(chalk14.red("Could not find last synced device/folder."));
|
|
1726
2016
|
process.exit(1);
|
|
1727
2017
|
}
|
|
1728
|
-
console.log(
|
|
2018
|
+
console.log(chalk14.blue(`Syncing: ${device.path}`));
|
|
1729
2019
|
const { data: proposedSingle } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
1730
2020
|
if (proposedSingle?.length) {
|
|
1731
|
-
console.log(
|
|
2021
|
+
console.log(chalk14.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
1732
2022
|
}
|
|
1733
2023
|
const result = await scanProject(device.path);
|
|
1734
2024
|
await supabase.from("claude_folders").update({
|
|
@@ -1742,7 +2032,7 @@ async function syncCommand(options) {
|
|
|
1742
2032
|
await pushToolings(supabase, device.folder_id, result.toolings);
|
|
1743
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);
|
|
1744
2034
|
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1745
|
-
console.log(
|
|
2035
|
+
console.log(chalk14.green("Synced."));
|
|
1746
2036
|
}
|
|
1747
2037
|
}
|
|
1748
2038
|
var init_sync = __esm({
|
|
@@ -1756,16 +2046,16 @@ var init_sync = __esm({
|
|
|
1756
2046
|
});
|
|
1757
2047
|
|
|
1758
2048
|
// dist/mcp/read-configs.js
|
|
1759
|
-
import { readFile as
|
|
1760
|
-
import { join as
|
|
1761
|
-
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";
|
|
1762
2052
|
import { existsSync as existsSync11 } from "node:fs";
|
|
1763
2053
|
import { readdir as readdir3 } from "node:fs/promises";
|
|
1764
2054
|
async function readJsonSafe(path) {
|
|
1765
2055
|
try {
|
|
1766
2056
|
if (!existsSync11(path))
|
|
1767
2057
|
return null;
|
|
1768
|
-
const raw = await
|
|
2058
|
+
const raw = await readFile10(path, "utf-8");
|
|
1769
2059
|
return JSON.parse(raw);
|
|
1770
2060
|
} catch {
|
|
1771
2061
|
return null;
|
|
@@ -1825,52 +2115,52 @@ function parseFlatConfig(data, source) {
|
|
|
1825
2115
|
return entries;
|
|
1826
2116
|
}
|
|
1827
2117
|
async function readAllMcpConfigs() {
|
|
1828
|
-
const home =
|
|
2118
|
+
const home = homedir8();
|
|
1829
2119
|
const entries = [];
|
|
1830
|
-
const userConfig = await readJsonSafe(
|
|
2120
|
+
const userConfig = await readJsonSafe(join14(home, ".claude.json"));
|
|
1831
2121
|
entries.push(...parseServers(userConfig, "global"));
|
|
1832
|
-
const globalMcp = await readJsonSafe(
|
|
2122
|
+
const globalMcp = await readJsonSafe(join14(home, ".claude", "mcp.json"));
|
|
1833
2123
|
entries.push(...parseServers(globalMcp, "global"));
|
|
1834
|
-
const cwdMcp = await readJsonSafe(
|
|
2124
|
+
const cwdMcp = await readJsonSafe(join14(process.cwd(), ".mcp.json"));
|
|
1835
2125
|
entries.push(...parseServers(cwdMcp, "project"));
|
|
1836
|
-
const pluginsBase =
|
|
2126
|
+
const pluginsBase = join14(home, ".claude", "plugins", "marketplaces");
|
|
1837
2127
|
if (existsSync11(pluginsBase)) {
|
|
1838
2128
|
try {
|
|
1839
2129
|
const marketplaces = await readdir3(pluginsBase, { withFileTypes: true });
|
|
1840
2130
|
for (const mp of marketplaces) {
|
|
1841
2131
|
if (!mp.isDirectory())
|
|
1842
2132
|
continue;
|
|
1843
|
-
const extDir =
|
|
2133
|
+
const extDir = join14(pluginsBase, mp.name, "external_plugins");
|
|
1844
2134
|
if (!existsSync11(extDir))
|
|
1845
2135
|
continue;
|
|
1846
2136
|
const plugins = await readdir3(extDir, { withFileTypes: true });
|
|
1847
2137
|
for (const plugin of plugins) {
|
|
1848
2138
|
if (!plugin.isDirectory())
|
|
1849
2139
|
continue;
|
|
1850
|
-
const pluginMcp = await readJsonSafe(
|
|
2140
|
+
const pluginMcp = await readJsonSafe(join14(extDir, plugin.name, ".mcp.json"));
|
|
1851
2141
|
entries.push(...parseServers(pluginMcp, "plugin"));
|
|
1852
2142
|
}
|
|
1853
2143
|
}
|
|
1854
2144
|
} catch {
|
|
1855
2145
|
}
|
|
1856
2146
|
}
|
|
1857
|
-
const cacheBase =
|
|
2147
|
+
const cacheBase = join14(home, ".claude", "plugins", "cache");
|
|
1858
2148
|
if (existsSync11(cacheBase)) {
|
|
1859
2149
|
try {
|
|
1860
2150
|
const registries = await readdir3(cacheBase, { withFileTypes: true });
|
|
1861
2151
|
for (const reg of registries) {
|
|
1862
2152
|
if (!reg.isDirectory())
|
|
1863
2153
|
continue;
|
|
1864
|
-
const regDir =
|
|
2154
|
+
const regDir = join14(cacheBase, reg.name);
|
|
1865
2155
|
const plugins = await readdir3(regDir, { withFileTypes: true });
|
|
1866
2156
|
for (const plugin of plugins) {
|
|
1867
2157
|
if (!plugin.isDirectory())
|
|
1868
2158
|
continue;
|
|
1869
|
-
const versionDirs = await readdir3(
|
|
2159
|
+
const versionDirs = await readdir3(join14(regDir, plugin.name), { withFileTypes: true });
|
|
1870
2160
|
for (const ver of versionDirs) {
|
|
1871
2161
|
if (!ver.isDirectory())
|
|
1872
2162
|
continue;
|
|
1873
|
-
const mcpPath =
|
|
2163
|
+
const mcpPath = join14(regDir, plugin.name, ver.name, ".mcp.json");
|
|
1874
2164
|
const flatData = await readJsonSafe(mcpPath);
|
|
1875
2165
|
if (flatData) {
|
|
1876
2166
|
entries.push(...parseFlatConfig(flatData, "plugin"));
|
|
@@ -1934,23 +2224,23 @@ function parseEtime(etime) {
|
|
|
1934
2224
|
}
|
|
1935
2225
|
return days * 86400 + (parts[0] ?? 0);
|
|
1936
2226
|
}
|
|
1937
|
-
function findProcessesForConfig(
|
|
1938
|
-
if (
|
|
2227
|
+
function findProcessesForConfig(config2, processes) {
|
|
2228
|
+
if (config2.type === "http")
|
|
1939
2229
|
return [];
|
|
1940
|
-
const packageName =
|
|
2230
|
+
const packageName = config2.args ? extractPackageName(config2.args) : null;
|
|
1941
2231
|
const matches = [];
|
|
1942
2232
|
const searchTerms = [];
|
|
1943
2233
|
if (packageName)
|
|
1944
2234
|
searchTerms.push(packageName);
|
|
1945
|
-
if (
|
|
1946
|
-
searchTerms.push(
|
|
2235
|
+
if (config2.command === "node" && config2.args?.[0]) {
|
|
2236
|
+
searchTerms.push(config2.args[0]);
|
|
1947
2237
|
}
|
|
1948
|
-
if (
|
|
1949
|
-
searchTerms.push(`mcp-server-${
|
|
1950
|
-
searchTerms.push(`${
|
|
2238
|
+
if (config2.name) {
|
|
2239
|
+
searchTerms.push(`mcp-server-${config2.name}`);
|
|
2240
|
+
searchTerms.push(`${config2.name}-mcp`);
|
|
1951
2241
|
}
|
|
1952
|
-
if ((
|
|
1953
|
-
for (const arg of
|
|
2242
|
+
if ((config2.command === "uvx" || config2.command === "pipx") && config2.args) {
|
|
2243
|
+
for (const arg of config2.args) {
|
|
1954
2244
|
if (!arg.startsWith("-") && arg !== "run")
|
|
1955
2245
|
searchTerms.push(arg);
|
|
1956
2246
|
}
|
|
@@ -2000,7 +2290,7 @@ var mcp_watch_exports = {};
|
|
|
2000
2290
|
__export(mcp_watch_exports, {
|
|
2001
2291
|
mcpWatchCommand: () => mcpWatchCommand
|
|
2002
2292
|
});
|
|
2003
|
-
import
|
|
2293
|
+
import chalk20 from "chalk";
|
|
2004
2294
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
2005
2295
|
import { createHash as createHash2 } from "node:crypto";
|
|
2006
2296
|
function detectTty() {
|
|
@@ -2014,10 +2304,10 @@ function detectTty() {
|
|
|
2014
2304
|
return null;
|
|
2015
2305
|
}
|
|
2016
2306
|
}
|
|
2017
|
-
function checkEnvVars(
|
|
2018
|
-
const required =
|
|
2307
|
+
function checkEnvVars(config2) {
|
|
2308
|
+
const required = config2.env ? Object.keys(config2.env) : [];
|
|
2019
2309
|
const missing = required.filter((key) => {
|
|
2020
|
-
const configValue =
|
|
2310
|
+
const configValue = config2.env?.[key];
|
|
2021
2311
|
const hasConfigValue = configValue && !configValue.startsWith("${");
|
|
2022
2312
|
return !hasConfigValue && !process.env[key];
|
|
2023
2313
|
});
|
|
@@ -2052,20 +2342,20 @@ async function checkChromeCdp() {
|
|
|
2052
2342
|
function buildRows(configs, httpResults, cdpStatus) {
|
|
2053
2343
|
const processes = getProcessTable();
|
|
2054
2344
|
const rows = [];
|
|
2055
|
-
for (const
|
|
2056
|
-
const { required, missing } = checkEnvVars(
|
|
2057
|
-
const packageName =
|
|
2058
|
-
if (
|
|
2059
|
-
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";
|
|
2060
2350
|
const status = reachability === "reachable" ? "running" : "stopped";
|
|
2061
2351
|
const detail = reachability === "reachable" ? "HTTP \u2014 remote service reachable" : reachability === "unreachable" ? "HTTP \u2014 remote service unreachable" : "HTTP \u2014 could not verify";
|
|
2062
2352
|
rows.push({
|
|
2063
|
-
server_name:
|
|
2064
|
-
config_source:
|
|
2353
|
+
server_name: config2.name,
|
|
2354
|
+
config_source: config2.source,
|
|
2065
2355
|
server_type: "http",
|
|
2066
2356
|
command: null,
|
|
2067
2357
|
package_name: null,
|
|
2068
|
-
http_url:
|
|
2358
|
+
http_url: config2.url ?? null,
|
|
2069
2359
|
status,
|
|
2070
2360
|
pid: null,
|
|
2071
2361
|
session_tty: null,
|
|
@@ -2079,10 +2369,10 @@ function buildRows(configs, httpResults, cdpStatus) {
|
|
|
2079
2369
|
}
|
|
2080
2370
|
if (missing.length > 0) {
|
|
2081
2371
|
rows.push({
|
|
2082
|
-
server_name:
|
|
2083
|
-
config_source:
|
|
2372
|
+
server_name: config2.name,
|
|
2373
|
+
config_source: config2.source,
|
|
2084
2374
|
server_type: "stdio",
|
|
2085
|
-
command:
|
|
2375
|
+
command: config2.command ?? null,
|
|
2086
2376
|
package_name: packageName,
|
|
2087
2377
|
http_url: null,
|
|
2088
2378
|
status: "error",
|
|
@@ -2096,15 +2386,15 @@ function buildRows(configs, httpResults, cdpStatus) {
|
|
|
2096
2386
|
});
|
|
2097
2387
|
continue;
|
|
2098
2388
|
}
|
|
2099
|
-
const isChromeDevtools =
|
|
2389
|
+
const isChromeDevtools = config2.name === "chrome-devtools" || (config2.args ?? []).some((a) => a.includes("chrome-devtools"));
|
|
2100
2390
|
const cdpDetail = isChromeDevtools && cdpStatus ? cdpStatus.status === "reachable" ? "Chrome CDP reachable" : "Chrome not reachable on port 9222" : null;
|
|
2101
|
-
const matches = findProcessesForConfig(
|
|
2391
|
+
const matches = findProcessesForConfig(config2, processes);
|
|
2102
2392
|
if (matches.length === 0) {
|
|
2103
2393
|
rows.push({
|
|
2104
|
-
server_name:
|
|
2105
|
-
config_source:
|
|
2394
|
+
server_name: config2.name,
|
|
2395
|
+
config_source: config2.source,
|
|
2106
2396
|
server_type: "stdio",
|
|
2107
|
-
command:
|
|
2397
|
+
command: config2.command ?? null,
|
|
2108
2398
|
package_name: packageName,
|
|
2109
2399
|
http_url: null,
|
|
2110
2400
|
status: "stopped",
|
|
@@ -2127,10 +2417,10 @@ function buildRows(configs, httpResults, cdpStatus) {
|
|
|
2127
2417
|
}
|
|
2128
2418
|
for (const match of byTty.values()) {
|
|
2129
2419
|
rows.push({
|
|
2130
|
-
server_name:
|
|
2131
|
-
config_source:
|
|
2420
|
+
server_name: config2.name,
|
|
2421
|
+
config_source: config2.source,
|
|
2132
2422
|
server_type: "stdio",
|
|
2133
|
-
command:
|
|
2423
|
+
command: config2.command ?? null,
|
|
2134
2424
|
package_name: packageName,
|
|
2135
2425
|
http_url: null,
|
|
2136
2426
|
status: "running",
|
|
@@ -2149,20 +2439,20 @@ function buildRows(configs, httpResults, cdpStatus) {
|
|
|
2149
2439
|
}
|
|
2150
2440
|
function printTable(rows, deviceName, watcherPid, cdpResult) {
|
|
2151
2441
|
process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
|
|
2152
|
-
console.log(
|
|
2153
|
-
MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) +
|
|
2154
|
-
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
|
|
2155
2445
|
`));
|
|
2156
2446
|
if (cdpResult) {
|
|
2157
2447
|
if (cdpResult.status === "reachable") {
|
|
2158
2448
|
const browserInfo = cdpResult.browser ? ` (${cdpResult.browser})` : "";
|
|
2159
|
-
console.log(
|
|
2160
|
-
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"));
|
|
2161
2451
|
} else {
|
|
2162
|
-
console.log(
|
|
2163
|
-
console.log(
|
|
2164
|
-
console.log(
|
|
2165
|
-
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"));
|
|
2166
2456
|
}
|
|
2167
2457
|
console.log("");
|
|
2168
2458
|
}
|
|
@@ -2183,41 +2473,41 @@ function printTable(rows, deviceName, watcherPid, cdpResult) {
|
|
|
2183
2473
|
sessionNum++;
|
|
2184
2474
|
const totalMem = servers.reduce((sum, s) => sum + (s.memory_mb ?? 0), 0);
|
|
2185
2475
|
const label = byTty.size === 1 ? "Claude Code session" : `Claude Code session ${sessionNum}`;
|
|
2186
|
-
console.log(
|
|
2476
|
+
console.log(chalk20.green(` ${label}`) + chalk20.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB \xB7 terminal ${tty}`));
|
|
2187
2477
|
for (const s of servers) {
|
|
2188
2478
|
const uptime = formatUptime(s.uptime_seconds ?? 0);
|
|
2189
|
-
const source =
|
|
2190
|
-
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}`);
|
|
2191
2481
|
}
|
|
2192
2482
|
console.log("");
|
|
2193
2483
|
}
|
|
2194
2484
|
if (byTty.size > 1) {
|
|
2195
|
-
console.log(
|
|
2485
|
+
console.log(chalk20.dim(" Each open Claude Code window runs its own set of MCP servers.\n"));
|
|
2196
2486
|
}
|
|
2197
2487
|
}
|
|
2198
2488
|
if (runningHttp.length > 0) {
|
|
2199
|
-
console.log(
|
|
2489
|
+
console.log(chalk20.blue(` Remote Services (${runningHttp.length})`) + chalk20.dim(" \u2014 HTTP endpoints reachable"));
|
|
2200
2490
|
for (const s of runningHttp) {
|
|
2201
|
-
console.log(` ${
|
|
2491
|
+
console.log(` ${chalk20.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk20.dim((s.http_url ?? "").padEnd(30))}`);
|
|
2202
2492
|
}
|
|
2203
2493
|
console.log("");
|
|
2204
2494
|
}
|
|
2205
2495
|
if (stopped.length > 0 || errored.length > 0) {
|
|
2206
2496
|
const notRunning = [...stopped, ...errored];
|
|
2207
|
-
console.log(
|
|
2497
|
+
console.log(chalk20.yellow(` Not Running (${notRunning.length})`) + chalk20.dim(" \u2014 configured but no process detected"));
|
|
2208
2498
|
for (const s of notRunning) {
|
|
2209
|
-
const icon = s.status === "error" ?
|
|
2210
|
-
const source =
|
|
2211
|
-
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}`) : "";
|
|
2212
2502
|
console.log(` ${icon} ${s.server_name.padEnd(20)} ${source}${detail}`);
|
|
2213
2503
|
}
|
|
2214
2504
|
console.log("");
|
|
2215
2505
|
}
|
|
2216
2506
|
if (rows.length === 0) {
|
|
2217
|
-
console.log(
|
|
2218
|
-
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"));
|
|
2219
2509
|
}
|
|
2220
|
-
console.log(
|
|
2510
|
+
console.log(chalk20.bgYellow.black.bold(" \u26A0 DO NOT CLOSE THIS WINDOW \u2014 it feeds live data to the dashboard \u26A0 "));
|
|
2221
2511
|
console.log("");
|
|
2222
2512
|
}
|
|
2223
2513
|
function formatUptime(seconds) {
|
|
@@ -2240,7 +2530,7 @@ async function mcpWatchCommand() {
|
|
|
2240
2530
|
const { data: existingWatchers } = await supabase.from("mcp_watchers").select("pid, tty, cli_version, started_at").eq("device_id", deviceId);
|
|
2241
2531
|
if (existingWatchers && existingWatchers.length > 0) {
|
|
2242
2532
|
console.log("");
|
|
2243
|
-
console.log(
|
|
2533
|
+
console.log(chalk20.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
|
|
2244
2534
|
for (const w of existingWatchers) {
|
|
2245
2535
|
try {
|
|
2246
2536
|
process.kill(w.pid, "SIGTERM");
|
|
@@ -2249,15 +2539,15 @@ async function mcpWatchCommand() {
|
|
|
2249
2539
|
}
|
|
2250
2540
|
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId);
|
|
2251
2541
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
2252
|
-
console.log(
|
|
2542
|
+
console.log(chalk20.dim(" Previous watcher stopped.\n"));
|
|
2253
2543
|
}
|
|
2254
2544
|
process.stdout.write(`\x1B]0;MCP mon\x07`);
|
|
2255
|
-
console.log(
|
|
2545
|
+
console.log(chalk20.blue(`Starting MCP monitor for ${deviceName}...`));
|
|
2256
2546
|
console.log("");
|
|
2257
|
-
console.log(
|
|
2258
|
-
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}`));
|
|
2259
2549
|
console.log("");
|
|
2260
|
-
console.log(
|
|
2550
|
+
console.log(chalk20.yellow(" Please note, closing this window will stop ALL watch reports."));
|
|
2261
2551
|
console.log("");
|
|
2262
2552
|
await supabase.from("mcp_watchers").upsert({
|
|
2263
2553
|
device_id: deviceId,
|
|
@@ -2283,7 +2573,7 @@ async function mcpWatchCommand() {
|
|
|
2283
2573
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2284
2574
|
const { error: deleteError } = await supabase.from("mcp_server_status").delete().eq("device_id", deviceId);
|
|
2285
2575
|
if (deleteError) {
|
|
2286
|
-
console.error(
|
|
2576
|
+
console.error(chalk20.red(` [debug] Failed to delete old status: ${deleteError.message}`));
|
|
2287
2577
|
}
|
|
2288
2578
|
if (rows.length > 0) {
|
|
2289
2579
|
const { error: insertError } = await supabase.from("mcp_server_status").insert(rows.map((row) => ({
|
|
@@ -2292,7 +2582,7 @@ async function mcpWatchCommand() {
|
|
|
2292
2582
|
checked_at: now
|
|
2293
2583
|
})));
|
|
2294
2584
|
if (insertError) {
|
|
2295
|
-
console.error(
|
|
2585
|
+
console.error(chalk20.red(` [debug] Failed to write MCP status: ${insertError.message}`));
|
|
2296
2586
|
}
|
|
2297
2587
|
}
|
|
2298
2588
|
await supabase.from("mcp_watchers").update({ last_heartbeat: now }).eq("device_id", deviceId).eq("pid", myPid);
|
|
@@ -2330,7 +2620,7 @@ async function mcpWatchCommand() {
|
|
|
2330
2620
|
clearInterval(interval);
|
|
2331
2621
|
clearInterval(envInterval);
|
|
2332
2622
|
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).eq("pid", myPid);
|
|
2333
|
-
console.log(
|
|
2623
|
+
console.log(chalk20.dim("\nMCP monitor stopped."));
|
|
2334
2624
|
process.exit(0);
|
|
2335
2625
|
};
|
|
2336
2626
|
process.on("SIGINT", () => {
|
|
@@ -2594,71 +2884,71 @@ init_map();
|
|
|
2594
2884
|
|
|
2595
2885
|
// dist/commands/simulate.js
|
|
2596
2886
|
init_dist();
|
|
2597
|
-
import { join as
|
|
2887
|
+
import { join as join11 } from "node:path";
|
|
2598
2888
|
import { existsSync as existsSync8 } from "node:fs";
|
|
2599
|
-
import { homedir as
|
|
2600
|
-
import
|
|
2889
|
+
import { homedir as homedir7 } from "node:os";
|
|
2890
|
+
import chalk12 from "chalk";
|
|
2601
2891
|
async function simulateCommand(prompt) {
|
|
2602
2892
|
const projectRoot = process.cwd();
|
|
2603
|
-
console.log(
|
|
2893
|
+
console.log(chalk12.blue(`Simulating prompt: "${prompt}"
|
|
2604
2894
|
`));
|
|
2605
|
-
console.log(
|
|
2606
|
-
const globalClaude =
|
|
2895
|
+
console.log(chalk12.dim("Files Claude would load:\n"));
|
|
2896
|
+
const globalClaude = join11(homedir7(), ".claude", "CLAUDE.md");
|
|
2607
2897
|
if (existsSync8(globalClaude)) {
|
|
2608
|
-
console.log(
|
|
2898
|
+
console.log(chalk12.green(" \u2713 ~/.claude/CLAUDE.md (global)"));
|
|
2609
2899
|
}
|
|
2610
2900
|
for (const rootFile of ROOT_FILES) {
|
|
2611
|
-
const fullPath =
|
|
2901
|
+
const fullPath = join11(projectRoot, rootFile);
|
|
2612
2902
|
if (existsSync8(fullPath)) {
|
|
2613
|
-
console.log(
|
|
2903
|
+
console.log(chalk12.green(` \u2713 ${rootFile} (project root)`));
|
|
2614
2904
|
}
|
|
2615
2905
|
}
|
|
2616
2906
|
const settingsFiles = [
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
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")
|
|
2621
2911
|
];
|
|
2622
2912
|
for (const sf of settingsFiles) {
|
|
2623
2913
|
if (existsSync8(sf)) {
|
|
2624
|
-
const display = sf.startsWith(
|
|
2625
|
-
console.log(
|
|
2914
|
+
const display = sf.startsWith(homedir7()) ? sf.replace(homedir7(), "~") : sf.replace(projectRoot + "/", "");
|
|
2915
|
+
console.log(chalk12.green(` \u2713 ${display} (settings)`));
|
|
2626
2916
|
}
|
|
2627
2917
|
}
|
|
2628
2918
|
const words = prompt.split(/\s+/);
|
|
2629
2919
|
for (const word of words) {
|
|
2630
2920
|
const cleaned = word.replace(/['"]/g, "");
|
|
2631
|
-
const candidatePath =
|
|
2921
|
+
const candidatePath = join11(projectRoot, cleaned);
|
|
2632
2922
|
if (existsSync8(candidatePath) && cleaned.includes("/")) {
|
|
2633
|
-
console.log(
|
|
2923
|
+
console.log(chalk12.cyan(` \u2192 ${cleaned} (referenced in prompt)`));
|
|
2634
2924
|
}
|
|
2635
2925
|
}
|
|
2636
|
-
console.log(
|
|
2926
|
+
console.log(chalk12.dim("\nNote: This is an approximation. Actual file loading depends on Claude Code internals."));
|
|
2637
2927
|
}
|
|
2638
2928
|
|
|
2639
2929
|
// dist/commands/print.js
|
|
2640
|
-
import { join as
|
|
2641
|
-
import { readFile as
|
|
2930
|
+
import { join as join12 } from "node:path";
|
|
2931
|
+
import { readFile as readFile8, writeFile as writeFile3 } from "node:fs/promises";
|
|
2642
2932
|
import { existsSync as existsSync9 } from "node:fs";
|
|
2643
|
-
import
|
|
2933
|
+
import chalk13 from "chalk";
|
|
2644
2934
|
async function printCommand(title) {
|
|
2645
2935
|
const projectRoot = process.cwd();
|
|
2646
|
-
const scanDataPath =
|
|
2936
|
+
const scanDataPath = join12(projectRoot, "output", "index.html");
|
|
2647
2937
|
if (!existsSync9(scanDataPath)) {
|
|
2648
|
-
console.error(
|
|
2938
|
+
console.error(chalk13.red("No scan data found. Run: md4ai scan"));
|
|
2649
2939
|
process.exit(1);
|
|
2650
2940
|
}
|
|
2651
|
-
const html = await
|
|
2941
|
+
const html = await readFile8(scanDataPath, "utf-8");
|
|
2652
2942
|
const match = html.match(/<script type="application\/json" id="scan-data">([\s\S]*?)<\/script>/);
|
|
2653
2943
|
if (!match) {
|
|
2654
|
-
console.error(
|
|
2944
|
+
console.error(chalk13.red("Could not extract scan data from output/index.html"));
|
|
2655
2945
|
process.exit(1);
|
|
2656
2946
|
}
|
|
2657
2947
|
const result = JSON.parse(match[1]);
|
|
2658
2948
|
const printHtml = generatePrintHtml(result, title);
|
|
2659
|
-
const outputPath =
|
|
2949
|
+
const outputPath = join12(projectRoot, "output", `print-${Date.now()}.html`);
|
|
2660
2950
|
await writeFile3(outputPath, printHtml, "utf-8");
|
|
2661
|
-
console.log(
|
|
2951
|
+
console.log(chalk13.green(`Print-ready wall sheet: ${outputPath}`));
|
|
2662
2952
|
}
|
|
2663
2953
|
function escapeHtml2(text) {
|
|
2664
2954
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -2724,7 +3014,7 @@ init_scanner();
|
|
|
2724
3014
|
init_push_toolings();
|
|
2725
3015
|
init_device_utils();
|
|
2726
3016
|
import { resolve as resolve4 } from "node:path";
|
|
2727
|
-
import
|
|
3017
|
+
import chalk15 from "chalk";
|
|
2728
3018
|
async function linkCommand(projectId) {
|
|
2729
3019
|
const { supabase, userId } = await getAuthenticatedClient();
|
|
2730
3020
|
const cwd = resolve4(process.cwd());
|
|
@@ -2732,11 +3022,11 @@ async function linkCommand(projectId) {
|
|
|
2732
3022
|
const osType = detectOs2();
|
|
2733
3023
|
const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
|
|
2734
3024
|
if (folderErr || !folder) {
|
|
2735
|
-
console.error(
|
|
2736
|
-
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."));
|
|
2737
3027
|
process.exit(1);
|
|
2738
3028
|
}
|
|
2739
|
-
console.log(
|
|
3029
|
+
console.log(chalk15.blue(`
|
|
2740
3030
|
Linking "${folder.name}" to this device...
|
|
2741
3031
|
`));
|
|
2742
3032
|
console.log(` Project: ${folder.name}`);
|
|
@@ -2760,12 +3050,12 @@ Linking "${folder.name}" to this device...
|
|
|
2760
3050
|
path: cwd
|
|
2761
3051
|
});
|
|
2762
3052
|
if (pathErr) {
|
|
2763
|
-
console.error(
|
|
3053
|
+
console.error(chalk15.red(`Failed to link: ${pathErr.message}`));
|
|
2764
3054
|
process.exit(1);
|
|
2765
3055
|
}
|
|
2766
3056
|
}
|
|
2767
|
-
console.log(
|
|
2768
|
-
console.log(
|
|
3057
|
+
console.log(chalk15.green("\nLinked successfully."));
|
|
3058
|
+
console.log(chalk15.blue("\nRunning initial scan...\n"));
|
|
2769
3059
|
const result = await scanProject(cwd);
|
|
2770
3060
|
console.log(` Files: ${result.graph.nodes.length}`);
|
|
2771
3061
|
console.log(` References:${result.graph.edges.length}`);
|
|
@@ -2783,7 +3073,7 @@ Linking "${folder.name}" to this device...
|
|
|
2783
3073
|
data_hash: result.dataHash
|
|
2784
3074
|
}).eq("id", folder.id);
|
|
2785
3075
|
if (scanErr) {
|
|
2786
|
-
console.error(
|
|
3076
|
+
console.error(chalk15.yellow(`Scan upload warning: ${scanErr.message}`));
|
|
2787
3077
|
}
|
|
2788
3078
|
await pushToolings(supabase, folder.id, result.toolings);
|
|
2789
3079
|
const configFiles = await readClaudeConfigFiles(cwd);
|
|
@@ -2797,7 +3087,7 @@ Linking "${folder.name}" to this device...
|
|
|
2797
3087
|
last_modified: file.lastModified
|
|
2798
3088
|
}, { onConflict: "folder_id,file_path" });
|
|
2799
3089
|
}
|
|
2800
|
-
console.log(
|
|
3090
|
+
console.log(chalk15.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
2801
3091
|
}
|
|
2802
3092
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
|
|
2803
3093
|
await saveState({
|
|
@@ -2806,34 +3096,34 @@ Linking "${folder.name}" to this device...
|
|
|
2806
3096
|
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2807
3097
|
});
|
|
2808
3098
|
const projectUrl = `https://www.md4ai.com/project/${folder.id}`;
|
|
2809
|
-
console.log(
|
|
2810
|
-
console.log(
|
|
3099
|
+
console.log(chalk15.green("\nDone! Project linked and scanned."));
|
|
3100
|
+
console.log(chalk15.cyan(`
|
|
2811
3101
|
${projectUrl}
|
|
2812
3102
|
`));
|
|
2813
|
-
console.log(
|
|
3103
|
+
console.log(chalk15.grey('Run "md4ai scan" to rescan at any time.'));
|
|
2814
3104
|
}
|
|
2815
3105
|
|
|
2816
3106
|
// dist/commands/import-bundle.js
|
|
2817
|
-
import { readFile as
|
|
2818
|
-
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";
|
|
2819
3109
|
import { existsSync as existsSync10 } from "node:fs";
|
|
2820
|
-
import
|
|
3110
|
+
import chalk16 from "chalk";
|
|
2821
3111
|
import { confirm as confirm3, input as input6 } from "@inquirer/prompts";
|
|
2822
3112
|
async function importBundleCommand(zipPath) {
|
|
2823
3113
|
if (!existsSync10(zipPath)) {
|
|
2824
|
-
console.error(
|
|
3114
|
+
console.error(chalk16.red(`File not found: ${zipPath}`));
|
|
2825
3115
|
process.exit(1);
|
|
2826
3116
|
}
|
|
2827
3117
|
const JSZip = (await import("jszip")).default;
|
|
2828
|
-
const zipData = await
|
|
3118
|
+
const zipData = await readFile9(zipPath);
|
|
2829
3119
|
const zip = await JSZip.loadAsync(zipData);
|
|
2830
3120
|
const manifestFile = zip.file("manifest.json");
|
|
2831
3121
|
if (!manifestFile) {
|
|
2832
|
-
console.error(
|
|
3122
|
+
console.error(chalk16.red("Invalid bundle: missing manifest.json"));
|
|
2833
3123
|
process.exit(1);
|
|
2834
3124
|
}
|
|
2835
3125
|
const manifest = JSON.parse(await manifestFile.async("string"));
|
|
2836
|
-
console.log(
|
|
3126
|
+
console.log(chalk16.blue(`
|
|
2837
3127
|
Bundle: ${manifest.folderName}`));
|
|
2838
3128
|
console.log(` Exported by: ${manifest.ownerEmail}`);
|
|
2839
3129
|
console.log(` Exported at: ${manifest.exportedAt}`);
|
|
@@ -2847,10 +3137,10 @@ Bundle: ${manifest.folderName}`));
|
|
|
2847
3137
|
}
|
|
2848
3138
|
}
|
|
2849
3139
|
if (files.length === 0) {
|
|
2850
|
-
console.log(
|
|
3140
|
+
console.log(chalk16.yellow("No Claude config files found in bundle."));
|
|
2851
3141
|
return;
|
|
2852
3142
|
}
|
|
2853
|
-
console.log(
|
|
3143
|
+
console.log(chalk16.blue(`
|
|
2854
3144
|
Files to extract:`));
|
|
2855
3145
|
for (const f of files) {
|
|
2856
3146
|
console.log(` ${f.filePath}`);
|
|
@@ -2863,34 +3153,34 @@ Files to extract:`));
|
|
|
2863
3153
|
message: `Extract ${files.length} file(s) to ${targetDir}?`
|
|
2864
3154
|
});
|
|
2865
3155
|
if (!proceed) {
|
|
2866
|
-
console.log(
|
|
3156
|
+
console.log(chalk16.yellow("Cancelled."));
|
|
2867
3157
|
return;
|
|
2868
3158
|
}
|
|
2869
3159
|
for (const file of files) {
|
|
2870
|
-
const fullPath =
|
|
2871
|
-
const dir =
|
|
3160
|
+
const fullPath = join13(targetDir, file.filePath);
|
|
3161
|
+
const dir = dirname3(fullPath);
|
|
2872
3162
|
if (!existsSync10(dir)) {
|
|
2873
3163
|
await mkdir3(dir, { recursive: true });
|
|
2874
3164
|
}
|
|
2875
3165
|
await writeFile4(fullPath, file.content, "utf-8");
|
|
2876
|
-
console.log(
|
|
3166
|
+
console.log(chalk16.green(` \u2713 ${file.filePath}`));
|
|
2877
3167
|
}
|
|
2878
|
-
console.log(
|
|
3168
|
+
console.log(chalk16.green(`
|
|
2879
3169
|
Done! ${files.length} file(s) extracted to ${targetDir}`));
|
|
2880
3170
|
}
|
|
2881
3171
|
|
|
2882
3172
|
// dist/commands/admin-update-tool.js
|
|
2883
3173
|
init_auth();
|
|
2884
|
-
import
|
|
3174
|
+
import chalk17 from "chalk";
|
|
2885
3175
|
var VALID_CATEGORIES = ["framework", "runtime", "cli", "mcp", "package", "database", "other"];
|
|
2886
3176
|
async function adminUpdateToolCommand(options) {
|
|
2887
3177
|
const { supabase } = await getAuthenticatedClient();
|
|
2888
3178
|
if (!options.name) {
|
|
2889
|
-
console.error(
|
|
3179
|
+
console.error(chalk17.red("--name is required."));
|
|
2890
3180
|
process.exit(1);
|
|
2891
3181
|
}
|
|
2892
3182
|
if (options.category && !VALID_CATEGORIES.includes(options.category)) {
|
|
2893
|
-
console.error(
|
|
3183
|
+
console.error(chalk17.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
|
|
2894
3184
|
process.exit(1);
|
|
2895
3185
|
}
|
|
2896
3186
|
const { data: existing } = await supabase.from("tools_registry").select("id, name, display_name, category").eq("name", options.name).maybeSingle();
|
|
@@ -2912,13 +3202,13 @@ async function adminUpdateToolCommand(options) {
|
|
|
2912
3202
|
updates.notes = options.notes;
|
|
2913
3203
|
const { error } = await supabase.from("tools_registry").update(updates).eq("id", existing.id);
|
|
2914
3204
|
if (error) {
|
|
2915
|
-
console.error(
|
|
3205
|
+
console.error(chalk17.red(`Failed to update: ${error.message}`));
|
|
2916
3206
|
process.exit(1);
|
|
2917
3207
|
}
|
|
2918
|
-
console.log(
|
|
3208
|
+
console.log(chalk17.green(`Updated "${existing.display_name}" in the registry.`));
|
|
2919
3209
|
} else {
|
|
2920
3210
|
if (!options.display || !options.category) {
|
|
2921
|
-
console.error(
|
|
3211
|
+
console.error(chalk17.red("New tools require --display and --category."));
|
|
2922
3212
|
process.exit(1);
|
|
2923
3213
|
}
|
|
2924
3214
|
const { error } = await supabase.from("tools_registry").insert({
|
|
@@ -2932,25 +3222,25 @@ async function adminUpdateToolCommand(options) {
|
|
|
2932
3222
|
notes: options.notes ?? null
|
|
2933
3223
|
});
|
|
2934
3224
|
if (error) {
|
|
2935
|
-
console.error(
|
|
3225
|
+
console.error(chalk17.red(`Failed to create: ${error.message}`));
|
|
2936
3226
|
process.exit(1);
|
|
2937
3227
|
}
|
|
2938
|
-
console.log(
|
|
3228
|
+
console.log(chalk17.green(`Added "${options.display}" to the registry.`));
|
|
2939
3229
|
}
|
|
2940
3230
|
}
|
|
2941
3231
|
|
|
2942
3232
|
// dist/commands/admin-list-tools.js
|
|
2943
3233
|
init_auth();
|
|
2944
|
-
import
|
|
3234
|
+
import chalk18 from "chalk";
|
|
2945
3235
|
async function adminListToolsCommand() {
|
|
2946
3236
|
const { supabase } = await getAuthenticatedClient();
|
|
2947
3237
|
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("category").order("display_name");
|
|
2948
3238
|
if (error) {
|
|
2949
|
-
console.error(
|
|
3239
|
+
console.error(chalk18.red(`Failed to fetch tools: ${error.message}`));
|
|
2950
3240
|
process.exit(1);
|
|
2951
3241
|
}
|
|
2952
3242
|
if (!tools?.length) {
|
|
2953
|
-
console.log(
|
|
3243
|
+
console.log(chalk18.yellow("No tools in the registry."));
|
|
2954
3244
|
return;
|
|
2955
3245
|
}
|
|
2956
3246
|
const nameW = Math.max(16, ...tools.map((t) => t.display_name.length));
|
|
@@ -2964,7 +3254,7 @@ async function adminListToolsCommand() {
|
|
|
2964
3254
|
"Beta".padEnd(betaW),
|
|
2965
3255
|
"Last Checked"
|
|
2966
3256
|
].join(" ");
|
|
2967
|
-
console.log(
|
|
3257
|
+
console.log(chalk18.bold(header));
|
|
2968
3258
|
console.log("\u2500".repeat(header.length));
|
|
2969
3259
|
for (const tool of tools) {
|
|
2970
3260
|
const lastChecked = tool.updated_at ? formatRelative(new Date(tool.updated_at)) : "\u2014";
|
|
@@ -2977,7 +3267,7 @@ async function adminListToolsCommand() {
|
|
|
2977
3267
|
].join(" ");
|
|
2978
3268
|
console.log(row);
|
|
2979
3269
|
}
|
|
2980
|
-
console.log(
|
|
3270
|
+
console.log(chalk18.grey(`
|
|
2981
3271
|
${tools.length} tool(s) in registry.`));
|
|
2982
3272
|
}
|
|
2983
3273
|
function formatRelative(date) {
|
|
@@ -2995,7 +3285,7 @@ function formatRelative(date) {
|
|
|
2995
3285
|
|
|
2996
3286
|
// dist/commands/admin-fetch-versions.js
|
|
2997
3287
|
init_auth();
|
|
2998
|
-
import
|
|
3288
|
+
import chalk19 from "chalk";
|
|
2999
3289
|
|
|
3000
3290
|
// dist/commands/version-sources.js
|
|
3001
3291
|
var VERSION_SOURCES = {
|
|
@@ -3024,11 +3314,11 @@ async function adminFetchVersionsCommand() {
|
|
|
3024
3314
|
const { supabase } = await getAuthenticatedClient();
|
|
3025
3315
|
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("display_name");
|
|
3026
3316
|
if (error) {
|
|
3027
|
-
console.error(
|
|
3317
|
+
console.error(chalk19.red(`Failed to fetch tools: ${error.message}`));
|
|
3028
3318
|
process.exit(1);
|
|
3029
3319
|
}
|
|
3030
3320
|
if (!tools?.length) {
|
|
3031
|
-
console.log(
|
|
3321
|
+
console.log(chalk19.yellow("No tools in the registry."));
|
|
3032
3322
|
}
|
|
3033
3323
|
const { data: allProjectToolings } = await supabase.from("project_toolings").select("tool_name, detection_source").is("tool_id", null);
|
|
3034
3324
|
const registeredNames = new Set((tools ?? []).map((t) => t.name));
|
|
@@ -3039,7 +3329,7 @@ async function adminFetchVersionsCommand() {
|
|
|
3039
3329
|
}
|
|
3040
3330
|
}
|
|
3041
3331
|
if (unregisteredNames.size > 0) {
|
|
3042
|
-
console.log(
|
|
3332
|
+
console.log(chalk19.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
|
|
3043
3333
|
`));
|
|
3044
3334
|
for (const name of unregisteredNames) {
|
|
3045
3335
|
const displayName = name;
|
|
@@ -3057,10 +3347,10 @@ async function adminFetchVersionsCommand() {
|
|
|
3057
3347
|
}
|
|
3058
3348
|
}
|
|
3059
3349
|
if (!tools?.length) {
|
|
3060
|
-
console.log(
|
|
3350
|
+
console.log(chalk19.yellow("No tools to fetch."));
|
|
3061
3351
|
return;
|
|
3062
3352
|
}
|
|
3063
|
-
console.log(
|
|
3353
|
+
console.log(chalk19.bold(`Fetching versions for ${tools.length} tool(s)...
|
|
3064
3354
|
`));
|
|
3065
3355
|
const results = await Promise.all(tools.map(async (tool) => {
|
|
3066
3356
|
const source = VERSION_SOURCES[tool.name] ?? { type: "npm", package: tool.name };
|
|
@@ -3099,10 +3389,10 @@ async function adminFetchVersionsCommand() {
|
|
|
3099
3389
|
"Beta".padEnd(betaW),
|
|
3100
3390
|
"Status".padEnd(statusW)
|
|
3101
3391
|
].join(" ");
|
|
3102
|
-
console.log(
|
|
3392
|
+
console.log(chalk19.bold(header));
|
|
3103
3393
|
console.log("\u2500".repeat(header.length));
|
|
3104
3394
|
for (const result of results) {
|
|
3105
|
-
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;
|
|
3106
3396
|
const row = [
|
|
3107
3397
|
result.displayName.padEnd(nameW),
|
|
3108
3398
|
(result.stable ?? "\u2014").padEnd(stableW),
|
|
@@ -3115,8 +3405,8 @@ async function adminFetchVersionsCommand() {
|
|
|
3115
3405
|
const unchanged = results.filter((r) => r.status === "unchanged").length;
|
|
3116
3406
|
const failed = results.filter((r) => r.status === "failed").length;
|
|
3117
3407
|
const noSource = results.filter((r) => r.status === "no source").length;
|
|
3118
|
-
console.log(
|
|
3119
|
-
${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`));
|
|
3120
3410
|
}
|
|
3121
3411
|
async function fetchVersions(source) {
|
|
3122
3412
|
const controller = new AbortController();
|
|
@@ -3174,10 +3464,10 @@ async function fetchGitHubVersions(repo, signal) {
|
|
|
3174
3464
|
init_mcp_watch();
|
|
3175
3465
|
|
|
3176
3466
|
// dist/commands/init-manifest.js
|
|
3177
|
-
import { resolve as resolve5, join as
|
|
3178
|
-
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";
|
|
3179
3469
|
import { existsSync as existsSync12 } from "node:fs";
|
|
3180
|
-
import
|
|
3470
|
+
import chalk21 from "chalk";
|
|
3181
3471
|
var SECRET_PATTERNS = [
|
|
3182
3472
|
/_KEY$/i,
|
|
3183
3473
|
/_SECRET$/i,
|
|
@@ -3193,7 +3483,7 @@ function isLikelySecret(name) {
|
|
|
3193
3483
|
async function discoverEnvFiles(projectRoot) {
|
|
3194
3484
|
const apps = [];
|
|
3195
3485
|
for (const envName of [".env", ".env.local", ".env.example"]) {
|
|
3196
|
-
const envPath =
|
|
3486
|
+
const envPath = join15(projectRoot, envName);
|
|
3197
3487
|
if (existsSync12(envPath)) {
|
|
3198
3488
|
const vars = await extractVarNames(envPath);
|
|
3199
3489
|
if (vars.length > 0) {
|
|
@@ -3204,11 +3494,11 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
3204
3494
|
}
|
|
3205
3495
|
const subdirs = ["web", "cli", "api", "app", "server", "packages"];
|
|
3206
3496
|
for (const sub of subdirs) {
|
|
3207
|
-
const subDir =
|
|
3497
|
+
const subDir = join15(projectRoot, sub);
|
|
3208
3498
|
if (!existsSync12(subDir))
|
|
3209
3499
|
continue;
|
|
3210
3500
|
for (const envName of [".env.local", ".env", ".env.example"]) {
|
|
3211
|
-
const envPath =
|
|
3501
|
+
const envPath = join15(subDir, envName);
|
|
3212
3502
|
if (existsSync12(envPath)) {
|
|
3213
3503
|
const vars = await extractVarNames(envPath);
|
|
3214
3504
|
if (vars.length > 0) {
|
|
@@ -3225,7 +3515,7 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
3225
3515
|
return apps;
|
|
3226
3516
|
}
|
|
3227
3517
|
async function extractVarNames(envPath) {
|
|
3228
|
-
const content = await
|
|
3518
|
+
const content = await readFile11(envPath, "utf-8");
|
|
3229
3519
|
const vars = [];
|
|
3230
3520
|
for (const line of content.split("\n")) {
|
|
3231
3521
|
const trimmed = line.trim();
|
|
@@ -3270,38 +3560,38 @@ function generateManifest(apps) {
|
|
|
3270
3560
|
}
|
|
3271
3561
|
async function initManifestCommand() {
|
|
3272
3562
|
const projectRoot = resolve5(process.cwd());
|
|
3273
|
-
const manifestPath =
|
|
3563
|
+
const manifestPath = join15(projectRoot, "docs", "reference", "env-manifest.md");
|
|
3274
3564
|
if (existsSync12(manifestPath)) {
|
|
3275
|
-
console.log(
|
|
3276
|
-
console.log(
|
|
3565
|
+
console.log(chalk21.yellow(`Manifest already exists: ${relative4(projectRoot, manifestPath)}`));
|
|
3566
|
+
console.log(chalk21.dim("Edit it directly to make changes."));
|
|
3277
3567
|
return;
|
|
3278
3568
|
}
|
|
3279
|
-
console.log(
|
|
3569
|
+
console.log(chalk21.blue("Scanning for .env files...\n"));
|
|
3280
3570
|
const apps = await discoverEnvFiles(projectRoot);
|
|
3281
3571
|
if (apps.length === 0) {
|
|
3282
|
-
console.log(
|
|
3283
|
-
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`));
|
|
3284
3574
|
return;
|
|
3285
3575
|
}
|
|
3286
3576
|
for (const app of apps) {
|
|
3287
|
-
console.log(` ${
|
|
3577
|
+
console.log(` ${chalk21.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
|
|
3288
3578
|
}
|
|
3289
3579
|
const content = generateManifest(apps);
|
|
3290
|
-
const dir =
|
|
3580
|
+
const dir = dirname4(manifestPath);
|
|
3291
3581
|
if (!existsSync12(dir)) {
|
|
3292
3582
|
await mkdir4(dir, { recursive: true });
|
|
3293
3583
|
}
|
|
3294
3584
|
await writeFile5(manifestPath, content, "utf-8");
|
|
3295
|
-
console.log(
|
|
3296
|
-
Manifest created: ${
|
|
3297
|
-
console.log(
|
|
3298
|
-
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."));
|
|
3299
3589
|
}
|
|
3300
3590
|
|
|
3301
3591
|
// dist/commands/update.js
|
|
3302
3592
|
init_check_update();
|
|
3303
3593
|
init_config();
|
|
3304
|
-
import
|
|
3594
|
+
import chalk22 from "chalk";
|
|
3305
3595
|
import { execFileSync as execFileSync6, spawn } from "node:child_process";
|
|
3306
3596
|
async function fetchLatestVersion() {
|
|
3307
3597
|
try {
|
|
@@ -3350,13 +3640,13 @@ function spawnPostUpdate() {
|
|
|
3350
3640
|
}
|
|
3351
3641
|
async function postUpdateFlow() {
|
|
3352
3642
|
const { confirm: confirm4 } = await import("@inquirer/prompts");
|
|
3353
|
-
console.log(
|
|
3643
|
+
console.log(chalk22.green(`
|
|
3354
3644
|
md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.
|
|
3355
3645
|
`));
|
|
3356
3646
|
const creds = await loadCredentials();
|
|
3357
3647
|
const isLoggedIn = !!creds?.accessToken && Date.now() < creds.expiresAt;
|
|
3358
3648
|
if (!isLoggedIn) {
|
|
3359
|
-
console.log(
|
|
3649
|
+
console.log(chalk22.yellow(" You are not logged in.\n"));
|
|
3360
3650
|
const wantLogin = await confirm4({
|
|
3361
3651
|
message: "Log in now?",
|
|
3362
3652
|
default: true
|
|
@@ -3367,7 +3657,7 @@ async function postUpdateFlow() {
|
|
|
3367
3657
|
await loginCommand2();
|
|
3368
3658
|
console.log("");
|
|
3369
3659
|
} else {
|
|
3370
|
-
console.log(
|
|
3660
|
+
console.log(chalk22.dim("\nRun md4ai login when you're ready.\n"));
|
|
3371
3661
|
return;
|
|
3372
3662
|
}
|
|
3373
3663
|
}
|
|
@@ -3401,7 +3691,7 @@ async function postUpdateFlow() {
|
|
|
3401
3691
|
const { mcpWatchCommand: mcpWatchCommand2 } = await Promise.resolve().then(() => (init_mcp_watch(), mcp_watch_exports));
|
|
3402
3692
|
await mcpWatchCommand2();
|
|
3403
3693
|
} else {
|
|
3404
|
-
console.log(
|
|
3694
|
+
console.log(chalk22.dim("\nYou can start monitoring later with: md4ai mcp-watch\n"));
|
|
3405
3695
|
}
|
|
3406
3696
|
}
|
|
3407
3697
|
async function updateCommand(options) {
|
|
@@ -3409,19 +3699,19 @@ async function updateCommand(options) {
|
|
|
3409
3699
|
await postUpdateFlow();
|
|
3410
3700
|
return;
|
|
3411
3701
|
}
|
|
3412
|
-
console.log(
|
|
3702
|
+
console.log(chalk22.blue(`
|
|
3413
3703
|
Current version: v${CURRENT_VERSION}`));
|
|
3414
|
-
console.log(
|
|
3704
|
+
console.log(chalk22.dim(" Checking for updates...\n"));
|
|
3415
3705
|
const latest = await fetchLatestVersion();
|
|
3416
3706
|
if (!latest) {
|
|
3417
|
-
console.log(
|
|
3707
|
+
console.log(chalk22.yellow(" Could not reach the npm registry. Check your internet connection."));
|
|
3418
3708
|
process.exit(1);
|
|
3419
3709
|
}
|
|
3420
3710
|
if (!isNewer2(latest, CURRENT_VERSION)) {
|
|
3421
3711
|
await postUpdateFlow();
|
|
3422
3712
|
return;
|
|
3423
3713
|
}
|
|
3424
|
-
console.log(
|
|
3714
|
+
console.log(chalk22.white(" Update available: ") + chalk22.dim(`v${CURRENT_VERSION}`) + chalk22.white(" \u2192 ") + chalk22.green.bold(`v${latest}
|
|
3425
3715
|
`));
|
|
3426
3716
|
const { confirm: confirm4 } = await import("@inquirer/prompts");
|
|
3427
3717
|
const wantUpdate = await confirm4({
|
|
@@ -3429,20 +3719,38 @@ async function updateCommand(options) {
|
|
|
3429
3719
|
default: true
|
|
3430
3720
|
});
|
|
3431
3721
|
if (!wantUpdate) {
|
|
3432
|
-
console.log(
|
|
3722
|
+
console.log(chalk22.dim("\nUpdate skipped.\n"));
|
|
3433
3723
|
return;
|
|
3434
3724
|
}
|
|
3435
|
-
console.log(
|
|
3725
|
+
console.log(chalk22.blue("\n Installing...\n"));
|
|
3436
3726
|
const ok = runNpmInstall();
|
|
3437
3727
|
if (!ok) {
|
|
3438
|
-
console.log(
|
|
3439
|
-
console.log(
|
|
3728
|
+
console.log(chalk22.red("\n npm install failed. Try running manually:"));
|
|
3729
|
+
console.log(chalk22.cyan(" npm install -g md4ai\n"));
|
|
3440
3730
|
process.exit(1);
|
|
3441
3731
|
}
|
|
3442
|
-
console.log(
|
|
3732
|
+
console.log(chalk22.green("\n Updated successfully.\n"));
|
|
3443
3733
|
spawnPostUpdate();
|
|
3444
3734
|
}
|
|
3445
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
|
+
|
|
3446
3754
|
// dist/index.js
|
|
3447
3755
|
init_check_update();
|
|
3448
3756
|
var program = new Command();
|
|
@@ -3465,6 +3773,8 @@ program.command("update").description("Check for updates, install, and optionall
|
|
|
3465
3773
|
program.command("check-update").description("Check if a newer version of md4ai is available").action(checkForUpdate);
|
|
3466
3774
|
program.command("mcp-watch").description("Monitor MCP server status on this device (runs until Ctrl+C)").action(mcpWatchCommand);
|
|
3467
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);
|
|
3468
3778
|
var admin = program.command("admin").description("Admin commands for managing the tools registry");
|
|
3469
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);
|
|
3470
3780
|
admin.command("list-tools").description("List all tools in the master registry").action(adminListToolsCommand);
|