md4ai 0.10.2 → 0.10.4
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/README.md +167 -0
- package/dist/index.bundled.js +475 -274
- package/package.json +6 -3
package/dist/index.bundled.js
CHANGED
|
@@ -9,6 +9,74 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// dist/check-update.js
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
async function fetchLatest() {
|
|
15
|
+
try {
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
18
|
+
const res = await fetch("https://registry.npmjs.org/md4ai/latest", {
|
|
19
|
+
signal: controller.signal
|
|
20
|
+
});
|
|
21
|
+
clearTimeout(timeout);
|
|
22
|
+
if (!res.ok)
|
|
23
|
+
return null;
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
return data.version ?? null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function printUpdateBanner(latest) {
|
|
31
|
+
console.log("");
|
|
32
|
+
console.log(chalk.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"));
|
|
33
|
+
console.log(chalk.yellow("\u2502") + chalk.bold(" Update available! ") + chalk.dim(`${CURRENT_VERSION}`) + chalk.white(" \u2192 ") + chalk.green.bold(`${latest}`) + " " + chalk.yellow("\u2502"));
|
|
34
|
+
console.log(chalk.yellow("\u2502") + " " + chalk.yellow("\u2502"));
|
|
35
|
+
console.log(chalk.yellow("\u2502") + " Run: " + chalk.cyan("md4ai update") + " " + chalk.yellow("\u2502"));
|
|
36
|
+
console.log(chalk.yellow("\u2502") + " " + chalk.yellow("\u2502"));
|
|
37
|
+
console.log(chalk.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"));
|
|
38
|
+
console.log("");
|
|
39
|
+
}
|
|
40
|
+
async function checkForUpdate() {
|
|
41
|
+
const latest = await fetchLatest();
|
|
42
|
+
if (!latest)
|
|
43
|
+
return;
|
|
44
|
+
if (latest !== CURRENT_VERSION && isNewer(latest, CURRENT_VERSION)) {
|
|
45
|
+
printUpdateBanner(latest);
|
|
46
|
+
} else {
|
|
47
|
+
console.log(chalk.green(`md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function autoCheckForUpdate() {
|
|
51
|
+
try {
|
|
52
|
+
const latest = await fetchLatest();
|
|
53
|
+
if (!latest)
|
|
54
|
+
return;
|
|
55
|
+
if (latest !== CURRENT_VERSION && isNewer(latest, CURRENT_VERSION)) {
|
|
56
|
+
printUpdateBanner(latest);
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function isNewer(a, b) {
|
|
62
|
+
const pa = a.split(".").map(Number);
|
|
63
|
+
const pb = b.split(".").map(Number);
|
|
64
|
+
for (let i = 0; i < 3; i++) {
|
|
65
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0))
|
|
66
|
+
return true;
|
|
67
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0))
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
var CURRENT_VERSION;
|
|
73
|
+
var init_check_update = __esm({
|
|
74
|
+
"dist/check-update.js"() {
|
|
75
|
+
"use strict";
|
|
76
|
+
CURRENT_VERSION = true ? "0.10.4" : "0.0.0-dev";
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
12
80
|
// ../packages/shared/dist/types.js
|
|
13
81
|
var init_types = __esm({
|
|
14
82
|
"../packages/shared/dist/types.js"() {
|
|
@@ -83,12 +151,8 @@ var init_dist = __esm({
|
|
|
83
151
|
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
84
152
|
import { join } from "node:path";
|
|
85
153
|
import { homedir } from "node:os";
|
|
86
|
-
import { existsSync } from "node:fs";
|
|
87
154
|
async function ensureConfigDir() {
|
|
88
|
-
|
|
89
|
-
await mkdir(configPath, { recursive: true });
|
|
90
|
-
await chmod(configPath, 448);
|
|
91
|
-
}
|
|
155
|
+
await mkdir(configPath, { recursive: true, mode: 448 });
|
|
92
156
|
}
|
|
93
157
|
async function saveCredentials(creds) {
|
|
94
158
|
await ensureConfigDir();
|
|
@@ -115,7 +179,7 @@ async function mergeCredentials(partial) {
|
|
|
115
179
|
}
|
|
116
180
|
async function clearCredentials() {
|
|
117
181
|
try {
|
|
118
|
-
await writeFile(credentialsPath, "{}",
|
|
182
|
+
await writeFile(credentialsPath, "{}", { mode: 384 });
|
|
119
183
|
} catch {
|
|
120
184
|
}
|
|
121
185
|
}
|
|
@@ -156,10 +220,10 @@ var login_exports = {};
|
|
|
156
220
|
__export(login_exports, {
|
|
157
221
|
loginCommand: () => loginCommand
|
|
158
222
|
});
|
|
159
|
-
import
|
|
223
|
+
import chalk2 from "chalk";
|
|
160
224
|
import { input, password } from "@inquirer/prompts";
|
|
161
225
|
async function loginCommand() {
|
|
162
|
-
console.log(
|
|
226
|
+
console.log(chalk2.blue("MD4AI Login\n"));
|
|
163
227
|
const email = await input({ message: "Email:" });
|
|
164
228
|
const pwd = await password({ message: "Password:" });
|
|
165
229
|
const anonKey = getAnonKey();
|
|
@@ -169,7 +233,7 @@ async function loginCommand() {
|
|
|
169
233
|
password: pwd
|
|
170
234
|
});
|
|
171
235
|
if (error) {
|
|
172
|
-
console.error(
|
|
236
|
+
console.error(chalk2.red(`Login failed: ${error.message}`));
|
|
173
237
|
process.exit(1);
|
|
174
238
|
}
|
|
175
239
|
await saveCredentials({
|
|
@@ -179,7 +243,7 @@ async function loginCommand() {
|
|
|
179
243
|
userId: data.user.id,
|
|
180
244
|
email: data.user.email ?? email
|
|
181
245
|
});
|
|
182
|
-
console.log(
|
|
246
|
+
console.log(chalk2.green(`
|
|
183
247
|
Logged in as ${data.user.email}`));
|
|
184
248
|
}
|
|
185
249
|
var init_login = __esm({
|
|
@@ -191,14 +255,14 @@ var init_login = __esm({
|
|
|
191
255
|
});
|
|
192
256
|
|
|
193
257
|
// dist/auth.js
|
|
194
|
-
import
|
|
258
|
+
import chalk5 from "chalk";
|
|
195
259
|
import { confirm, input as input2, password as password2 } from "@inquirer/prompts";
|
|
196
260
|
async function getAuthenticatedClient() {
|
|
197
261
|
const creds = await loadCredentials();
|
|
198
262
|
const needsLogin = !creds?.accessToken || Date.now() > creds.expiresAt;
|
|
199
263
|
if (needsLogin) {
|
|
200
264
|
const reason = !creds?.accessToken ? "Not logged in." : "Session expired.";
|
|
201
|
-
console.log(
|
|
265
|
+
console.log(chalk5.yellow(`${reason}`));
|
|
202
266
|
const shouldLogin = await confirm({ message: "Would you like to log in now?" });
|
|
203
267
|
if (!shouldLogin) {
|
|
204
268
|
process.exit(0);
|
|
@@ -230,7 +294,7 @@ async function refreshSession() {
|
|
|
230
294
|
async function getLongLivedClient() {
|
|
231
295
|
const creds = await loadCredentials();
|
|
232
296
|
if (!creds?.accessToken || !creds?.refreshToken) {
|
|
233
|
-
console.log(
|
|
297
|
+
console.log(chalk5.yellow("Not logged in."));
|
|
234
298
|
const shouldLogin = await confirm({ message: "Would you like to log in now?" });
|
|
235
299
|
if (!shouldLogin)
|
|
236
300
|
process.exit(0);
|
|
@@ -247,7 +311,7 @@ async function getLongLivedClient() {
|
|
|
247
311
|
});
|
|
248
312
|
return { supabase: result.client, userId: result.userId };
|
|
249
313
|
} catch {
|
|
250
|
-
console.log(
|
|
314
|
+
console.log(chalk5.yellow("Session expired \u2014 please log in again."));
|
|
251
315
|
const shouldLogin = await confirm({ message: "Would you like to log in now?" });
|
|
252
316
|
if (!shouldLogin)
|
|
253
317
|
process.exit(0);
|
|
@@ -265,7 +329,7 @@ async function promptLogin() {
|
|
|
265
329
|
password: pwd
|
|
266
330
|
});
|
|
267
331
|
if (error) {
|
|
268
|
-
console.error(
|
|
332
|
+
console.error(chalk5.red(`Login failed: ${error.message}`));
|
|
269
333
|
process.exit(1);
|
|
270
334
|
}
|
|
271
335
|
await saveCredentials({
|
|
@@ -275,7 +339,7 @@ async function promptLogin() {
|
|
|
275
339
|
userId: data.user.id,
|
|
276
340
|
email: data.user.email ?? email
|
|
277
341
|
});
|
|
278
|
-
console.log(
|
|
342
|
+
console.log(chalk5.green(`Logged in as ${data.user.email}
|
|
279
343
|
`));
|
|
280
344
|
const authedSupabase = createSupabaseClient(anonKey, data.session.access_token);
|
|
281
345
|
return { supabase: authedSupabase, userId: data.user.id };
|
|
@@ -290,7 +354,7 @@ var init_auth = __esm({
|
|
|
290
354
|
|
|
291
355
|
// dist/scanner/file-parser.js
|
|
292
356
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
293
|
-
import { existsSync
|
|
357
|
+
import { existsSync } from "node:fs";
|
|
294
358
|
import { resolve as resolve2, dirname, join as join2 } from "node:path";
|
|
295
359
|
import { homedir as homedir2 } from "node:os";
|
|
296
360
|
async function parseFileReferences(filePath, projectRoot) {
|
|
@@ -318,7 +382,7 @@ async function parseFileReferences(filePath, projectRoot) {
|
|
|
318
382
|
if (!target.includes("/") && /^[A-Z]/.test(baseName))
|
|
319
383
|
continue;
|
|
320
384
|
const resolved = resolveTarget(target, filePath, projectRoot);
|
|
321
|
-
if (resolved &&
|
|
385
|
+
if (resolved && existsSync(resolved)) {
|
|
322
386
|
addRef(refs, relPath, resolved, projectRoot);
|
|
323
387
|
} else if (resolved) {
|
|
324
388
|
const targetRel = resolved.startsWith(projectRoot) ? resolved.slice(projectRoot.length + 1) : resolved;
|
|
@@ -361,7 +425,7 @@ function resolveTarget(target, sourceFilePath, projectRoot) {
|
|
|
361
425
|
}
|
|
362
426
|
if (target.includes("/")) {
|
|
363
427
|
const fromRoot = join2(projectRoot, target);
|
|
364
|
-
if (
|
|
428
|
+
if (existsSync(fromRoot))
|
|
365
429
|
return fromRoot;
|
|
366
430
|
}
|
|
367
431
|
return resolve2(dirname(sourceFilePath), target);
|
|
@@ -387,7 +451,7 @@ function parseJsonPathReferences(content, fromRel, projectRoot, refs, brokenRefs
|
|
|
387
451
|
while ((match = pattern.exec(value)) !== null) {
|
|
388
452
|
const relTarget = match[1];
|
|
389
453
|
const resolved = join2(projectRoot, relTarget);
|
|
390
|
-
if (
|
|
454
|
+
if (existsSync(resolved)) {
|
|
391
455
|
addRef(refs, fromRel, resolved, projectRoot);
|
|
392
456
|
} else {
|
|
393
457
|
brokenRefs.push({ from: fromRel, to: relTarget, rawRef: relTarget });
|
|
@@ -401,7 +465,7 @@ function parseJsonPathReferences(content, fromRel, projectRoot, refs, brokenRefs
|
|
|
401
465
|
if (!target || target.startsWith("http"))
|
|
402
466
|
continue;
|
|
403
467
|
const resolved = join2(projectRoot, target);
|
|
404
|
-
if (
|
|
468
|
+
if (existsSync(resolved)) {
|
|
405
469
|
addRef(refs, fromRel, resolved, projectRoot);
|
|
406
470
|
} else {
|
|
407
471
|
brokenRefs.push({ from: fromRel, to: target, rawRef: target });
|
|
@@ -609,7 +673,7 @@ var init_orphan_detector = __esm({
|
|
|
609
673
|
// dist/scanner/skills-parser.js
|
|
610
674
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
611
675
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
612
|
-
import { existsSync as
|
|
676
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
613
677
|
import { join as join5 } from "node:path";
|
|
614
678
|
import { homedir as homedir3 } from "node:os";
|
|
615
679
|
async function parseSkills(projectRoot) {
|
|
@@ -638,7 +702,7 @@ async function parseSkills(projectRoot) {
|
|
|
638
702
|
await parseSettingsForPlugins(projectSettings, skills, false);
|
|
639
703
|
await parseSettingsForPlugins(projectLocalSettings, skills, false);
|
|
640
704
|
const skillsMd = join5(projectRoot, "skills.md");
|
|
641
|
-
if (
|
|
705
|
+
if (existsSync2(skillsMd)) {
|
|
642
706
|
const content = await readFile3(skillsMd, "utf-8");
|
|
643
707
|
const skillNames = content.match(/^#+\s+(.+)$/gm);
|
|
644
708
|
if (skillNames) {
|
|
@@ -661,7 +725,7 @@ async function parseSkills(projectRoot) {
|
|
|
661
725
|
return Array.from(skills.values());
|
|
662
726
|
}
|
|
663
727
|
async function parseSettingsForPlugins(settingsPath, skills, isMachineWide) {
|
|
664
|
-
if (!
|
|
728
|
+
if (!existsSync2(settingsPath))
|
|
665
729
|
return;
|
|
666
730
|
try {
|
|
667
731
|
const content = await readFile3(settingsPath, "utf-8");
|
|
@@ -698,7 +762,7 @@ var init_skills_parser = __esm({
|
|
|
698
762
|
|
|
699
763
|
// dist/scanner/tooling-detector.js
|
|
700
764
|
import { readFile as readFile4, readdir } from "node:fs/promises";
|
|
701
|
-
import { existsSync as
|
|
765
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
702
766
|
import { join as join6 } from "node:path";
|
|
703
767
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
704
768
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -715,7 +779,7 @@ async function detectToolings(projectRoot) {
|
|
|
715
779
|
async function detectFromPackageJson(projectRoot) {
|
|
716
780
|
const toolings = [];
|
|
717
781
|
const pkgPath = join6(projectRoot, "package.json");
|
|
718
|
-
if (!
|
|
782
|
+
if (!existsSync3(pkgPath))
|
|
719
783
|
return toolings;
|
|
720
784
|
const resolvedVersions = await getResolvedVersions(projectRoot);
|
|
721
785
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -759,7 +823,7 @@ async function discoverWorkspacePackageJsons(projectRoot) {
|
|
|
759
823
|
const cleanPattern = pattern.replace(/\/\*\*?$/, "");
|
|
760
824
|
if (isGlob) {
|
|
761
825
|
const parentDir = join6(projectRoot, cleanPattern);
|
|
762
|
-
if (!
|
|
826
|
+
if (!existsSync3(parentDir))
|
|
763
827
|
continue;
|
|
764
828
|
try {
|
|
765
829
|
const entries = await readdir(parentDir, { withFileTypes: true });
|
|
@@ -767,7 +831,7 @@ async function discoverWorkspacePackageJsons(projectRoot) {
|
|
|
767
831
|
if (!entry.isDirectory())
|
|
768
832
|
continue;
|
|
769
833
|
const pkgPath = join6(parentDir, entry.name, "package.json");
|
|
770
|
-
if (
|
|
834
|
+
if (existsSync3(pkgPath)) {
|
|
771
835
|
results.push(pkgPath);
|
|
772
836
|
}
|
|
773
837
|
}
|
|
@@ -775,7 +839,7 @@ async function discoverWorkspacePackageJsons(projectRoot) {
|
|
|
775
839
|
}
|
|
776
840
|
} else {
|
|
777
841
|
const pkgPath = join6(projectRoot, cleanPattern, "package.json");
|
|
778
|
-
if (
|
|
842
|
+
if (existsSync3(pkgPath)) {
|
|
779
843
|
results.push(pkgPath);
|
|
780
844
|
}
|
|
781
845
|
}
|
|
@@ -784,7 +848,7 @@ async function discoverWorkspacePackageJsons(projectRoot) {
|
|
|
784
848
|
}
|
|
785
849
|
async function getWorkspacePatterns(projectRoot) {
|
|
786
850
|
const pnpmWorkspace = join6(projectRoot, "pnpm-workspace.yaml");
|
|
787
|
-
if (
|
|
851
|
+
if (existsSync3(pnpmWorkspace)) {
|
|
788
852
|
try {
|
|
789
853
|
const content = await readFile4(pnpmWorkspace, "utf-8");
|
|
790
854
|
const patterns = [];
|
|
@@ -827,7 +891,7 @@ function stripVersionPrefix(version) {
|
|
|
827
891
|
async function getResolvedVersions(projectRoot) {
|
|
828
892
|
const versions = /* @__PURE__ */ new Map();
|
|
829
893
|
const pnpmLock = join6(projectRoot, "pnpm-lock.yaml");
|
|
830
|
-
if (
|
|
894
|
+
if (existsSync3(pnpmLock)) {
|
|
831
895
|
try {
|
|
832
896
|
const content = await readFile4(pnpmLock, "utf-8");
|
|
833
897
|
const versionPattern = /^\s{4}'?(@?[^@\s:]+)(?:@[^:]+)?'?:\s*(?:version:\s*)?'?(\d+\.\d+[^'\s]*)/gm;
|
|
@@ -843,7 +907,7 @@ async function getResolvedVersions(projectRoot) {
|
|
|
843
907
|
}
|
|
844
908
|
if (versions.size === 0) {
|
|
845
909
|
const npmLock = join6(projectRoot, "package-lock.json");
|
|
846
|
-
if (
|
|
910
|
+
if (existsSync3(npmLock)) {
|
|
847
911
|
try {
|
|
848
912
|
const content = await readFile4(npmLock, "utf-8");
|
|
849
913
|
const lock = JSON.parse(content);
|
|
@@ -864,7 +928,7 @@ async function getResolvedVersions(projectRoot) {
|
|
|
864
928
|
function detectFromCli(projectRoot) {
|
|
865
929
|
const toolings = [];
|
|
866
930
|
for (const { name, command, args, conditionFile } of CLI_VERSION_COMMANDS) {
|
|
867
|
-
if (conditionFile && !
|
|
931
|
+
if (conditionFile && !existsSync3(join6(projectRoot, conditionFile))) {
|
|
868
932
|
continue;
|
|
869
933
|
}
|
|
870
934
|
try {
|
|
@@ -897,7 +961,7 @@ async function detectFromMcpSettings(projectRoot) {
|
|
|
897
961
|
];
|
|
898
962
|
const seen = /* @__PURE__ */ new Set();
|
|
899
963
|
for (const settingsPath of settingsPaths) {
|
|
900
|
-
if (!
|
|
964
|
+
if (!existsSync3(settingsPath))
|
|
901
965
|
continue;
|
|
902
966
|
try {
|
|
903
967
|
const content = await readFile4(settingsPath, "utf-8");
|
|
@@ -1042,13 +1106,24 @@ async function fetchVercelEnvVars(projectId, orgId, token) {
|
|
|
1042
1106
|
}
|
|
1043
1107
|
const data = await res.json();
|
|
1044
1108
|
const envs = data.envs ?? [];
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
targets
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1109
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1110
|
+
for (const e of envs) {
|
|
1111
|
+
const targets = e.target ?? ["production", "preview", "development"];
|
|
1112
|
+
const existing = merged.get(e.key);
|
|
1113
|
+
if (existing) {
|
|
1114
|
+
const targetSet = /* @__PURE__ */ new Set([...existing.targets, ...targets]);
|
|
1115
|
+
existing.targets = Array.from(targetSet);
|
|
1116
|
+
} else {
|
|
1117
|
+
merged.set(e.key, {
|
|
1118
|
+
key: e.key,
|
|
1119
|
+
targets: [...targets],
|
|
1120
|
+
type: e.type ?? "plain",
|
|
1121
|
+
inManifest: false
|
|
1122
|
+
// populated later by the scanner
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return Array.from(merged.values());
|
|
1052
1127
|
}
|
|
1053
1128
|
var VercelApiError;
|
|
1054
1129
|
var init_fetch_env_vars = __esm({
|
|
@@ -1074,11 +1149,11 @@ import { readFile as readFile7, glob as glob2 } from "node:fs/promises";
|
|
|
1074
1149
|
import { execFile } from "node:child_process";
|
|
1075
1150
|
import { promisify } from "node:util";
|
|
1076
1151
|
import { join as join9, relative as relative2 } from "node:path";
|
|
1077
|
-
import { existsSync as
|
|
1078
|
-
import
|
|
1152
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1153
|
+
import chalk9 from "chalk";
|
|
1079
1154
|
async function scanEnvManifest(projectRoot) {
|
|
1080
1155
|
const manifestFullPath = join9(projectRoot, MANIFEST_PATH);
|
|
1081
|
-
if (!
|
|
1156
|
+
if (!existsSync4(manifestFullPath)) {
|
|
1082
1157
|
return null;
|
|
1083
1158
|
}
|
|
1084
1159
|
const manifestContent = await readFile7(manifestFullPath, "utf-8");
|
|
@@ -1136,16 +1211,16 @@ function tokenFixInstructions(source) {
|
|
|
1136
1211
|
async function checkVercelEnvVars(projectRoot, variables) {
|
|
1137
1212
|
const tokenResult = await resolveVercelTokenWithSource();
|
|
1138
1213
|
if (!tokenResult) {
|
|
1139
|
-
console.log(
|
|
1140
|
-
console.log(
|
|
1141
|
-
console.log(
|
|
1214
|
+
console.log(chalk9.dim(" Vercel checks skipped (no token configured)"));
|
|
1215
|
+
console.log(chalk9.dim(" To enable: md4ai config set vercel-token <token>"));
|
|
1216
|
+
console.log(chalk9.dim(" Generate a token at https://vercel.com/account/tokens"));
|
|
1142
1217
|
return null;
|
|
1143
1218
|
}
|
|
1144
1219
|
const { token, source } = tokenResult;
|
|
1145
1220
|
const projects = await discoverVercelProjects(projectRoot);
|
|
1146
1221
|
if (projects.length === 0)
|
|
1147
1222
|
return null;
|
|
1148
|
-
console.log(
|
|
1223
|
+
console.log(chalk9.dim(` Checking ${projects.length} Vercel project(s)...`));
|
|
1149
1224
|
const discovered = [];
|
|
1150
1225
|
const manifestVarNames = new Set(variables.map((v) => v.name));
|
|
1151
1226
|
let tokenErrorShown = false;
|
|
@@ -1167,23 +1242,23 @@ async function checkVercelEnvVars(projectRoot, variables) {
|
|
|
1167
1242
|
mv.vercelStatus = {};
|
|
1168
1243
|
mv.vercelStatus[proj.projectName] = vercelKeySet.has(mv.name) ? "present" : "missing";
|
|
1169
1244
|
}
|
|
1170
|
-
console.log(
|
|
1245
|
+
console.log(chalk9.dim(` ${proj.projectName}: ${vars.length} var(s)`));
|
|
1171
1246
|
} catch (err) {
|
|
1172
1247
|
if (err instanceof VercelApiError && err.isInvalidToken && !tokenErrorShown) {
|
|
1173
1248
|
tokenErrorShown = true;
|
|
1174
|
-
console.log(
|
|
1175
|
-
console.log(
|
|
1176
|
-
console.log(
|
|
1249
|
+
console.log(chalk9.red(` ${proj.projectName}: Token is invalid or expired`));
|
|
1250
|
+
console.log(chalk9.yellow(` Token source: ${tokenSourceLabel(source)}`));
|
|
1251
|
+
console.log(chalk9.yellow(" To fix:"));
|
|
1177
1252
|
for (const step of tokenFixInstructions(source)) {
|
|
1178
|
-
console.log(
|
|
1253
|
+
console.log(chalk9.yellow(` \u2192 ${step}`));
|
|
1179
1254
|
}
|
|
1180
1255
|
} else if (err instanceof VercelApiError && err.statusCode === 403) {
|
|
1181
|
-
console.log(
|
|
1182
|
-
console.log(
|
|
1183
|
-
console.log(
|
|
1184
|
-
console.log(
|
|
1256
|
+
console.log(chalk9.yellow(` ${proj.projectName}: ${err.message}`));
|
|
1257
|
+
console.log(chalk9.dim(` Token source: ${tokenSourceLabel(source)}`));
|
|
1258
|
+
console.log(chalk9.dim(" Ensure the token has access to this team/project"));
|
|
1259
|
+
console.log(chalk9.dim(" Check: https://vercel.com/account/tokens"));
|
|
1185
1260
|
} else {
|
|
1186
|
-
console.log(
|
|
1261
|
+
console.log(chalk9.yellow(` ${proj.projectName}: ${err instanceof Error ? err.message : "API error"}`));
|
|
1187
1262
|
}
|
|
1188
1263
|
}
|
|
1189
1264
|
}
|
|
@@ -1222,16 +1297,16 @@ async function listGitHubSecretNames(repoSlug) {
|
|
|
1222
1297
|
async function checkGitHubSecrets(projectRoot, variables) {
|
|
1223
1298
|
const slug = await detectGitHubRepoSlug(projectRoot);
|
|
1224
1299
|
if (!slug) {
|
|
1225
|
-
console.log(
|
|
1300
|
+
console.log(chalk9.dim(" GitHub checks skipped (no GitHub remote detected)"));
|
|
1226
1301
|
return null;
|
|
1227
1302
|
}
|
|
1228
1303
|
const secretNames = await listGitHubSecretNames(slug);
|
|
1229
1304
|
if (!secretNames) {
|
|
1230
|
-
console.log(
|
|
1305
|
+
console.log(chalk9.dim(" GitHub checks skipped (gh CLI not available or not authenticated)"));
|
|
1231
1306
|
return slug;
|
|
1232
1307
|
}
|
|
1233
1308
|
const secretSet = new Set(secretNames);
|
|
1234
|
-
console.log(
|
|
1309
|
+
console.log(chalk9.dim(` GitHub secrets: ${secretNames.length} found in ${slug}`));
|
|
1235
1310
|
for (const v of variables) {
|
|
1236
1311
|
if (v.requiredIn.github) {
|
|
1237
1312
|
v.githubStatus = secretSet.has(v.name) ? "present" : "missing";
|
|
@@ -1320,7 +1395,7 @@ async function checkLocalEnvFiles(projectRoot, apps) {
|
|
|
1320
1395
|
for (const app of apps) {
|
|
1321
1396
|
const envPath = join9(projectRoot, app.envFilePath);
|
|
1322
1397
|
const varNames = /* @__PURE__ */ new Set();
|
|
1323
|
-
if (
|
|
1398
|
+
if (existsSync4(envPath)) {
|
|
1324
1399
|
const content = await readFile7(envPath, "utf-8");
|
|
1325
1400
|
for (const line of content.split("\n")) {
|
|
1326
1401
|
const m = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
@@ -1335,7 +1410,7 @@ async function checkLocalEnvFiles(projectRoot, apps) {
|
|
|
1335
1410
|
}
|
|
1336
1411
|
async function parseWorkflowSecrets(projectRoot) {
|
|
1337
1412
|
const workflowsDir = join9(projectRoot, ".github", "workflows");
|
|
1338
|
-
if (!
|
|
1413
|
+
if (!existsSync4(workflowsDir))
|
|
1339
1414
|
return [];
|
|
1340
1415
|
const refs = [];
|
|
1341
1416
|
const secretRe = /\$\{\{\s*secrets\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;
|
|
@@ -1416,11 +1491,111 @@ var init_env_manifest_scanner = __esm({
|
|
|
1416
1491
|
}
|
|
1417
1492
|
});
|
|
1418
1493
|
|
|
1494
|
+
// dist/scanner/marketplace-scanner.js
|
|
1495
|
+
import { readFile as readFile8, readdir as readdir2, stat } from "node:fs/promises";
|
|
1496
|
+
import { join as join10, resolve as resolve3 } from "node:path";
|
|
1497
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1498
|
+
import { homedir as homedir6 } from "node:os";
|
|
1499
|
+
async function scanMarketplacePlugins() {
|
|
1500
|
+
const pluginsDir = join10(homedir6(), ".claude", "plugins");
|
|
1501
|
+
const installedPath = join10(pluginsDir, "installed_plugins.json");
|
|
1502
|
+
const marketplacesPath = join10(pluginsDir, "known_marketplaces.json");
|
|
1503
|
+
if (!existsSync5(installedPath))
|
|
1504
|
+
return [];
|
|
1505
|
+
let installed;
|
|
1506
|
+
let knownMarketplaces = {};
|
|
1507
|
+
try {
|
|
1508
|
+
installed = JSON.parse(await readFile8(installedPath, "utf-8"));
|
|
1509
|
+
} catch {
|
|
1510
|
+
return [];
|
|
1511
|
+
}
|
|
1512
|
+
try {
|
|
1513
|
+
if (existsSync5(marketplacesPath)) {
|
|
1514
|
+
knownMarketplaces = JSON.parse(await readFile8(marketplacesPath, "utf-8"));
|
|
1515
|
+
}
|
|
1516
|
+
} catch {
|
|
1517
|
+
}
|
|
1518
|
+
const results = [];
|
|
1519
|
+
const expectedBase = resolve3(pluginsDir);
|
|
1520
|
+
for (const [key, entries] of Object.entries(installed.plugins ?? {})) {
|
|
1521
|
+
if (!entries?.length)
|
|
1522
|
+
continue;
|
|
1523
|
+
const entry = entries[0];
|
|
1524
|
+
const resolvedInstallPath = resolve3(entry.installPath);
|
|
1525
|
+
if (!resolvedInstallPath.startsWith(expectedBase + "/"))
|
|
1526
|
+
continue;
|
|
1527
|
+
const atIdx = key.lastIndexOf("@");
|
|
1528
|
+
if (atIdx === -1)
|
|
1529
|
+
continue;
|
|
1530
|
+
const pluginName = key.slice(0, atIdx);
|
|
1531
|
+
const marketplace = key.slice(atIdx + 1);
|
|
1532
|
+
const homepageUrl = buildHomepageUrl(knownMarketplaces, marketplace, pluginName);
|
|
1533
|
+
const skills = await discoverSkills(entry.installPath);
|
|
1534
|
+
results.push({
|
|
1535
|
+
marketplace,
|
|
1536
|
+
pluginName,
|
|
1537
|
+
version: entry.version ?? null,
|
|
1538
|
+
homepageUrl,
|
|
1539
|
+
installedAt: entry.installedAt ?? null,
|
|
1540
|
+
lastUpdated: entry.lastUpdated ?? null,
|
|
1541
|
+
skills
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
results.sort((a, b) => a.marketplace.localeCompare(b.marketplace) || a.pluginName.localeCompare(b.pluginName));
|
|
1545
|
+
return results;
|
|
1546
|
+
}
|
|
1547
|
+
function buildHomepageUrl(knownMarketplaces, marketplace, pluginName) {
|
|
1548
|
+
const mp = knownMarketplaces[marketplace];
|
|
1549
|
+
if (!mp?.source?.repo)
|
|
1550
|
+
return null;
|
|
1551
|
+
const repo = mp.source.repo;
|
|
1552
|
+
const baseUrl = `https://github.com/${repo}`;
|
|
1553
|
+
if (marketplace === "claude-plugins-official") {
|
|
1554
|
+
return `${baseUrl}/tree/main/plugins/${pluginName}`;
|
|
1555
|
+
}
|
|
1556
|
+
return baseUrl;
|
|
1557
|
+
}
|
|
1558
|
+
async function discoverSkills(installPath) {
|
|
1559
|
+
const skillsDir = join10(installPath, "skills");
|
|
1560
|
+
if (!existsSync5(skillsDir))
|
|
1561
|
+
return [];
|
|
1562
|
+
const skills = [];
|
|
1563
|
+
try {
|
|
1564
|
+
const entries = await readdir2(skillsDir, { withFileTypes: true });
|
|
1565
|
+
for (const entry of entries) {
|
|
1566
|
+
if (!entry.isDirectory())
|
|
1567
|
+
continue;
|
|
1568
|
+
const skillMd = join10(skillsDir, entry.name, "SKILL.md");
|
|
1569
|
+
if (!existsSync5(skillMd))
|
|
1570
|
+
continue;
|
|
1571
|
+
let lastModified = null;
|
|
1572
|
+
try {
|
|
1573
|
+
const fileStat = await stat(skillMd);
|
|
1574
|
+
lastModified = fileStat.mtime.toISOString();
|
|
1575
|
+
} catch {
|
|
1576
|
+
}
|
|
1577
|
+
skills.push({
|
|
1578
|
+
name: entry.name,
|
|
1579
|
+
skillPath: `skills/${entry.name}/SKILL.md`,
|
|
1580
|
+
lastModified
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
} catch {
|
|
1584
|
+
}
|
|
1585
|
+
skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
1586
|
+
return skills;
|
|
1587
|
+
}
|
|
1588
|
+
var init_marketplace_scanner = __esm({
|
|
1589
|
+
"dist/scanner/marketplace-scanner.js"() {
|
|
1590
|
+
"use strict";
|
|
1591
|
+
}
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1419
1594
|
// dist/scanner/index.js
|
|
1420
|
-
import { readdir as
|
|
1421
|
-
import { join as
|
|
1595
|
+
import { readdir as readdir3 } from "node:fs/promises";
|
|
1596
|
+
import { join as join11, relative as relative3 } from "node:path";
|
|
1422
1597
|
import { existsSync as existsSync6 } from "node:fs";
|
|
1423
|
-
import { homedir as
|
|
1598
|
+
import { homedir as homedir7 } from "node:os";
|
|
1424
1599
|
import { createHash } from "node:crypto";
|
|
1425
1600
|
async function scanProject(projectRoot) {
|
|
1426
1601
|
const allFiles = await discoverFiles(projectRoot);
|
|
@@ -1428,7 +1603,7 @@ async function scanProject(projectRoot) {
|
|
|
1428
1603
|
const allRefs = [];
|
|
1429
1604
|
const allBrokenRefs = [];
|
|
1430
1605
|
for (const file of allFiles) {
|
|
1431
|
-
const fullPath = file.startsWith("/") ? file :
|
|
1606
|
+
const fullPath = file.startsWith("/") ? file : join11(projectRoot, file);
|
|
1432
1607
|
try {
|
|
1433
1608
|
const { refs, brokenRefs: brokenRefs2 } = await parseFileReferences(fullPath, projectRoot);
|
|
1434
1609
|
allRefs.push(...refs);
|
|
@@ -1442,6 +1617,7 @@ async function scanProject(projectRoot) {
|
|
|
1442
1617
|
const skills = await parseSkills(projectRoot);
|
|
1443
1618
|
const toolings = await detectToolings(projectRoot);
|
|
1444
1619
|
const envManifest = await scanEnvManifest(projectRoot);
|
|
1620
|
+
const marketplacePlugins = await scanMarketplacePlugins();
|
|
1445
1621
|
const depthMap = /* @__PURE__ */ new Map();
|
|
1446
1622
|
const queue = [...rootFiles];
|
|
1447
1623
|
for (const r of queue)
|
|
@@ -1462,7 +1638,7 @@ async function scanProject(projectRoot) {
|
|
|
1462
1638
|
depth: depthMap.get(br.from) ?? 999
|
|
1463
1639
|
}));
|
|
1464
1640
|
brokenRefs.sort((a, b) => a.depth - b.depth || a.from.localeCompare(b.from));
|
|
1465
|
-
const scanData = JSON.stringify({ graph, orphans, brokenRefs, skills, staleFiles, toolings, envManifest });
|
|
1641
|
+
const scanData = JSON.stringify({ graph, orphans, brokenRefs, skills, staleFiles, toolings, envManifest, marketplacePlugins });
|
|
1466
1642
|
const dataHash = createHash("sha256").update(scanData).digest("hex");
|
|
1467
1643
|
return {
|
|
1468
1644
|
graph,
|
|
@@ -1472,32 +1648,33 @@ async function scanProject(projectRoot) {
|
|
|
1472
1648
|
staleFiles,
|
|
1473
1649
|
toolings,
|
|
1474
1650
|
envManifest,
|
|
1651
|
+
marketplacePlugins,
|
|
1475
1652
|
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1476
1653
|
dataHash
|
|
1477
1654
|
};
|
|
1478
1655
|
}
|
|
1479
1656
|
async function discoverFiles(projectRoot) {
|
|
1480
1657
|
const files = [];
|
|
1481
|
-
const claudeDir =
|
|
1658
|
+
const claudeDir = join11(projectRoot, ".claude");
|
|
1482
1659
|
if (existsSync6(claudeDir)) {
|
|
1483
1660
|
await walkDir(claudeDir, projectRoot, files);
|
|
1484
1661
|
}
|
|
1485
|
-
if (existsSync6(
|
|
1662
|
+
if (existsSync6(join11(projectRoot, "CLAUDE.md"))) {
|
|
1486
1663
|
files.push("CLAUDE.md");
|
|
1487
1664
|
}
|
|
1488
|
-
if (existsSync6(
|
|
1665
|
+
if (existsSync6(join11(projectRoot, "skills.md"))) {
|
|
1489
1666
|
files.push("skills.md");
|
|
1490
1667
|
}
|
|
1491
|
-
const plansDir =
|
|
1668
|
+
const plansDir = join11(projectRoot, "docs", "plans");
|
|
1492
1669
|
if (existsSync6(plansDir)) {
|
|
1493
1670
|
await walkDir(plansDir, projectRoot, files);
|
|
1494
1671
|
}
|
|
1495
1672
|
return [...new Set(files)];
|
|
1496
1673
|
}
|
|
1497
1674
|
async function walkDir(dir, projectRoot, files) {
|
|
1498
|
-
const entries = await
|
|
1675
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
1499
1676
|
for (const entry of entries) {
|
|
1500
|
-
const fullPath =
|
|
1677
|
+
const fullPath = join11(dir, entry.name);
|
|
1501
1678
|
if (entry.isDirectory()) {
|
|
1502
1679
|
if (["node_modules", ".git", ".turbo", "cache", "session-env"].includes(entry.name))
|
|
1503
1680
|
continue;
|
|
@@ -1516,71 +1693,43 @@ function identifyRoots(allFiles, projectRoot) {
|
|
|
1516
1693
|
}
|
|
1517
1694
|
}
|
|
1518
1695
|
for (const globalFile of GLOBAL_ROOT_FILES) {
|
|
1519
|
-
const expanded = globalFile.replace("~",
|
|
1696
|
+
const expanded = globalFile.replace("~", homedir7());
|
|
1520
1697
|
if (existsSync6(expanded)) {
|
|
1521
1698
|
roots.push(globalFile);
|
|
1522
1699
|
}
|
|
1523
1700
|
}
|
|
1524
1701
|
return roots;
|
|
1525
1702
|
}
|
|
1526
|
-
async function readClaudeConfigFiles(projectRoot) {
|
|
1527
|
-
const { readFile:
|
|
1528
|
-
const {
|
|
1529
|
-
const
|
|
1530
|
-
const configPatterns = [
|
|
1531
|
-
"CLAUDE.md",
|
|
1532
|
-
".claude/CLAUDE.md",
|
|
1533
|
-
".claude/settings.json",
|
|
1534
|
-
".claude/commands/*.md",
|
|
1535
|
-
".claude/skills/*.md"
|
|
1536
|
-
];
|
|
1703
|
+
async function readClaudeConfigFiles(projectRoot, graphFilePaths) {
|
|
1704
|
+
const { readFile: readFile13, stat: stat2 } = await import("node:fs/promises");
|
|
1705
|
+
const { existsSync: existsSync14 } = await import("node:fs");
|
|
1706
|
+
const filePaths = graphFilePaths ?? await discoverFiles(projectRoot);
|
|
1537
1707
|
const files = [];
|
|
1538
1708
|
const seen = /* @__PURE__ */ new Set();
|
|
1539
|
-
|
|
1540
|
-
const relPath = relative5(projectRoot, fullPath);
|
|
1709
|
+
for (const relPath of filePaths) {
|
|
1541
1710
|
if (seen.has(relPath))
|
|
1542
|
-
|
|
1711
|
+
continue;
|
|
1712
|
+
if (relPath.startsWith("~") || relPath.startsWith("/"))
|
|
1713
|
+
continue;
|
|
1543
1714
|
seen.add(relPath);
|
|
1715
|
+
const fullPath = join11(projectRoot, relPath);
|
|
1716
|
+
if (!existsSync14(fullPath))
|
|
1717
|
+
continue;
|
|
1544
1718
|
try {
|
|
1545
|
-
const content = await
|
|
1546
|
-
const fileStat = await
|
|
1719
|
+
const content = await readFile13(fullPath, "utf-8");
|
|
1720
|
+
const fileStat = await stat2(fullPath);
|
|
1547
1721
|
const lastMod = getGitLastModified(fullPath, projectRoot);
|
|
1548
1722
|
files.push({ filePath: relPath, content, sizeBytes: fileStat.size, lastModified: lastMod });
|
|
1549
1723
|
} catch {
|
|
1550
1724
|
}
|
|
1551
1725
|
}
|
|
1552
|
-
for (const pattern of configPatterns) {
|
|
1553
|
-
for await (const fullPath of glob3(join16(projectRoot, pattern))) {
|
|
1554
|
-
if (!existsSync13(fullPath))
|
|
1555
|
-
continue;
|
|
1556
|
-
await addFile(fullPath);
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
const pkgPath = join16(projectRoot, "package.json");
|
|
1560
|
-
if (existsSync13(pkgPath)) {
|
|
1561
|
-
try {
|
|
1562
|
-
const pkgContent = await readFile12(pkgPath, "utf-8");
|
|
1563
|
-
const pkg = JSON.parse(pkgContent);
|
|
1564
|
-
const preflightCmd = pkg.scripts?.preflight;
|
|
1565
|
-
if (preflightCmd) {
|
|
1566
|
-
const match = preflightCmd.match(/(?:bash\s+|sh\s+|\.\/)?(\S+\.(?:sh|bash|ts|js|mjs))/);
|
|
1567
|
-
if (match) {
|
|
1568
|
-
const scriptPath = join16(projectRoot, match[1]);
|
|
1569
|
-
if (existsSync13(scriptPath)) {
|
|
1570
|
-
await addFile(scriptPath);
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
} catch {
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
1726
|
return files;
|
|
1578
1727
|
}
|
|
1579
1728
|
function detectStaleFiles(allFiles, projectRoot) {
|
|
1580
1729
|
const stale = [];
|
|
1581
1730
|
const now = Date.now();
|
|
1582
1731
|
for (const file of allFiles) {
|
|
1583
|
-
const fullPath =
|
|
1732
|
+
const fullPath = join11(projectRoot, file);
|
|
1584
1733
|
const lastMod = getGitLastModified(fullPath, projectRoot);
|
|
1585
1734
|
if (lastMod) {
|
|
1586
1735
|
const days = Math.floor((now - new Date(lastMod).getTime()) / (1e3 * 60 * 60 * 24));
|
|
@@ -1602,6 +1751,7 @@ var init_scanner = __esm({
|
|
|
1602
1751
|
init_git_dates();
|
|
1603
1752
|
init_tooling_detector();
|
|
1604
1753
|
init_env_manifest_scanner();
|
|
1754
|
+
init_marketplace_scanner();
|
|
1605
1755
|
}
|
|
1606
1756
|
});
|
|
1607
1757
|
|
|
@@ -1708,7 +1858,7 @@ function generateOfflineHtml(result, projectRoot) {
|
|
|
1708
1858
|
}
|
|
1709
1859
|
</script>
|
|
1710
1860
|
|
|
1711
|
-
<script type="application/json" id="scan-data">${dataJson}</script>
|
|
1861
|
+
<script type="application/json" id="scan-data">${dataJson.replace(/<\//g, "<\\/")}</script>
|
|
1712
1862
|
</body>
|
|
1713
1863
|
</html>`;
|
|
1714
1864
|
}
|
|
@@ -1721,74 +1871,6 @@ var init_html_generator = __esm({
|
|
|
1721
1871
|
}
|
|
1722
1872
|
});
|
|
1723
1873
|
|
|
1724
|
-
// dist/check-update.js
|
|
1725
|
-
import chalk9 from "chalk";
|
|
1726
|
-
async function fetchLatest() {
|
|
1727
|
-
try {
|
|
1728
|
-
const controller = new AbortController();
|
|
1729
|
-
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
1730
|
-
const res = await fetch("https://registry.npmjs.org/md4ai/latest", {
|
|
1731
|
-
signal: controller.signal
|
|
1732
|
-
});
|
|
1733
|
-
clearTimeout(timeout);
|
|
1734
|
-
if (!res.ok)
|
|
1735
|
-
return null;
|
|
1736
|
-
const data = await res.json();
|
|
1737
|
-
return data.version ?? null;
|
|
1738
|
-
} catch {
|
|
1739
|
-
return null;
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
function printUpdateBanner(latest) {
|
|
1743
|
-
console.log("");
|
|
1744
|
-
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"));
|
|
1745
|
-
console.log(chalk9.yellow("\u2502") + chalk9.bold(" Update available! ") + chalk9.dim(`${CURRENT_VERSION}`) + chalk9.white(" \u2192 ") + chalk9.green.bold(`${latest}`) + " " + chalk9.yellow("\u2502"));
|
|
1746
|
-
console.log(chalk9.yellow("\u2502") + " " + chalk9.yellow("\u2502"));
|
|
1747
|
-
console.log(chalk9.yellow("\u2502") + " Run: " + chalk9.cyan("md4ai update") + " " + chalk9.yellow("\u2502"));
|
|
1748
|
-
console.log(chalk9.yellow("\u2502") + " " + chalk9.yellow("\u2502"));
|
|
1749
|
-
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"));
|
|
1750
|
-
console.log("");
|
|
1751
|
-
}
|
|
1752
|
-
async function checkForUpdate() {
|
|
1753
|
-
const latest = await fetchLatest();
|
|
1754
|
-
if (!latest)
|
|
1755
|
-
return;
|
|
1756
|
-
if (latest !== CURRENT_VERSION && isNewer(latest, CURRENT_VERSION)) {
|
|
1757
|
-
printUpdateBanner(latest);
|
|
1758
|
-
} else {
|
|
1759
|
-
console.log(chalk9.green(`md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.`));
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
async function autoCheckForUpdate() {
|
|
1763
|
-
try {
|
|
1764
|
-
const latest = await fetchLatest();
|
|
1765
|
-
if (!latest)
|
|
1766
|
-
return;
|
|
1767
|
-
if (latest !== CURRENT_VERSION && isNewer(latest, CURRENT_VERSION)) {
|
|
1768
|
-
printUpdateBanner(latest);
|
|
1769
|
-
}
|
|
1770
|
-
} catch {
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
function isNewer(a, b) {
|
|
1774
|
-
const pa = a.split(".").map(Number);
|
|
1775
|
-
const pb = b.split(".").map(Number);
|
|
1776
|
-
for (let i = 0; i < 3; i++) {
|
|
1777
|
-
if ((pa[i] ?? 0) > (pb[i] ?? 0))
|
|
1778
|
-
return true;
|
|
1779
|
-
if ((pa[i] ?? 0) < (pb[i] ?? 0))
|
|
1780
|
-
return false;
|
|
1781
|
-
}
|
|
1782
|
-
return false;
|
|
1783
|
-
}
|
|
1784
|
-
var CURRENT_VERSION;
|
|
1785
|
-
var init_check_update = __esm({
|
|
1786
|
-
"dist/check-update.js"() {
|
|
1787
|
-
"use strict";
|
|
1788
|
-
CURRENT_VERSION = true ? "0.10.2" : "0.0.0-dev";
|
|
1789
|
-
}
|
|
1790
|
-
});
|
|
1791
|
-
|
|
1792
1874
|
// dist/commands/push-toolings.js
|
|
1793
1875
|
async function pushToolings(supabase, folderId, toolings) {
|
|
1794
1876
|
if (!toolings.length)
|
|
@@ -1820,7 +1902,7 @@ async function pushHealthResults(supabase, folderId, manifest) {
|
|
|
1820
1902
|
if (!checks?.length)
|
|
1821
1903
|
return;
|
|
1822
1904
|
const results = [];
|
|
1823
|
-
const ranAt =
|
|
1905
|
+
const ranAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1824
1906
|
for (const chk of checks) {
|
|
1825
1907
|
const config2 = chk.check_config;
|
|
1826
1908
|
if (chk.check_type === "env_var_set") {
|
|
@@ -1885,6 +1967,22 @@ async function pushHealthResults(supabase, folderId, manifest) {
|
|
|
1885
1967
|
console.error(chalk10.yellow(`Health results warning: ${error.message}`));
|
|
1886
1968
|
} else {
|
|
1887
1969
|
console.log(chalk10.green(` Updated ${results.length} health check result(s).`));
|
|
1970
|
+
const checkIds = results.map((r) => r.check_id);
|
|
1971
|
+
const { data: allResults } = await supabase.from("env_health_results").select("id, check_id, ran_at").in("check_id", checkIds).order("ran_at", { ascending: false });
|
|
1972
|
+
if (allResults) {
|
|
1973
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1974
|
+
const toDelete = [];
|
|
1975
|
+
for (const r of allResults) {
|
|
1976
|
+
if (seen.has(r.check_id)) {
|
|
1977
|
+
toDelete.push(r.id);
|
|
1978
|
+
} else {
|
|
1979
|
+
seen.add(r.check_id);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
if (toDelete.length > 0) {
|
|
1983
|
+
await supabase.from("env_health_results").delete().in("id", toDelete);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1888
1986
|
}
|
|
1889
1987
|
}
|
|
1890
1988
|
}
|
|
@@ -1937,14 +2035,14 @@ var map_exports = {};
|
|
|
1937
2035
|
__export(map_exports, {
|
|
1938
2036
|
mapCommand: () => mapCommand
|
|
1939
2037
|
});
|
|
1940
|
-
import { resolve as
|
|
2038
|
+
import { resolve as resolve4, basename } from "node:path";
|
|
1941
2039
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
1942
2040
|
import { existsSync as existsSync7 } from "node:fs";
|
|
1943
2041
|
import chalk11 from "chalk";
|
|
1944
2042
|
import { select as select3, input as input5, confirm as confirm2 } from "@inquirer/prompts";
|
|
1945
2043
|
async function mapCommand(path, options) {
|
|
1946
2044
|
await checkForUpdate();
|
|
1947
|
-
const projectRoot =
|
|
2045
|
+
const projectRoot = resolve4(path ?? process.cwd());
|
|
1948
2046
|
if (!existsSync7(projectRoot)) {
|
|
1949
2047
|
console.error(chalk11.red(`Path not found: ${projectRoot}`));
|
|
1950
2048
|
process.exit(1);
|
|
@@ -1960,6 +2058,7 @@ async function mapCommand(path, options) {
|
|
|
1960
2058
|
console.log(` Skills: ${result.skills.length}`);
|
|
1961
2059
|
console.log(` Toolings: ${result.toolings.length}`);
|
|
1962
2060
|
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
2061
|
+
console.log(` Plugins: ${result.marketplacePlugins.length} (${result.marketplacePlugins.reduce((n, p) => n + p.skills.length, 0)} skills)`);
|
|
1963
2062
|
console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
|
|
1964
2063
|
if (result.brokenRefs.length > 0) {
|
|
1965
2064
|
console.log(chalk11.red(`
|
|
@@ -1971,11 +2070,11 @@ async function mapCommand(path, options) {
|
|
|
1971
2070
|
console.log(chalk11.red(` ... and ${result.brokenRefs.length - 5} more`));
|
|
1972
2071
|
}
|
|
1973
2072
|
}
|
|
1974
|
-
const outputDir =
|
|
2073
|
+
const outputDir = resolve4(projectRoot, "output");
|
|
1975
2074
|
if (!existsSync7(outputDir)) {
|
|
1976
2075
|
await mkdir2(outputDir, { recursive: true });
|
|
1977
2076
|
}
|
|
1978
|
-
const htmlPath =
|
|
2077
|
+
const htmlPath = resolve4(outputDir, "index.html");
|
|
1979
2078
|
const html = generateOfflineHtml(result, projectRoot);
|
|
1980
2079
|
await writeFile2(htmlPath, html, "utf-8");
|
|
1981
2080
|
console.log(chalk11.green(`
|
|
@@ -1999,15 +2098,20 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1999
2098
|
value: f
|
|
2000
2099
|
}))
|
|
2001
2100
|
});
|
|
2101
|
+
const resolvedRoot = resolve4(projectRoot);
|
|
2002
2102
|
for (const file of toDelete) {
|
|
2003
|
-
const fullPath =
|
|
2103
|
+
const fullPath = resolve4(projectRoot, file.file_path);
|
|
2104
|
+
if (!fullPath.startsWith(resolvedRoot + "/")) {
|
|
2105
|
+
console.error(chalk11.red(` Blocked path traversal: ${file.file_path}`));
|
|
2106
|
+
continue;
|
|
2107
|
+
}
|
|
2004
2108
|
try {
|
|
2005
2109
|
const { unlink } = await import("node:fs/promises");
|
|
2006
2110
|
await unlink(fullPath);
|
|
2007
2111
|
await supabase.from("folder_files").delete().eq("id", file.id);
|
|
2008
2112
|
console.log(chalk11.green(` Deleted: ${file.file_path}`));
|
|
2009
2113
|
} catch (err) {
|
|
2010
|
-
console.error(chalk11.red(` Failed to delete ${file.file_path}: ${err}`));
|
|
2114
|
+
console.error(chalk11.red(` Failed to delete ${file.file_path}: ${err instanceof Error ? err.message : String(err)}`));
|
|
2011
2115
|
}
|
|
2012
2116
|
}
|
|
2013
2117
|
const keptIds = proposedFiles.filter((f) => !toDelete.some((d) => d.id === f.id)).map((f) => f.id);
|
|
@@ -2021,22 +2125,34 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2021
2125
|
} else if (proposedFiles?.length) {
|
|
2022
2126
|
console.log(chalk11.dim(` ${proposedFiles.length} file(s) proposed for deletion \u2014 skipped (non-interactive mode).`));
|
|
2023
2127
|
}
|
|
2024
|
-
|
|
2128
|
+
let storedManifest = null;
|
|
2129
|
+
if (!result.envManifest) {
|
|
2130
|
+
const { data: stored, error: fetchErr } = await supabase.from("claude_folders").select("env_manifest_json").eq("id", folder_id).single();
|
|
2131
|
+
if (!fetchErr && stored?.env_manifest_json) {
|
|
2132
|
+
storedManifest = stored.env_manifest_json;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
const updatePayload = {
|
|
2025
2136
|
graph_json: result.graph,
|
|
2026
2137
|
orphans_json: result.orphans,
|
|
2027
2138
|
broken_refs_json: result.brokenRefs,
|
|
2028
2139
|
skills_table_json: result.skills,
|
|
2029
2140
|
stale_files_json: result.staleFiles,
|
|
2030
|
-
|
|
2141
|
+
marketplace_plugins_json: result.marketplacePlugins,
|
|
2031
2142
|
last_scanned: result.scannedAt,
|
|
2032
2143
|
data_hash: result.dataHash
|
|
2033
|
-
}
|
|
2144
|
+
};
|
|
2145
|
+
if (result.envManifest) {
|
|
2146
|
+
updatePayload.env_manifest_json = result.envManifest;
|
|
2147
|
+
}
|
|
2148
|
+
const { error } = await supabase.from("claude_folders").update(updatePayload).eq("id", folder_id);
|
|
2034
2149
|
if (error) {
|
|
2035
2150
|
console.error(chalk11.yellow(`Sync warning: ${error.message}`));
|
|
2036
2151
|
} else {
|
|
2037
2152
|
await pushToolings(supabase, folder_id, result.toolings);
|
|
2038
|
-
|
|
2039
|
-
|
|
2153
|
+
const manifestForHealth = result.envManifest ?? storedManifest;
|
|
2154
|
+
if (manifestForHealth) {
|
|
2155
|
+
await pushHealthResults(supabase, folder_id, manifestForHealth);
|
|
2040
2156
|
}
|
|
2041
2157
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
|
|
2042
2158
|
await saveState({
|
|
@@ -2048,7 +2164,8 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2048
2164
|
console.log(chalk11.cyan(`
|
|
2049
2165
|
https://www.md4ai.com/project/${folder_id}
|
|
2050
2166
|
`));
|
|
2051
|
-
const
|
|
2167
|
+
const graphPaths = result.graph.nodes.map((n) => n.filePath);
|
|
2168
|
+
const configFiles = await readClaudeConfigFiles(projectRoot, graphPaths);
|
|
2052
2169
|
if (configFiles.length > 0) {
|
|
2053
2170
|
for (const file of configFiles) {
|
|
2054
2171
|
await supabase.from("folder_files").upsert({
|
|
@@ -2120,6 +2237,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2120
2237
|
skills_table_json: result.skills,
|
|
2121
2238
|
stale_files_json: result.staleFiles,
|
|
2122
2239
|
env_manifest_json: result.envManifest,
|
|
2240
|
+
marketplace_plugins_json: result.marketplacePlugins,
|
|
2123
2241
|
last_scanned: result.scannedAt,
|
|
2124
2242
|
data_hash: result.dataHash
|
|
2125
2243
|
}).eq("id", folderId);
|
|
@@ -2127,7 +2245,8 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2127
2245
|
if (result.envManifest) {
|
|
2128
2246
|
await pushHealthResults(sb, folderId, result.envManifest);
|
|
2129
2247
|
}
|
|
2130
|
-
const
|
|
2248
|
+
const graphPaths2 = result.graph.nodes.map((n) => n.filePath);
|
|
2249
|
+
const configFiles = await readClaudeConfigFiles(projectRoot, graphPaths2);
|
|
2131
2250
|
for (const file of configFiles) {
|
|
2132
2251
|
await sb.from("folder_files").upsert({
|
|
2133
2252
|
folder_id: folderId,
|
|
@@ -2172,7 +2291,13 @@ var sync_exports = {};
|
|
|
2172
2291
|
__export(sync_exports, {
|
|
2173
2292
|
syncCommand: () => syncCommand
|
|
2174
2293
|
});
|
|
2294
|
+
import { resolve as resolve5 } from "node:path";
|
|
2295
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
2175
2296
|
import chalk14 from "chalk";
|
|
2297
|
+
function isValidProjectPath(p) {
|
|
2298
|
+
const resolved = resolve5(p);
|
|
2299
|
+
return resolved.startsWith("/") && !resolved.includes("..") && existsSync10(resolved);
|
|
2300
|
+
}
|
|
2176
2301
|
async function syncCommand(options) {
|
|
2177
2302
|
const { supabase } = await getAuthenticatedClient();
|
|
2178
2303
|
if (options.all) {
|
|
@@ -2182,6 +2307,10 @@ async function syncCommand(options) {
|
|
|
2182
2307
|
process.exit(1);
|
|
2183
2308
|
}
|
|
2184
2309
|
for (const device of devices) {
|
|
2310
|
+
if (!isValidProjectPath(device.path)) {
|
|
2311
|
+
console.error(chalk14.red(` Skipping invalid path: ${device.path}`));
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2185
2314
|
console.log(chalk14.blue(`Syncing: ${device.path}`));
|
|
2186
2315
|
const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
2187
2316
|
if (proposedAll?.length) {
|
|
@@ -2219,6 +2348,10 @@ async function syncCommand(options) {
|
|
|
2219
2348
|
console.error(chalk14.red("Could not find last synced device/folder."));
|
|
2220
2349
|
process.exit(1);
|
|
2221
2350
|
}
|
|
2351
|
+
if (!isValidProjectPath(device.path)) {
|
|
2352
|
+
console.error(chalk14.red(`Invalid project path: ${device.path}`));
|
|
2353
|
+
process.exit(1);
|
|
2354
|
+
}
|
|
2222
2355
|
console.log(chalk14.blue(`Syncing: ${device.path}`));
|
|
2223
2356
|
const { data: proposedSingle } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
2224
2357
|
if (proposedSingle?.length) {
|
|
@@ -2251,16 +2384,16 @@ var init_sync = __esm({
|
|
|
2251
2384
|
});
|
|
2252
2385
|
|
|
2253
2386
|
// dist/mcp/read-configs.js
|
|
2254
|
-
import { readFile as
|
|
2387
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
2255
2388
|
import { join as join14 } from "node:path";
|
|
2256
|
-
import { homedir as
|
|
2257
|
-
import { existsSync as
|
|
2258
|
-
import { readdir as
|
|
2389
|
+
import { homedir as homedir9 } from "node:os";
|
|
2390
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
2391
|
+
import { readdir as readdir4 } from "node:fs/promises";
|
|
2259
2392
|
async function readJsonSafe(path) {
|
|
2260
2393
|
try {
|
|
2261
|
-
if (!
|
|
2394
|
+
if (!existsSync12(path))
|
|
2262
2395
|
return null;
|
|
2263
|
-
const raw = await
|
|
2396
|
+
const raw = await readFile11(path, "utf-8");
|
|
2264
2397
|
return JSON.parse(raw);
|
|
2265
2398
|
} catch {
|
|
2266
2399
|
return null;
|
|
@@ -2320,7 +2453,7 @@ function parseFlatConfig(data, source) {
|
|
|
2320
2453
|
return entries;
|
|
2321
2454
|
}
|
|
2322
2455
|
async function readAllMcpConfigs() {
|
|
2323
|
-
const home =
|
|
2456
|
+
const home = homedir9();
|
|
2324
2457
|
const entries = [];
|
|
2325
2458
|
const userConfig = await readJsonSafe(join14(home, ".claude.json"));
|
|
2326
2459
|
entries.push(...parseServers(userConfig, "global"));
|
|
@@ -2329,16 +2462,16 @@ async function readAllMcpConfigs() {
|
|
|
2329
2462
|
const cwdMcp = await readJsonSafe(join14(process.cwd(), ".mcp.json"));
|
|
2330
2463
|
entries.push(...parseServers(cwdMcp, "project"));
|
|
2331
2464
|
const pluginsBase = join14(home, ".claude", "plugins", "marketplaces");
|
|
2332
|
-
if (
|
|
2465
|
+
if (existsSync12(pluginsBase)) {
|
|
2333
2466
|
try {
|
|
2334
|
-
const marketplaces = await
|
|
2467
|
+
const marketplaces = await readdir4(pluginsBase, { withFileTypes: true });
|
|
2335
2468
|
for (const mp of marketplaces) {
|
|
2336
2469
|
if (!mp.isDirectory())
|
|
2337
2470
|
continue;
|
|
2338
2471
|
const extDir = join14(pluginsBase, mp.name, "external_plugins");
|
|
2339
|
-
if (!
|
|
2472
|
+
if (!existsSync12(extDir))
|
|
2340
2473
|
continue;
|
|
2341
|
-
const plugins = await
|
|
2474
|
+
const plugins = await readdir4(extDir, { withFileTypes: true });
|
|
2342
2475
|
for (const plugin of plugins) {
|
|
2343
2476
|
if (!plugin.isDirectory())
|
|
2344
2477
|
continue;
|
|
@@ -2350,18 +2483,18 @@ async function readAllMcpConfigs() {
|
|
|
2350
2483
|
}
|
|
2351
2484
|
}
|
|
2352
2485
|
const cacheBase = join14(home, ".claude", "plugins", "cache");
|
|
2353
|
-
if (
|
|
2486
|
+
if (existsSync12(cacheBase)) {
|
|
2354
2487
|
try {
|
|
2355
|
-
const registries = await
|
|
2488
|
+
const registries = await readdir4(cacheBase, { withFileTypes: true });
|
|
2356
2489
|
for (const reg of registries) {
|
|
2357
2490
|
if (!reg.isDirectory())
|
|
2358
2491
|
continue;
|
|
2359
2492
|
const regDir = join14(cacheBase, reg.name);
|
|
2360
|
-
const plugins = await
|
|
2493
|
+
const plugins = await readdir4(regDir, { withFileTypes: true });
|
|
2361
2494
|
for (const plugin of plugins) {
|
|
2362
2495
|
if (!plugin.isDirectory())
|
|
2363
2496
|
continue;
|
|
2364
|
-
const versionDirs = await
|
|
2497
|
+
const versionDirs = await readdir4(join14(regDir, plugin.name), { withFileTypes: true });
|
|
2365
2498
|
for (const ver of versionDirs) {
|
|
2366
2499
|
if (!ver.isDirectory())
|
|
2367
2500
|
continue;
|
|
@@ -2769,7 +2902,9 @@ async function mcpWatchCommand() {
|
|
|
2769
2902
|
console.log(chalk20.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
|
|
2770
2903
|
for (const w of existingWatchers) {
|
|
2771
2904
|
try {
|
|
2772
|
-
|
|
2905
|
+
if (typeof w.pid === "number" && w.pid > 1 && w.pid !== process.pid) {
|
|
2906
|
+
process.kill(w.pid, "SIGTERM");
|
|
2907
|
+
}
|
|
2773
2908
|
} catch {
|
|
2774
2909
|
}
|
|
2775
2910
|
}
|
|
@@ -2981,28 +3116,71 @@ var init_mcp_watch = __esm({
|
|
|
2981
3116
|
});
|
|
2982
3117
|
|
|
2983
3118
|
// dist/index.js
|
|
2984
|
-
init_login();
|
|
2985
3119
|
import { Command } from "commander";
|
|
2986
3120
|
|
|
3121
|
+
// dist/sentry.js
|
|
3122
|
+
init_check_update();
|
|
3123
|
+
import * as Sentry from "@sentry/node";
|
|
3124
|
+
var DSN = process.env.MD4AI_SENTRY_DSN;
|
|
3125
|
+
function initSentry() {
|
|
3126
|
+
if (!DSN)
|
|
3127
|
+
return;
|
|
3128
|
+
Sentry.init({
|
|
3129
|
+
dsn: DSN,
|
|
3130
|
+
release: `md4ai-cli@${CURRENT_VERSION}`,
|
|
3131
|
+
environment: process.env.NODE_ENV === "development" ? "development" : "production",
|
|
3132
|
+
tracesSampleRate: 0,
|
|
3133
|
+
beforeSend(event) {
|
|
3134
|
+
if (event.exception?.values) {
|
|
3135
|
+
for (const exc of event.exception.values) {
|
|
3136
|
+
if (exc.stacktrace?.frames) {
|
|
3137
|
+
for (const frame of exc.stacktrace.frames) {
|
|
3138
|
+
if (frame.filename) {
|
|
3139
|
+
const idx = frame.filename.indexOf("md4ai");
|
|
3140
|
+
if (idx >= 0)
|
|
3141
|
+
frame.filename = frame.filename.slice(idx);
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
return event;
|
|
3148
|
+
}
|
|
3149
|
+
});
|
|
3150
|
+
}
|
|
3151
|
+
function captureException2(err) {
|
|
3152
|
+
if (!DSN)
|
|
3153
|
+
return;
|
|
3154
|
+
Sentry.captureException(err);
|
|
3155
|
+
}
|
|
3156
|
+
async function flushSentry() {
|
|
3157
|
+
if (!DSN)
|
|
3158
|
+
return;
|
|
3159
|
+
await Sentry.flush(2e3);
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
// dist/index.js
|
|
3163
|
+
init_login();
|
|
3164
|
+
|
|
2987
3165
|
// dist/commands/logout.js
|
|
2988
3166
|
init_config();
|
|
2989
|
-
import
|
|
3167
|
+
import chalk3 from "chalk";
|
|
2990
3168
|
async function logoutCommand() {
|
|
2991
3169
|
await clearCredentials();
|
|
2992
|
-
console.log(
|
|
3170
|
+
console.log(chalk3.green("Logged out successfully."));
|
|
2993
3171
|
}
|
|
2994
3172
|
|
|
2995
3173
|
// dist/commands/status.js
|
|
2996
3174
|
init_dist();
|
|
2997
3175
|
init_config();
|
|
2998
|
-
import
|
|
3176
|
+
import chalk4 from "chalk";
|
|
2999
3177
|
async function statusCommand() {
|
|
3000
3178
|
const creds = await loadCredentials();
|
|
3001
3179
|
if (!creds?.accessToken) {
|
|
3002
|
-
console.log(
|
|
3180
|
+
console.log(chalk4.yellow("Not logged in. Run: md4ai login"));
|
|
3003
3181
|
return;
|
|
3004
3182
|
}
|
|
3005
|
-
console.log(
|
|
3183
|
+
console.log(chalk4.blue("MD4AI Status\n"));
|
|
3006
3184
|
console.log(` User: ${creds.email}`);
|
|
3007
3185
|
console.log(` Expires: ${new Date(creds.expiresAt).toLocaleString()}`);
|
|
3008
3186
|
try {
|
|
@@ -3015,13 +3193,13 @@ async function statusCommand() {
|
|
|
3015
3193
|
const state = await loadState();
|
|
3016
3194
|
console.log(` Last sync: ${state.lastSyncAt ?? "never"}`);
|
|
3017
3195
|
} catch {
|
|
3018
|
-
console.log(
|
|
3196
|
+
console.log(chalk4.yellow(" (Could not fetch remote data)"));
|
|
3019
3197
|
}
|
|
3020
3198
|
}
|
|
3021
3199
|
|
|
3022
3200
|
// dist/commands/add-folder.js
|
|
3023
3201
|
init_auth();
|
|
3024
|
-
import
|
|
3202
|
+
import chalk6 from "chalk";
|
|
3025
3203
|
import { input as input3, select } from "@inquirer/prompts";
|
|
3026
3204
|
async function addFolderCommand() {
|
|
3027
3205
|
const { supabase, userId } = await getAuthenticatedClient();
|
|
@@ -3060,11 +3238,11 @@ async function addFolderCommand() {
|
|
|
3060
3238
|
team_id: teamId
|
|
3061
3239
|
}).select().single();
|
|
3062
3240
|
if (error) {
|
|
3063
|
-
console.error(
|
|
3241
|
+
console.error(chalk6.red(`Failed to create folder: ${error.message}`));
|
|
3064
3242
|
process.exit(1);
|
|
3065
3243
|
}
|
|
3066
3244
|
const teamLabel = teamId ? `(team: ${allTeams.get(teamId)})` : "(personal)";
|
|
3067
|
-
console.log(
|
|
3245
|
+
console.log(chalk6.green(`
|
|
3068
3246
|
Folder "${name}" created ${teamLabel} (${data.id})`));
|
|
3069
3247
|
}
|
|
3070
3248
|
|
|
@@ -3072,7 +3250,7 @@ Folder "${name}" created ${teamLabel} (${data.id})`));
|
|
|
3072
3250
|
init_auth();
|
|
3073
3251
|
import { resolve } from "node:path";
|
|
3074
3252
|
import { hostname, platform } from "node:os";
|
|
3075
|
-
import
|
|
3253
|
+
import chalk7 from "chalk";
|
|
3076
3254
|
import { input as input4, select as select2 } from "@inquirer/prompts";
|
|
3077
3255
|
function detectOs() {
|
|
3078
3256
|
const p = platform();
|
|
@@ -3093,14 +3271,14 @@ function suggestDeviceName() {
|
|
|
3093
3271
|
async function addDeviceCommand() {
|
|
3094
3272
|
const { supabase, userId } = await getAuthenticatedClient();
|
|
3095
3273
|
const cwd = resolve(process.cwd());
|
|
3096
|
-
console.log(
|
|
3274
|
+
console.log(chalk7.blue.bold("\n\u{1F4C2} Where is this Claude project?\n"));
|
|
3097
3275
|
const localPath = await input4({
|
|
3098
3276
|
message: "Local project path:",
|
|
3099
3277
|
default: cwd
|
|
3100
3278
|
});
|
|
3101
3279
|
const { data: folders, error: foldersErr } = await supabase.from("claude_folders").select("id, name").order("name");
|
|
3102
3280
|
if (foldersErr || !folders?.length) {
|
|
3103
|
-
console.error(
|
|
3281
|
+
console.error(chalk7.red("No folders found. Run: md4ai add-folder"));
|
|
3104
3282
|
process.exit(1);
|
|
3105
3283
|
}
|
|
3106
3284
|
const folderId = await select2({
|
|
@@ -3133,16 +3311,16 @@ async function addDeviceCommand() {
|
|
|
3133
3311
|
description: description || null
|
|
3134
3312
|
});
|
|
3135
3313
|
if (error) {
|
|
3136
|
-
console.error(
|
|
3314
|
+
console.error(chalk7.red(`Failed to add device: ${error.message}`));
|
|
3137
3315
|
process.exit(1);
|
|
3138
3316
|
}
|
|
3139
|
-
console.log(
|
|
3317
|
+
console.log(chalk7.green(`
|
|
3140
3318
|
Device "${deviceName}" added to folder.`));
|
|
3141
3319
|
}
|
|
3142
3320
|
|
|
3143
3321
|
// dist/commands/list-devices.js
|
|
3144
3322
|
init_auth();
|
|
3145
|
-
import
|
|
3323
|
+
import chalk8 from "chalk";
|
|
3146
3324
|
async function listDevicesCommand() {
|
|
3147
3325
|
const { supabase } = await getAuthenticatedClient();
|
|
3148
3326
|
const { data: devices, error } = await supabase.from("device_paths").select(`
|
|
@@ -3150,11 +3328,11 @@ async function listDevicesCommand() {
|
|
|
3150
3328
|
claude_folders!inner ( name )
|
|
3151
3329
|
`).order("device_name");
|
|
3152
3330
|
if (error) {
|
|
3153
|
-
console.error(
|
|
3331
|
+
console.error(chalk8.red(`Failed to list devices: ${error.message}`));
|
|
3154
3332
|
process.exit(1);
|
|
3155
3333
|
}
|
|
3156
3334
|
if (!devices?.length) {
|
|
3157
|
-
console.log(
|
|
3335
|
+
console.log(chalk8.yellow("No devices found. Run: md4ai add-device"));
|
|
3158
3336
|
return;
|
|
3159
3337
|
}
|
|
3160
3338
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -3165,12 +3343,12 @@ async function listDevicesCommand() {
|
|
|
3165
3343
|
}
|
|
3166
3344
|
for (const [deviceName, entries] of grouped) {
|
|
3167
3345
|
const first = entries[0];
|
|
3168
|
-
console.log(
|
|
3169
|
-
${deviceName}`) +
|
|
3346
|
+
console.log(chalk8.bold(`
|
|
3347
|
+
${deviceName}`) + chalk8.dim(` (${first.os_type})`));
|
|
3170
3348
|
for (const entry of entries) {
|
|
3171
3349
|
const folderName = entry.claude_folders?.name ?? "unknown";
|
|
3172
3350
|
const synced = entry.last_synced ? new Date(entry.last_synced).toLocaleString() : "never";
|
|
3173
|
-
console.log(` ${
|
|
3351
|
+
console.log(` ${chalk8.cyan(folderName)} \u2192 ${entry.path}`);
|
|
3174
3352
|
console.log(` Last synced: ${synced}`);
|
|
3175
3353
|
}
|
|
3176
3354
|
}
|
|
@@ -3181,41 +3359,41 @@ init_map();
|
|
|
3181
3359
|
|
|
3182
3360
|
// dist/commands/simulate.js
|
|
3183
3361
|
init_dist();
|
|
3184
|
-
import { join as
|
|
3362
|
+
import { join as join12 } from "node:path";
|
|
3185
3363
|
import { existsSync as existsSync8 } from "node:fs";
|
|
3186
|
-
import { homedir as
|
|
3364
|
+
import { homedir as homedir8 } from "node:os";
|
|
3187
3365
|
import chalk12 from "chalk";
|
|
3188
3366
|
async function simulateCommand(prompt) {
|
|
3189
3367
|
const projectRoot = process.cwd();
|
|
3190
3368
|
console.log(chalk12.blue(`Simulating prompt: "${prompt}"
|
|
3191
3369
|
`));
|
|
3192
3370
|
console.log(chalk12.dim("Files Claude would load:\n"));
|
|
3193
|
-
const globalClaude =
|
|
3371
|
+
const globalClaude = join12(homedir8(), ".claude", "CLAUDE.md");
|
|
3194
3372
|
if (existsSync8(globalClaude)) {
|
|
3195
3373
|
console.log(chalk12.green(" \u2713 ~/.claude/CLAUDE.md (global)"));
|
|
3196
3374
|
}
|
|
3197
3375
|
for (const rootFile of ROOT_FILES) {
|
|
3198
|
-
const fullPath =
|
|
3376
|
+
const fullPath = join12(projectRoot, rootFile);
|
|
3199
3377
|
if (existsSync8(fullPath)) {
|
|
3200
3378
|
console.log(chalk12.green(` \u2713 ${rootFile} (project root)`));
|
|
3201
3379
|
}
|
|
3202
3380
|
}
|
|
3203
3381
|
const settingsFiles = [
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3382
|
+
join12(homedir8(), ".claude", "settings.json"),
|
|
3383
|
+
join12(homedir8(), ".claude", "settings.local.json"),
|
|
3384
|
+
join12(projectRoot, ".claude", "settings.json"),
|
|
3385
|
+
join12(projectRoot, ".claude", "settings.local.json")
|
|
3208
3386
|
];
|
|
3209
3387
|
for (const sf of settingsFiles) {
|
|
3210
3388
|
if (existsSync8(sf)) {
|
|
3211
|
-
const display = sf.startsWith(
|
|
3389
|
+
const display = sf.startsWith(homedir8()) ? sf.replace(homedir8(), "~") : sf.replace(projectRoot + "/", "");
|
|
3212
3390
|
console.log(chalk12.green(` \u2713 ${display} (settings)`));
|
|
3213
3391
|
}
|
|
3214
3392
|
}
|
|
3215
3393
|
const words = prompt.split(/\s+/);
|
|
3216
3394
|
for (const word of words) {
|
|
3217
3395
|
const cleaned = word.replace(/['"]/g, "");
|
|
3218
|
-
const candidatePath =
|
|
3396
|
+
const candidatePath = join12(projectRoot, cleaned);
|
|
3219
3397
|
if (existsSync8(candidatePath) && cleaned.includes("/")) {
|
|
3220
3398
|
console.log(chalk12.cyan(` \u2192 ${cleaned} (referenced in prompt)`));
|
|
3221
3399
|
}
|
|
@@ -3224,18 +3402,18 @@ async function simulateCommand(prompt) {
|
|
|
3224
3402
|
}
|
|
3225
3403
|
|
|
3226
3404
|
// dist/commands/print.js
|
|
3227
|
-
import { join as
|
|
3228
|
-
import { readFile as
|
|
3405
|
+
import { join as join13 } from "node:path";
|
|
3406
|
+
import { readFile as readFile9, writeFile as writeFile3 } from "node:fs/promises";
|
|
3229
3407
|
import { existsSync as existsSync9 } from "node:fs";
|
|
3230
3408
|
import chalk13 from "chalk";
|
|
3231
3409
|
async function printCommand(title) {
|
|
3232
3410
|
const projectRoot = process.cwd();
|
|
3233
|
-
const scanDataPath =
|
|
3411
|
+
const scanDataPath = join13(projectRoot, "output", "index.html");
|
|
3234
3412
|
if (!existsSync9(scanDataPath)) {
|
|
3235
3413
|
console.error(chalk13.red("No scan data found. Run: md4ai scan"));
|
|
3236
3414
|
process.exit(1);
|
|
3237
3415
|
}
|
|
3238
|
-
const html = await
|
|
3416
|
+
const html = await readFile9(scanDataPath, "utf-8");
|
|
3239
3417
|
const match = html.match(/<script type="application\/json" id="scan-data">([\s\S]*?)<\/script>/);
|
|
3240
3418
|
if (!match) {
|
|
3241
3419
|
console.error(chalk13.red("Could not extract scan data from output/index.html"));
|
|
@@ -3243,7 +3421,7 @@ async function printCommand(title) {
|
|
|
3243
3421
|
}
|
|
3244
3422
|
const result = JSON.parse(match[1]);
|
|
3245
3423
|
const printHtml = generatePrintHtml(result, title);
|
|
3246
|
-
const outputPath =
|
|
3424
|
+
const outputPath = join13(projectRoot, "output", `print-${Date.now()}.html`);
|
|
3247
3425
|
await writeFile3(outputPath, printHtml, "utf-8");
|
|
3248
3426
|
console.log(chalk13.green(`Print-ready wall sheet: ${outputPath}`));
|
|
3249
3427
|
}
|
|
@@ -3310,11 +3488,11 @@ init_config();
|
|
|
3310
3488
|
init_scanner();
|
|
3311
3489
|
init_push_toolings();
|
|
3312
3490
|
init_device_utils();
|
|
3313
|
-
import { resolve as
|
|
3491
|
+
import { resolve as resolve6 } from "node:path";
|
|
3314
3492
|
import chalk15 from "chalk";
|
|
3315
3493
|
async function linkCommand(projectId) {
|
|
3316
3494
|
const { supabase, userId } = await getAuthenticatedClient();
|
|
3317
|
-
const cwd =
|
|
3495
|
+
const cwd = resolve6(process.cwd());
|
|
3318
3496
|
const deviceName = detectDeviceName();
|
|
3319
3497
|
const osType = detectOs2();
|
|
3320
3498
|
const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
|
|
@@ -3360,6 +3538,7 @@ Linking "${folder.name}" to this device...
|
|
|
3360
3538
|
console.log(` Skills: ${result.skills.length}`);
|
|
3361
3539
|
console.log(` Toolings: ${result.toolings.length}`);
|
|
3362
3540
|
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
3541
|
+
console.log(` Plugins: ${result.marketplacePlugins.length} (${result.marketplacePlugins.reduce((n, p) => n + p.skills.length, 0)} skills)`);
|
|
3363
3542
|
const { error: scanErr } = await supabase.from("claude_folders").update({
|
|
3364
3543
|
graph_json: result.graph,
|
|
3365
3544
|
orphans_json: result.orphans,
|
|
@@ -3367,6 +3546,7 @@ Linking "${folder.name}" to this device...
|
|
|
3367
3546
|
skills_table_json: result.skills,
|
|
3368
3547
|
stale_files_json: result.staleFiles,
|
|
3369
3548
|
env_manifest_json: result.envManifest,
|
|
3549
|
+
marketplace_plugins_json: result.marketplacePlugins,
|
|
3370
3550
|
last_scanned: result.scannedAt,
|
|
3371
3551
|
data_hash: result.dataHash
|
|
3372
3552
|
}).eq("id", folder.id);
|
|
@@ -3374,7 +3554,8 @@ Linking "${folder.name}" to this device...
|
|
|
3374
3554
|
console.error(chalk15.yellow(`Scan upload warning: ${scanErr.message}`));
|
|
3375
3555
|
}
|
|
3376
3556
|
await pushToolings(supabase, folder.id, result.toolings);
|
|
3377
|
-
const
|
|
3557
|
+
const graphPaths = result.graph.nodes.map((n) => n.filePath);
|
|
3558
|
+
const configFiles = await readClaudeConfigFiles(cwd, graphPaths);
|
|
3378
3559
|
if (configFiles.length > 0) {
|
|
3379
3560
|
for (const file of configFiles) {
|
|
3380
3561
|
await supabase.from("folder_files").upsert({
|
|
@@ -3402,18 +3583,24 @@ Linking "${folder.name}" to this device...
|
|
|
3402
3583
|
}
|
|
3403
3584
|
|
|
3404
3585
|
// dist/commands/import-bundle.js
|
|
3405
|
-
import { readFile as
|
|
3406
|
-
import {
|
|
3407
|
-
import { existsSync as
|
|
3586
|
+
import { readFile as readFile10, writeFile as writeFile4, mkdir as mkdir3, stat as fsStat } from "node:fs/promises";
|
|
3587
|
+
import { dirname as dirname3, resolve as resolve7 } from "node:path";
|
|
3588
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
3408
3589
|
import chalk16 from "chalk";
|
|
3409
3590
|
import { confirm as confirm3, input as input6 } from "@inquirer/prompts";
|
|
3591
|
+
var MAX_BUNDLE_SIZE_BYTES = 50 * 1024 * 1024;
|
|
3410
3592
|
async function importBundleCommand(zipPath) {
|
|
3411
|
-
if (!
|
|
3593
|
+
if (!existsSync11(zipPath)) {
|
|
3412
3594
|
console.error(chalk16.red(`File not found: ${zipPath}`));
|
|
3413
3595
|
process.exit(1);
|
|
3414
3596
|
}
|
|
3597
|
+
const fileStat = await fsStat(zipPath);
|
|
3598
|
+
if (fileStat.size > MAX_BUNDLE_SIZE_BYTES) {
|
|
3599
|
+
console.error(chalk16.red(`Bundle too large (${Math.round(fileStat.size / 1024 / 1024)} MB). Maximum is 50 MB.`));
|
|
3600
|
+
process.exit(1);
|
|
3601
|
+
}
|
|
3415
3602
|
const JSZip = (await import("jszip")).default;
|
|
3416
|
-
const zipData = await
|
|
3603
|
+
const zipData = await readFile10(zipPath);
|
|
3417
3604
|
const zip = await JSZip.loadAsync(zipData);
|
|
3418
3605
|
const manifestFile = zip.file("manifest.json");
|
|
3419
3606
|
if (!manifestFile) {
|
|
@@ -3454,10 +3641,15 @@ Files to extract:`));
|
|
|
3454
3641
|
console.log(chalk16.yellow("Cancelled."));
|
|
3455
3642
|
return;
|
|
3456
3643
|
}
|
|
3644
|
+
const resolvedTarget = resolve7(targetDir);
|
|
3457
3645
|
for (const file of files) {
|
|
3458
|
-
const fullPath =
|
|
3646
|
+
const fullPath = resolve7(targetDir, file.filePath);
|
|
3647
|
+
if (!fullPath.startsWith(resolvedTarget + "/") && fullPath !== resolvedTarget) {
|
|
3648
|
+
console.error(chalk16.red(` Blocked path traversal: ${file.filePath}`));
|
|
3649
|
+
continue;
|
|
3650
|
+
}
|
|
3459
3651
|
const dir = dirname3(fullPath);
|
|
3460
|
-
if (!
|
|
3652
|
+
if (!existsSync11(dir)) {
|
|
3461
3653
|
await mkdir3(dir, { recursive: true });
|
|
3462
3654
|
}
|
|
3463
3655
|
await writeFile4(fullPath, file.content, "utf-8");
|
|
@@ -3762,9 +3954,9 @@ async function fetchGitHubVersions(repo, signal) {
|
|
|
3762
3954
|
init_mcp_watch();
|
|
3763
3955
|
|
|
3764
3956
|
// dist/commands/init-manifest.js
|
|
3765
|
-
import { resolve as
|
|
3766
|
-
import { readFile as
|
|
3767
|
-
import { existsSync as
|
|
3957
|
+
import { resolve as resolve8, join as join15, relative as relative4, dirname as dirname4 } from "node:path";
|
|
3958
|
+
import { readFile as readFile12, writeFile as writeFile5, mkdir as mkdir4 } from "node:fs/promises";
|
|
3959
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
3768
3960
|
import chalk21 from "chalk";
|
|
3769
3961
|
var SECRET_PATTERNS = [
|
|
3770
3962
|
/_KEY$/i,
|
|
@@ -3782,7 +3974,7 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
3782
3974
|
const apps = [];
|
|
3783
3975
|
for (const envName of [".env", ".env.local", ".env.example"]) {
|
|
3784
3976
|
const envPath = join15(projectRoot, envName);
|
|
3785
|
-
if (
|
|
3977
|
+
if (existsSync13(envPath)) {
|
|
3786
3978
|
const vars = await extractVarNames(envPath);
|
|
3787
3979
|
if (vars.length > 0) {
|
|
3788
3980
|
apps.push({ name: "root", envFilePath: envName, vars });
|
|
@@ -3793,11 +3985,11 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
3793
3985
|
const subdirs = ["web", "cli", "api", "app", "server", "packages"];
|
|
3794
3986
|
for (const sub of subdirs) {
|
|
3795
3987
|
const subDir = join15(projectRoot, sub);
|
|
3796
|
-
if (!
|
|
3988
|
+
if (!existsSync13(subDir))
|
|
3797
3989
|
continue;
|
|
3798
3990
|
for (const envName of [".env.local", ".env", ".env.example"]) {
|
|
3799
3991
|
const envPath = join15(subDir, envName);
|
|
3800
|
-
if (
|
|
3992
|
+
if (existsSync13(envPath)) {
|
|
3801
3993
|
const vars = await extractVarNames(envPath);
|
|
3802
3994
|
if (vars.length > 0) {
|
|
3803
3995
|
apps.push({
|
|
@@ -3813,7 +4005,7 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
3813
4005
|
return apps;
|
|
3814
4006
|
}
|
|
3815
4007
|
async function extractVarNames(envPath) {
|
|
3816
|
-
const content = await
|
|
4008
|
+
const content = await readFile12(envPath, "utf-8");
|
|
3817
4009
|
const vars = [];
|
|
3818
4010
|
for (const line of content.split("\n")) {
|
|
3819
4011
|
const trimmed = line.trim();
|
|
@@ -3857,9 +4049,9 @@ function generateManifest(apps) {
|
|
|
3857
4049
|
return lines.join("\n");
|
|
3858
4050
|
}
|
|
3859
4051
|
async function initManifestCommand() {
|
|
3860
|
-
const projectRoot =
|
|
4052
|
+
const projectRoot = resolve8(process.cwd());
|
|
3861
4053
|
const manifestPath = join15(projectRoot, "docs", "reference", "env-manifest.md");
|
|
3862
|
-
if (
|
|
4054
|
+
if (existsSync13(manifestPath)) {
|
|
3863
4055
|
console.log(chalk21.yellow(`Manifest already exists: ${relative4(projectRoot, manifestPath)}`));
|
|
3864
4056
|
console.log(chalk21.dim("Edit it directly to make changes."));
|
|
3865
4057
|
return;
|
|
@@ -3876,7 +4068,7 @@ async function initManifestCommand() {
|
|
|
3876
4068
|
}
|
|
3877
4069
|
const content = generateManifest(apps);
|
|
3878
4070
|
const dir = dirname4(manifestPath);
|
|
3879
|
-
if (!
|
|
4071
|
+
if (!existsSync13(dir)) {
|
|
3880
4072
|
await mkdir4(dir, { recursive: true });
|
|
3881
4073
|
}
|
|
3882
4074
|
await writeFile5(manifestPath, content, "utf-8");
|
|
@@ -4036,7 +4228,7 @@ init_check_update();
|
|
|
4036
4228
|
init_map();
|
|
4037
4229
|
init_mcp_watch();
|
|
4038
4230
|
import chalk23 from "chalk";
|
|
4039
|
-
import { resolve as
|
|
4231
|
+
import { resolve as resolve9 } from "node:path";
|
|
4040
4232
|
async function fetchLatestVersion2() {
|
|
4041
4233
|
try {
|
|
4042
4234
|
const controller = new AbortController();
|
|
@@ -4065,7 +4257,7 @@ function isNewer3(a, b) {
|
|
|
4065
4257
|
return false;
|
|
4066
4258
|
}
|
|
4067
4259
|
async function startCommand() {
|
|
4068
|
-
const projectRoot =
|
|
4260
|
+
const projectRoot = resolve9(process.cwd());
|
|
4069
4261
|
console.log("");
|
|
4070
4262
|
console.log(chalk23.bold.cyan(` MD4AI v${CURRENT_VERSION}`));
|
|
4071
4263
|
console.log(chalk23.dim(` ${projectRoot}`));
|
|
@@ -4169,6 +4361,7 @@ var admin = program.command("admin").description("Admin commands for managing th
|
|
|
4169
4361
|
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);
|
|
4170
4362
|
admin.command("list-tools").description("List all tools in the master registry").action(adminListToolsCommand);
|
|
4171
4363
|
admin.command("fetch-versions").description("Fetch latest stable and beta versions from npm and GitHub").action(adminFetchVersionsCommand);
|
|
4364
|
+
initSentry();
|
|
4172
4365
|
if (process.argv.length <= 2) {
|
|
4173
4366
|
void startCommand();
|
|
4174
4367
|
} else {
|
|
@@ -4179,3 +4372,11 @@ if (process.argv.length <= 2) {
|
|
|
4179
4372
|
autoCheckForUpdate();
|
|
4180
4373
|
}
|
|
4181
4374
|
}
|
|
4375
|
+
process.on("uncaughtException", async (err) => {
|
|
4376
|
+
captureException2(err);
|
|
4377
|
+
await flushSentry();
|
|
4378
|
+
process.exit(1);
|
|
4379
|
+
});
|
|
4380
|
+
process.on("unhandledRejection", (reason) => {
|
|
4381
|
+
captureException2(reason);
|
|
4382
|
+
});
|