md4ai 0.17.3 → 0.18.0
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 +262 -268
- package/package.json +1 -1
package/dist/index.bundled.js
CHANGED
|
@@ -49,16 +49,6 @@ function printUpdateBanner(latest) {
|
|
|
49
49
|
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"));
|
|
50
50
|
console.log("");
|
|
51
51
|
}
|
|
52
|
-
async function checkForUpdate() {
|
|
53
|
-
const latest = await fetchLatest();
|
|
54
|
-
if (!latest)
|
|
55
|
-
return;
|
|
56
|
-
if (latest !== CURRENT_VERSION && isNewer(latest, CURRENT_VERSION)) {
|
|
57
|
-
printUpdateBanner(latest);
|
|
58
|
-
} else {
|
|
59
|
-
console.log(chalk.green(`md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.`));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
52
|
async function autoCheckForUpdate() {
|
|
63
53
|
try {
|
|
64
54
|
const latest = await fetchLatest();
|
|
@@ -122,7 +112,7 @@ var CURRENT_VERSION;
|
|
|
122
112
|
var init_check_update = __esm({
|
|
123
113
|
"dist/check-update.js"() {
|
|
124
114
|
"use strict";
|
|
125
|
-
CURRENT_VERSION = true ? "0.
|
|
115
|
+
CURRENT_VERSION = true ? "0.18.0" : "0.0.0-dev";
|
|
126
116
|
}
|
|
127
117
|
});
|
|
128
118
|
|
|
@@ -2809,139 +2799,15 @@ var init_map = __esm({
|
|
|
2809
2799
|
}
|
|
2810
2800
|
});
|
|
2811
2801
|
|
|
2812
|
-
// dist/commands/sync.js
|
|
2813
|
-
var sync_exports = {};
|
|
2814
|
-
__export(sync_exports, {
|
|
2815
|
-
syncCommand: () => syncCommand
|
|
2816
|
-
});
|
|
2817
|
-
import { resolve as resolve5 } from "node:path";
|
|
2818
|
-
import { existsSync as existsSync11 } from "node:fs";
|
|
2819
|
-
import chalk15 from "chalk";
|
|
2820
|
-
function isValidProjectPath(p) {
|
|
2821
|
-
const resolved = resolve5(p);
|
|
2822
|
-
return resolved.startsWith("/") && !resolved.includes("..") && existsSync11(resolved);
|
|
2823
|
-
}
|
|
2824
|
-
async function syncCommand(options) {
|
|
2825
|
-
const { supabase, userId } = await getAuthenticatedClient();
|
|
2826
|
-
if (options.all) {
|
|
2827
|
-
const currentDeviceName = detectDeviceName();
|
|
2828
|
-
const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path").eq("device_name", currentDeviceName);
|
|
2829
|
-
if (error || !devices?.length) {
|
|
2830
|
-
console.log(chalk15.yellow(`
|
|
2831
|
-
No projects linked on ${currentDeviceName} yet.
|
|
2832
|
-
`));
|
|
2833
|
-
console.log(chalk15.dim(" To link a project, cd into its folder and run:"));
|
|
2834
|
-
console.log(chalk15.cyan(" md4ai scan"));
|
|
2835
|
-
console.log(chalk15.dim(" Or link an existing project from the dashboard:"));
|
|
2836
|
-
console.log(chalk15.cyan(" md4ai link <project-id>\n"));
|
|
2837
|
-
return;
|
|
2838
|
-
}
|
|
2839
|
-
for (const device of devices) {
|
|
2840
|
-
if (!isValidProjectPath(device.path)) {
|
|
2841
|
-
console.error(chalk15.red(` Skipping invalid path: ${device.path}`));
|
|
2842
|
-
continue;
|
|
2843
|
-
}
|
|
2844
|
-
console.log(chalk15.blue(`Syncing: ${device.path}`));
|
|
2845
|
-
const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
2846
|
-
if (proposedAll?.length) {
|
|
2847
|
-
console.log(chalk15.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
2848
|
-
}
|
|
2849
|
-
try {
|
|
2850
|
-
const result = await scanProject(device.path, device.folder_id);
|
|
2851
|
-
const { data: allDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
|
|
2852
|
-
const allDeviceId = allDeviceRow?.id;
|
|
2853
|
-
if (allDeviceId) {
|
|
2854
|
-
await supabase.from("device_scans").upsert({
|
|
2855
|
-
folder_id: device.folder_id,
|
|
2856
|
-
device_id: allDeviceId,
|
|
2857
|
-
user_id: userId,
|
|
2858
|
-
graph_json: result.graph,
|
|
2859
|
-
orphans_json: result.orphans,
|
|
2860
|
-
skills_table_json: result.skills,
|
|
2861
|
-
stale_files_json: result.staleFiles,
|
|
2862
|
-
broken_refs_json: result.brokenRefs,
|
|
2863
|
-
env_manifest_json: result.envManifest,
|
|
2864
|
-
plugin_versions_json: result.pluginVersions,
|
|
2865
|
-
data_hash: result.dataHash,
|
|
2866
|
-
scanned_at: result.scannedAt,
|
|
2867
|
-
cli_version: CURRENT_VERSION
|
|
2868
|
-
}, { onConflict: "folder_id,device_id" });
|
|
2869
|
-
}
|
|
2870
|
-
await pushToolings(supabase, device.folder_id, result.toolings, allDeviceId);
|
|
2871
|
-
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
2872
|
-
console.log(chalk15.green(` Done: ${device.device_name}`));
|
|
2873
|
-
} catch (err) {
|
|
2874
|
-
console.error(chalk15.red(` Failed: ${device.path}: ${err}`));
|
|
2875
|
-
}
|
|
2876
|
-
}
|
|
2877
|
-
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2878
|
-
console.log(chalk15.green("\nAll devices synced."));
|
|
2879
|
-
} else {
|
|
2880
|
-
const state = await loadState();
|
|
2881
|
-
if (!state.lastFolderId) {
|
|
2882
|
-
console.error(chalk15.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
|
|
2883
|
-
process.exit(1);
|
|
2884
|
-
}
|
|
2885
|
-
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();
|
|
2886
|
-
if (!device) {
|
|
2887
|
-
console.error(chalk15.red("Could not find last synced device/folder."));
|
|
2888
|
-
process.exit(1);
|
|
2889
|
-
}
|
|
2890
|
-
if (!isValidProjectPath(device.path)) {
|
|
2891
|
-
console.error(chalk15.red(`Invalid project path: ${device.path}`));
|
|
2892
|
-
process.exit(1);
|
|
2893
|
-
}
|
|
2894
|
-
console.log(chalk15.blue(`Syncing: ${device.path}`));
|
|
2895
|
-
const { data: proposedSingle } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
2896
|
-
if (proposedSingle?.length) {
|
|
2897
|
-
console.log(chalk15.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
2898
|
-
}
|
|
2899
|
-
const result = await scanProject(device.path, device.folder_id);
|
|
2900
|
-
const { data: singleDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
|
|
2901
|
-
const singleDeviceId = singleDeviceRow?.id;
|
|
2902
|
-
if (singleDeviceId) {
|
|
2903
|
-
await supabase.from("device_scans").upsert({
|
|
2904
|
-
folder_id: device.folder_id,
|
|
2905
|
-
device_id: singleDeviceId,
|
|
2906
|
-
user_id: userId,
|
|
2907
|
-
graph_json: result.graph,
|
|
2908
|
-
orphans_json: result.orphans,
|
|
2909
|
-
skills_table_json: result.skills,
|
|
2910
|
-
stale_files_json: result.staleFiles,
|
|
2911
|
-
broken_refs_json: result.brokenRefs,
|
|
2912
|
-
plugin_versions_json: result.pluginVersions,
|
|
2913
|
-
data_hash: result.dataHash,
|
|
2914
|
-
scanned_at: result.scannedAt,
|
|
2915
|
-
cli_version: CURRENT_VERSION
|
|
2916
|
-
}, { onConflict: "folder_id,device_id" });
|
|
2917
|
-
}
|
|
2918
|
-
await pushToolings(supabase, device.folder_id, result.toolings, singleDeviceId);
|
|
2919
|
-
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
2920
|
-
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2921
|
-
console.log(chalk15.green("Synced."));
|
|
2922
|
-
}
|
|
2923
|
-
}
|
|
2924
|
-
var init_sync = __esm({
|
|
2925
|
-
"dist/commands/sync.js"() {
|
|
2926
|
-
"use strict";
|
|
2927
|
-
init_auth();
|
|
2928
|
-
init_config();
|
|
2929
|
-
init_scanner();
|
|
2930
|
-
init_push_toolings();
|
|
2931
|
-
init_check_update();
|
|
2932
|
-
init_device_utils();
|
|
2933
|
-
}
|
|
2934
|
-
});
|
|
2935
|
-
|
|
2936
2802
|
// dist/mcp/read-configs.js
|
|
2937
2803
|
import { readFile as readFile12 } from "node:fs/promises";
|
|
2938
2804
|
import { join as join15 } from "node:path";
|
|
2939
2805
|
import { homedir as homedir9 } from "node:os";
|
|
2940
|
-
import { existsSync as
|
|
2806
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
2941
2807
|
import { readdir as readdir6 } from "node:fs/promises";
|
|
2942
2808
|
async function readJsonSafe(path) {
|
|
2943
2809
|
try {
|
|
2944
|
-
if (!
|
|
2810
|
+
if (!existsSync12(path))
|
|
2945
2811
|
return null;
|
|
2946
2812
|
const raw = await readFile12(path, "utf-8");
|
|
2947
2813
|
return JSON.parse(raw);
|
|
@@ -3012,14 +2878,14 @@ async function readAllMcpConfigs() {
|
|
|
3012
2878
|
const cwdMcp = await readJsonSafe(join15(process.cwd(), ".mcp.json"));
|
|
3013
2879
|
entries.push(...parseServers(cwdMcp, "project"));
|
|
3014
2880
|
const pluginsBase = join15(home, ".claude", "plugins", "marketplaces");
|
|
3015
|
-
if (
|
|
2881
|
+
if (existsSync12(pluginsBase)) {
|
|
3016
2882
|
try {
|
|
3017
2883
|
const marketplaces = await readdir6(pluginsBase, { withFileTypes: true });
|
|
3018
2884
|
for (const mp of marketplaces) {
|
|
3019
2885
|
if (!mp.isDirectory())
|
|
3020
2886
|
continue;
|
|
3021
2887
|
const extDir = join15(pluginsBase, mp.name, "external_plugins");
|
|
3022
|
-
if (!
|
|
2888
|
+
if (!existsSync12(extDir))
|
|
3023
2889
|
continue;
|
|
3024
2890
|
const plugins = await readdir6(extDir, { withFileTypes: true });
|
|
3025
2891
|
for (const plugin of plugins) {
|
|
@@ -3033,7 +2899,7 @@ async function readAllMcpConfigs() {
|
|
|
3033
2899
|
}
|
|
3034
2900
|
}
|
|
3035
2901
|
const cacheBase = join15(home, ".claude", "plugins", "cache");
|
|
3036
|
-
if (
|
|
2902
|
+
if (existsSync12(cacheBase)) {
|
|
3037
2903
|
try {
|
|
3038
2904
|
const registries = await readdir6(cacheBase, { withFileTypes: true });
|
|
3039
2905
|
for (const reg of registries) {
|
|
@@ -3198,7 +3064,7 @@ var mcp_watch_exports = {};
|
|
|
3198
3064
|
__export(mcp_watch_exports, {
|
|
3199
3065
|
mcpWatchCommand: () => mcpWatchCommand
|
|
3200
3066
|
});
|
|
3201
|
-
import
|
|
3067
|
+
import chalk20 from "chalk";
|
|
3202
3068
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
3203
3069
|
import { createHash as createHash2 } from "node:crypto";
|
|
3204
3070
|
function detectTty() {
|
|
@@ -3352,20 +3218,20 @@ function buildRows(configs, httpResults, cdpStatus) {
|
|
|
3352
3218
|
}
|
|
3353
3219
|
function printTable(rows, deviceName, watcherPid, cdpResult) {
|
|
3354
3220
|
process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
|
|
3355
|
-
console.log(
|
|
3356
|
-
MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) +
|
|
3357
|
-
console.log(
|
|
3221
|
+
console.log(chalk20.bold.cyan(`
|
|
3222
|
+
MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) + chalk20.dim(` (PID ${watcherPid})`));
|
|
3223
|
+
console.log(chalk20.dim(` ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \xB7 refreshes every 30s \xB7 Ctrl+C to stop
|
|
3358
3224
|
`));
|
|
3359
3225
|
if (cdpResult) {
|
|
3360
3226
|
if (cdpResult.status === "reachable") {
|
|
3361
3227
|
const browserInfo = cdpResult.browser ? ` (${cdpResult.browser})` : "";
|
|
3362
|
-
console.log(
|
|
3363
|
-
console.log(
|
|
3228
|
+
console.log(chalk20.green(" \u2714 Chrome browser connected") + chalk20.dim(browserInfo));
|
|
3229
|
+
console.log(chalk20.dim(" Claude Code can control Chrome via DevTools Protocol on localhost:9222"));
|
|
3364
3230
|
} else {
|
|
3365
|
-
console.log(
|
|
3366
|
-
console.log(
|
|
3367
|
-
console.log(
|
|
3368
|
-
console.log(
|
|
3231
|
+
console.log(chalk20.red(" \u2717 Chrome browser not connected"));
|
|
3232
|
+
console.log(chalk20.dim(" Claude Code cannot reach Chrome. To fix:"));
|
|
3233
|
+
console.log(chalk20.dim(" 1. Open Chrome with: google-chrome --remote-debugging-port=9222"));
|
|
3234
|
+
console.log(chalk20.dim(" 2. Or add --remote-debugging-port=9222 to your Chrome shortcut"));
|
|
3369
3235
|
}
|
|
3370
3236
|
console.log("");
|
|
3371
3237
|
}
|
|
@@ -3386,41 +3252,41 @@ function printTable(rows, deviceName, watcherPid, cdpResult) {
|
|
|
3386
3252
|
sessionNum++;
|
|
3387
3253
|
const totalMem = servers.reduce((sum, s) => sum + (s.memory_mb ?? 0), 0);
|
|
3388
3254
|
const label = byTty.size === 1 ? "Claude Code session" : `Claude Code session ${sessionNum}`;
|
|
3389
|
-
console.log(
|
|
3255
|
+
console.log(chalk20.green(` ${label}`) + chalk20.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB \xB7 terminal ${tty}`));
|
|
3390
3256
|
for (const s of servers) {
|
|
3391
3257
|
const uptime = formatUptime(s.uptime_seconds ?? 0);
|
|
3392
|
-
const source =
|
|
3393
|
-
console.log(` ${
|
|
3258
|
+
const source = chalk20.dim(`[${s.config_source}]`);
|
|
3259
|
+
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}`);
|
|
3394
3260
|
}
|
|
3395
3261
|
console.log("");
|
|
3396
3262
|
}
|
|
3397
3263
|
if (byTty.size > 1) {
|
|
3398
|
-
console.log(
|
|
3264
|
+
console.log(chalk20.dim(" Each open Claude Code window runs its own set of MCP servers.\n"));
|
|
3399
3265
|
}
|
|
3400
3266
|
}
|
|
3401
3267
|
if (runningHttp.length > 0) {
|
|
3402
|
-
console.log(
|
|
3268
|
+
console.log(chalk20.blue(` Remote Services (${runningHttp.length})`) + chalk20.dim(" \u2014 HTTP endpoints reachable"));
|
|
3403
3269
|
for (const s of runningHttp) {
|
|
3404
|
-
console.log(` ${
|
|
3270
|
+
console.log(` ${chalk20.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk20.dim((s.http_url ?? "").padEnd(30))}`);
|
|
3405
3271
|
}
|
|
3406
3272
|
console.log("");
|
|
3407
3273
|
}
|
|
3408
3274
|
if (stopped.length > 0 || errored.length > 0) {
|
|
3409
3275
|
const notRunning = [...stopped, ...errored];
|
|
3410
|
-
console.log(
|
|
3276
|
+
console.log(chalk20.yellow(` Not Running (${notRunning.length})`) + chalk20.dim(" \u2014 configured but no process detected"));
|
|
3411
3277
|
for (const s of notRunning) {
|
|
3412
|
-
const icon = s.status === "error" ?
|
|
3413
|
-
const source =
|
|
3414
|
-
const detail = s.error_detail ?
|
|
3278
|
+
const icon = s.status === "error" ? chalk20.red("\u2717") : chalk20.yellow("\u25CB");
|
|
3279
|
+
const source = chalk20.dim(`[${s.config_source}]`);
|
|
3280
|
+
const detail = s.error_detail ? chalk20.dim(` \u2014 ${s.error_detail}`) : "";
|
|
3415
3281
|
console.log(` ${icon} ${s.server_name.padEnd(20)} ${source}${detail}`);
|
|
3416
3282
|
}
|
|
3417
3283
|
console.log("");
|
|
3418
3284
|
}
|
|
3419
3285
|
if (rows.length === 0) {
|
|
3420
|
-
console.log(
|
|
3421
|
-
console.log(
|
|
3286
|
+
console.log(chalk20.yellow(" No MCP servers configured."));
|
|
3287
|
+
console.log(chalk20.dim(" Configure servers in ~/.claude/mcp.json or .mcp.json\n"));
|
|
3422
3288
|
}
|
|
3423
|
-
console.log(
|
|
3289
|
+
console.log(chalk20.bgYellow.black.bold(" \u26A0 DO NOT CLOSE THIS WINDOW \u2014 it feeds live data to the dashboard \u26A0 "));
|
|
3424
3290
|
console.log("");
|
|
3425
3291
|
}
|
|
3426
3292
|
function formatUptime(seconds) {
|
|
@@ -3450,7 +3316,7 @@ async function mcpWatchCommand() {
|
|
|
3450
3316
|
const { data: existingWatchers } = await supabase.from("mcp_watchers").select("pid, tty, cli_version, started_at").eq("device_id", deviceId);
|
|
3451
3317
|
if (existingWatchers && existingWatchers.length > 0) {
|
|
3452
3318
|
console.log("");
|
|
3453
|
-
console.log(
|
|
3319
|
+
console.log(chalk20.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
|
|
3454
3320
|
for (const w of existingWatchers) {
|
|
3455
3321
|
try {
|
|
3456
3322
|
if (typeof w.pid === "number" && w.pid > 1 && w.pid !== process.pid) {
|
|
@@ -3461,15 +3327,15 @@ async function mcpWatchCommand() {
|
|
|
3461
3327
|
}
|
|
3462
3328
|
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId);
|
|
3463
3329
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
3464
|
-
console.log(
|
|
3330
|
+
console.log(chalk20.dim(" Previous watcher stopped.\n"));
|
|
3465
3331
|
}
|
|
3466
3332
|
process.stdout.write(`\x1B]0;MCP mon\x07`);
|
|
3467
|
-
console.log(
|
|
3333
|
+
console.log(chalk20.blue(`Starting MCP monitor for ${deviceName}...`));
|
|
3468
3334
|
console.log("");
|
|
3469
|
-
console.log(
|
|
3470
|
-
console.log(
|
|
3335
|
+
console.log(chalk20.dim(" View this in the online dashboard at:"));
|
|
3336
|
+
console.log(chalk20.cyan(` https://www.md4ai.com/device/${deviceId}`));
|
|
3471
3337
|
console.log("");
|
|
3472
|
-
console.log(
|
|
3338
|
+
console.log(chalk20.yellow(" Please note, closing this window will stop ALL watch reports."));
|
|
3473
3339
|
console.log("");
|
|
3474
3340
|
await supabase.from("mcp_watchers").upsert({
|
|
3475
3341
|
device_id: deviceId,
|
|
@@ -3499,7 +3365,7 @@ async function mcpWatchCommand() {
|
|
|
3499
3365
|
return;
|
|
3500
3366
|
}
|
|
3501
3367
|
if (deleteError) {
|
|
3502
|
-
console.error(
|
|
3368
|
+
console.error(chalk20.red(` [debug] Failed to delete old status: ${deleteError.message}`));
|
|
3503
3369
|
}
|
|
3504
3370
|
if (rows.length > 0) {
|
|
3505
3371
|
const { error: insertError } = await supabase.from("mcp_server_status").insert(rows.map((row) => ({
|
|
@@ -3512,7 +3378,7 @@ async function mcpWatchCommand() {
|
|
|
3512
3378
|
return;
|
|
3513
3379
|
}
|
|
3514
3380
|
if (insertError) {
|
|
3515
|
-
console.error(
|
|
3381
|
+
console.error(chalk20.red(` [debug] Failed to write MCP status: ${insertError.message}`));
|
|
3516
3382
|
}
|
|
3517
3383
|
}
|
|
3518
3384
|
const { error: heartbeatError } = await supabase.from("mcp_watchers").update({ last_heartbeat: now }).eq("device_id", deviceId).eq("pid", myPid);
|
|
@@ -3530,11 +3396,11 @@ async function mcpWatchCommand() {
|
|
|
3530
3396
|
return;
|
|
3531
3397
|
}
|
|
3532
3398
|
jwtRefreshAttempted = true;
|
|
3533
|
-
console.log(
|
|
3399
|
+
console.log(chalk20.yellow("\n Session token expired \u2014 attempting to refresh..."));
|
|
3534
3400
|
const refreshed = await refreshSession();
|
|
3535
3401
|
if (refreshed) {
|
|
3536
3402
|
supabase = refreshed.supabase;
|
|
3537
|
-
console.log(
|
|
3403
|
+
console.log(chalk20.green(" Session refreshed successfully. Resuming monitoring.\n"));
|
|
3538
3404
|
jwtRefreshAttempted = false;
|
|
3539
3405
|
await supabase.from("mcp_watchers").upsert({
|
|
3540
3406
|
device_id: deviceId,
|
|
@@ -3552,14 +3418,14 @@ async function mcpWatchCommand() {
|
|
|
3552
3418
|
function printSessionExpired() {
|
|
3553
3419
|
process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
|
|
3554
3420
|
console.log("");
|
|
3555
|
-
console.log(
|
|
3421
|
+
console.log(chalk20.bgRed.white.bold(" \u2717 SESSION EXPIRED \u2014 this watcher is no longer sending data to the dashboard \u2717 "));
|
|
3556
3422
|
console.log("");
|
|
3557
|
-
console.log(
|
|
3558
|
-
console.log(
|
|
3423
|
+
console.log(chalk20.yellow(" Your authentication token has expired and could not be refreshed."));
|
|
3424
|
+
console.log(chalk20.yellow(' The dashboard will show "No watchers" because this process can no longer update it.'));
|
|
3559
3425
|
console.log("");
|
|
3560
|
-
console.log(
|
|
3561
|
-
console.log(
|
|
3562
|
-
console.log(
|
|
3426
|
+
console.log(chalk20.white(" To fix this:"));
|
|
3427
|
+
console.log(chalk20.cyan(" 1. Run: md4ai login"));
|
|
3428
|
+
console.log(chalk20.cyan(" 2. Then restart the watcher: md4ai mcp-watch"));
|
|
3563
3429
|
console.log("");
|
|
3564
3430
|
}
|
|
3565
3431
|
let interval;
|
|
@@ -3602,7 +3468,7 @@ async function mcpWatchCommand() {
|
|
|
3602
3468
|
clearInterval(interval);
|
|
3603
3469
|
clearInterval(envInterval);
|
|
3604
3470
|
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).eq("pid", myPid);
|
|
3605
|
-
console.log(
|
|
3471
|
+
console.log(chalk20.dim("\nMCP monitor stopped."));
|
|
3606
3472
|
process.exit(0);
|
|
3607
3473
|
};
|
|
3608
3474
|
process.on("SIGINT", () => {
|
|
@@ -3695,6 +3561,130 @@ var init_mcp_watch = __esm({
|
|
|
3695
3561
|
}
|
|
3696
3562
|
});
|
|
3697
3563
|
|
|
3564
|
+
// dist/commands/sync.js
|
|
3565
|
+
var sync_exports = {};
|
|
3566
|
+
__export(sync_exports, {
|
|
3567
|
+
syncCommand: () => syncCommand
|
|
3568
|
+
});
|
|
3569
|
+
import { resolve as resolve8 } from "node:path";
|
|
3570
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
3571
|
+
import chalk22 from "chalk";
|
|
3572
|
+
function isValidProjectPath(p) {
|
|
3573
|
+
const resolved = resolve8(p);
|
|
3574
|
+
return resolved.startsWith("/") && !resolved.includes("..") && existsSync14(resolved);
|
|
3575
|
+
}
|
|
3576
|
+
async function syncCommand(options) {
|
|
3577
|
+
const { supabase, userId } = await getAuthenticatedClient();
|
|
3578
|
+
if (options.all) {
|
|
3579
|
+
const currentDeviceName = detectDeviceName();
|
|
3580
|
+
const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path").eq("device_name", currentDeviceName);
|
|
3581
|
+
if (error || !devices?.length) {
|
|
3582
|
+
console.log(chalk22.yellow(`
|
|
3583
|
+
No projects linked on ${currentDeviceName} yet.
|
|
3584
|
+
`));
|
|
3585
|
+
console.log(chalk22.dim(" To link a project, cd into its folder and run:"));
|
|
3586
|
+
console.log(chalk22.cyan(" md4ai scan"));
|
|
3587
|
+
console.log(chalk22.dim(" Or link an existing project from the dashboard:"));
|
|
3588
|
+
console.log(chalk22.cyan(" md4ai link <project-id>\n"));
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
for (const device of devices) {
|
|
3592
|
+
if (!isValidProjectPath(device.path)) {
|
|
3593
|
+
console.error(chalk22.red(` Skipping invalid path: ${device.path}`));
|
|
3594
|
+
continue;
|
|
3595
|
+
}
|
|
3596
|
+
console.log(chalk22.blue(`Syncing: ${device.path}`));
|
|
3597
|
+
const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
3598
|
+
if (proposedAll?.length) {
|
|
3599
|
+
console.log(chalk22.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
3600
|
+
}
|
|
3601
|
+
try {
|
|
3602
|
+
const result = await scanProject(device.path, device.folder_id);
|
|
3603
|
+
const { data: allDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
|
|
3604
|
+
const allDeviceId = allDeviceRow?.id;
|
|
3605
|
+
if (allDeviceId) {
|
|
3606
|
+
await supabase.from("device_scans").upsert({
|
|
3607
|
+
folder_id: device.folder_id,
|
|
3608
|
+
device_id: allDeviceId,
|
|
3609
|
+
user_id: userId,
|
|
3610
|
+
graph_json: result.graph,
|
|
3611
|
+
orphans_json: result.orphans,
|
|
3612
|
+
skills_table_json: result.skills,
|
|
3613
|
+
stale_files_json: result.staleFiles,
|
|
3614
|
+
broken_refs_json: result.brokenRefs,
|
|
3615
|
+
env_manifest_json: result.envManifest,
|
|
3616
|
+
plugin_versions_json: result.pluginVersions,
|
|
3617
|
+
data_hash: result.dataHash,
|
|
3618
|
+
scanned_at: result.scannedAt,
|
|
3619
|
+
cli_version: CURRENT_VERSION
|
|
3620
|
+
}, { onConflict: "folder_id,device_id" });
|
|
3621
|
+
}
|
|
3622
|
+
await pushToolings(supabase, device.folder_id, result.toolings, allDeviceId);
|
|
3623
|
+
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
3624
|
+
console.log(chalk22.green(` Done: ${device.device_name}`));
|
|
3625
|
+
} catch (err) {
|
|
3626
|
+
console.error(chalk22.red(` Failed: ${device.path}: ${err}`));
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3630
|
+
console.log(chalk22.green("\nAll devices synced."));
|
|
3631
|
+
} else {
|
|
3632
|
+
const state = await loadState();
|
|
3633
|
+
if (!state.lastFolderId) {
|
|
3634
|
+
console.error(chalk22.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
|
|
3635
|
+
process.exit(1);
|
|
3636
|
+
}
|
|
3637
|
+
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();
|
|
3638
|
+
if (!device) {
|
|
3639
|
+
console.error(chalk22.red("Could not find last synced device/folder."));
|
|
3640
|
+
process.exit(1);
|
|
3641
|
+
}
|
|
3642
|
+
if (!isValidProjectPath(device.path)) {
|
|
3643
|
+
console.error(chalk22.red(`Invalid project path: ${device.path}`));
|
|
3644
|
+
process.exit(1);
|
|
3645
|
+
}
|
|
3646
|
+
console.log(chalk22.blue(`Syncing: ${device.path}`));
|
|
3647
|
+
const { data: proposedSingle } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
3648
|
+
if (proposedSingle?.length) {
|
|
3649
|
+
console.log(chalk22.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
3650
|
+
}
|
|
3651
|
+
const result = await scanProject(device.path, device.folder_id);
|
|
3652
|
+
const { data: singleDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
|
|
3653
|
+
const singleDeviceId = singleDeviceRow?.id;
|
|
3654
|
+
if (singleDeviceId) {
|
|
3655
|
+
await supabase.from("device_scans").upsert({
|
|
3656
|
+
folder_id: device.folder_id,
|
|
3657
|
+
device_id: singleDeviceId,
|
|
3658
|
+
user_id: userId,
|
|
3659
|
+
graph_json: result.graph,
|
|
3660
|
+
orphans_json: result.orphans,
|
|
3661
|
+
skills_table_json: result.skills,
|
|
3662
|
+
stale_files_json: result.staleFiles,
|
|
3663
|
+
broken_refs_json: result.brokenRefs,
|
|
3664
|
+
plugin_versions_json: result.pluginVersions,
|
|
3665
|
+
data_hash: result.dataHash,
|
|
3666
|
+
scanned_at: result.scannedAt,
|
|
3667
|
+
cli_version: CURRENT_VERSION
|
|
3668
|
+
}, { onConflict: "folder_id,device_id" });
|
|
3669
|
+
}
|
|
3670
|
+
await pushToolings(supabase, device.folder_id, result.toolings, singleDeviceId);
|
|
3671
|
+
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
3672
|
+
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3673
|
+
console.log(chalk22.green("Synced."));
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
var init_sync = __esm({
|
|
3677
|
+
"dist/commands/sync.js"() {
|
|
3678
|
+
"use strict";
|
|
3679
|
+
init_auth();
|
|
3680
|
+
init_config();
|
|
3681
|
+
init_scanner();
|
|
3682
|
+
init_push_toolings();
|
|
3683
|
+
init_check_update();
|
|
3684
|
+
init_device_utils();
|
|
3685
|
+
}
|
|
3686
|
+
});
|
|
3687
|
+
|
|
3698
3688
|
// dist/index.js
|
|
3699
3689
|
import { Command } from "commander";
|
|
3700
3690
|
|
|
@@ -4059,9 +4049,6 @@ function generatePrintHtml(result, title) {
|
|
|
4059
4049
|
</html>`;
|
|
4060
4050
|
}
|
|
4061
4051
|
|
|
4062
|
-
// dist/index.js
|
|
4063
|
-
init_sync();
|
|
4064
|
-
|
|
4065
4052
|
// dist/commands/link.js
|
|
4066
4053
|
init_auth();
|
|
4067
4054
|
init_config();
|
|
@@ -4069,23 +4056,23 @@ init_scanner();
|
|
|
4069
4056
|
init_push_toolings();
|
|
4070
4057
|
init_device_utils();
|
|
4071
4058
|
init_check_update();
|
|
4072
|
-
import { resolve as
|
|
4073
|
-
import
|
|
4059
|
+
import { resolve as resolve5 } from "node:path";
|
|
4060
|
+
import chalk15 from "chalk";
|
|
4074
4061
|
async function linkCommand(projectId) {
|
|
4075
4062
|
const shouldContinue = await promptUpdateIfAvailable(["link", projectId]);
|
|
4076
4063
|
if (!shouldContinue)
|
|
4077
4064
|
return;
|
|
4078
4065
|
const { supabase, userId } = await getAuthenticatedClient();
|
|
4079
|
-
const cwd =
|
|
4066
|
+
const cwd = resolve5(process.cwd());
|
|
4080
4067
|
const deviceName = detectDeviceName();
|
|
4081
4068
|
const osType = detectOs();
|
|
4082
4069
|
const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
|
|
4083
4070
|
if (folderErr || !folder) {
|
|
4084
|
-
console.error(
|
|
4085
|
-
console.error(
|
|
4071
|
+
console.error(chalk15.red("Project not found, or you do not have access."));
|
|
4072
|
+
console.error(chalk15.yellow("Check the project ID in the MD4AI web dashboard."));
|
|
4086
4073
|
process.exit(1);
|
|
4087
4074
|
}
|
|
4088
|
-
console.log(
|
|
4075
|
+
console.log(chalk15.blue(`
|
|
4089
4076
|
Linking "${folder.name}" to this device...
|
|
4090
4077
|
`));
|
|
4091
4078
|
console.log(` Project: ${folder.name}`);
|
|
@@ -4111,12 +4098,12 @@ Linking "${folder.name}" to this device...
|
|
|
4111
4098
|
path: cwd
|
|
4112
4099
|
});
|
|
4113
4100
|
if (pathErr) {
|
|
4114
|
-
console.error(
|
|
4101
|
+
console.error(chalk15.red(`Failed to link: ${pathErr.message}`));
|
|
4115
4102
|
process.exit(1);
|
|
4116
4103
|
}
|
|
4117
4104
|
}
|
|
4118
|
-
console.log(
|
|
4119
|
-
console.log(
|
|
4105
|
+
console.log(chalk15.green("\nLinked successfully."));
|
|
4106
|
+
console.log(chalk15.blue("\nRunning initial scan...\n"));
|
|
4120
4107
|
const result = await scanProject(cwd, folder.id);
|
|
4121
4108
|
console.log(` Files: ${result.graph.nodes.length}`);
|
|
4122
4109
|
console.log(` References:${result.graph.edges.length}`);
|
|
@@ -4157,7 +4144,7 @@ Linking "${folder.name}" to this device...
|
|
|
4157
4144
|
device_id: deviceId
|
|
4158
4145
|
}, { onConflict: "folder_id,file_path,device_id" });
|
|
4159
4146
|
}
|
|
4160
|
-
console.log(
|
|
4147
|
+
console.log(chalk15.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
4161
4148
|
}
|
|
4162
4149
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
|
|
4163
4150
|
await saveState({
|
|
@@ -4166,28 +4153,28 @@ Linking "${folder.name}" to this device...
|
|
|
4166
4153
|
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4167
4154
|
});
|
|
4168
4155
|
const projectUrl = `https://www.md4ai.com/project/${folder.id}`;
|
|
4169
|
-
console.log(
|
|
4170
|
-
console.log(
|
|
4156
|
+
console.log(chalk15.green("\nDone! Project linked and scanned."));
|
|
4157
|
+
console.log(chalk15.cyan(`
|
|
4171
4158
|
${projectUrl}
|
|
4172
4159
|
`));
|
|
4173
|
-
console.log(
|
|
4160
|
+
console.log(chalk15.grey('Run "md4ai scan" to rescan at any time.'));
|
|
4174
4161
|
}
|
|
4175
4162
|
|
|
4176
4163
|
// dist/commands/import-bundle.js
|
|
4177
4164
|
import { readFile as readFile11, writeFile as writeFile4, mkdir as mkdir3, stat as fsStat } from "node:fs/promises";
|
|
4178
|
-
import { dirname as dirname3, resolve as
|
|
4179
|
-
import { existsSync as
|
|
4180
|
-
import
|
|
4165
|
+
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
4166
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
4167
|
+
import chalk16 from "chalk";
|
|
4181
4168
|
import { confirm as confirm3, input as input6 } from "@inquirer/prompts";
|
|
4182
4169
|
var MAX_BUNDLE_SIZE_BYTES = 50 * 1024 * 1024;
|
|
4183
4170
|
async function importBundleCommand(zipPath) {
|
|
4184
|
-
if (!
|
|
4185
|
-
console.error(
|
|
4171
|
+
if (!existsSync11(zipPath)) {
|
|
4172
|
+
console.error(chalk16.red(`File not found: ${zipPath}`));
|
|
4186
4173
|
process.exit(1);
|
|
4187
4174
|
}
|
|
4188
4175
|
const fileStat = await fsStat(zipPath);
|
|
4189
4176
|
if (fileStat.size > MAX_BUNDLE_SIZE_BYTES) {
|
|
4190
|
-
console.error(
|
|
4177
|
+
console.error(chalk16.red(`Bundle too large (${Math.round(fileStat.size / 1024 / 1024)} MB). Maximum is 50 MB.`));
|
|
4191
4178
|
process.exit(1);
|
|
4192
4179
|
}
|
|
4193
4180
|
const JSZip = (await import("jszip")).default;
|
|
@@ -4195,11 +4182,11 @@ async function importBundleCommand(zipPath) {
|
|
|
4195
4182
|
const zip = await JSZip.loadAsync(zipData);
|
|
4196
4183
|
const manifestFile = zip.file("manifest.json");
|
|
4197
4184
|
if (!manifestFile) {
|
|
4198
|
-
console.error(
|
|
4185
|
+
console.error(chalk16.red("Invalid bundle: missing manifest.json"));
|
|
4199
4186
|
process.exit(1);
|
|
4200
4187
|
}
|
|
4201
4188
|
const manifest = JSON.parse(await manifestFile.async("string"));
|
|
4202
|
-
console.log(
|
|
4189
|
+
console.log(chalk16.blue(`
|
|
4203
4190
|
Bundle: ${manifest.folderName}`));
|
|
4204
4191
|
console.log(` Exported by: ${manifest.ownerEmail}`);
|
|
4205
4192
|
console.log(` Exported at: ${manifest.exportedAt}`);
|
|
@@ -4213,10 +4200,10 @@ Bundle: ${manifest.folderName}`));
|
|
|
4213
4200
|
}
|
|
4214
4201
|
}
|
|
4215
4202
|
if (files.length === 0) {
|
|
4216
|
-
console.log(
|
|
4203
|
+
console.log(chalk16.yellow("No Claude config files found in bundle."));
|
|
4217
4204
|
return;
|
|
4218
4205
|
}
|
|
4219
|
-
console.log(
|
|
4206
|
+
console.log(chalk16.blue(`
|
|
4220
4207
|
Files to extract:`));
|
|
4221
4208
|
for (const f of files) {
|
|
4222
4209
|
console.log(` ${f.filePath}`);
|
|
@@ -4229,39 +4216,39 @@ Files to extract:`));
|
|
|
4229
4216
|
message: `Extract ${files.length} file(s) to ${targetDir}?`
|
|
4230
4217
|
});
|
|
4231
4218
|
if (!proceed) {
|
|
4232
|
-
console.log(
|
|
4219
|
+
console.log(chalk16.yellow("Cancelled."));
|
|
4233
4220
|
return;
|
|
4234
4221
|
}
|
|
4235
|
-
const resolvedTarget =
|
|
4222
|
+
const resolvedTarget = resolve6(targetDir);
|
|
4236
4223
|
for (const file of files) {
|
|
4237
|
-
const fullPath =
|
|
4224
|
+
const fullPath = resolve6(targetDir, file.filePath);
|
|
4238
4225
|
if (!fullPath.startsWith(resolvedTarget + "/") && fullPath !== resolvedTarget) {
|
|
4239
|
-
console.error(
|
|
4226
|
+
console.error(chalk16.red(` Blocked path traversal: ${file.filePath}`));
|
|
4240
4227
|
continue;
|
|
4241
4228
|
}
|
|
4242
4229
|
const dir = dirname3(fullPath);
|
|
4243
|
-
if (!
|
|
4230
|
+
if (!existsSync11(dir)) {
|
|
4244
4231
|
await mkdir3(dir, { recursive: true });
|
|
4245
4232
|
}
|
|
4246
4233
|
await writeFile4(fullPath, file.content, "utf-8");
|
|
4247
|
-
console.log(
|
|
4234
|
+
console.log(chalk16.green(` \u2713 ${file.filePath}`));
|
|
4248
4235
|
}
|
|
4249
|
-
console.log(
|
|
4236
|
+
console.log(chalk16.green(`
|
|
4250
4237
|
Done! ${files.length} file(s) extracted to ${targetDir}`));
|
|
4251
4238
|
}
|
|
4252
4239
|
|
|
4253
4240
|
// dist/commands/admin-update-tool.js
|
|
4254
4241
|
init_auth();
|
|
4255
|
-
import
|
|
4242
|
+
import chalk17 from "chalk";
|
|
4256
4243
|
var VALID_CATEGORIES = ["framework", "runtime", "cli", "mcp", "package", "database", "other"];
|
|
4257
4244
|
async function adminUpdateToolCommand(options) {
|
|
4258
4245
|
const { supabase } = await getAuthenticatedClient();
|
|
4259
4246
|
if (!options.name) {
|
|
4260
|
-
console.error(
|
|
4247
|
+
console.error(chalk17.red("--name is required."));
|
|
4261
4248
|
process.exit(1);
|
|
4262
4249
|
}
|
|
4263
4250
|
if (options.category && !VALID_CATEGORIES.includes(options.category)) {
|
|
4264
|
-
console.error(
|
|
4251
|
+
console.error(chalk17.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
|
|
4265
4252
|
process.exit(1);
|
|
4266
4253
|
}
|
|
4267
4254
|
const { data: existing } = await supabase.from("tools_registry").select("id, name, display_name, category").eq("name", options.name).maybeSingle();
|
|
@@ -4283,13 +4270,13 @@ async function adminUpdateToolCommand(options) {
|
|
|
4283
4270
|
updates.notes = options.notes;
|
|
4284
4271
|
const { error } = await supabase.from("tools_registry").update(updates).eq("id", existing.id);
|
|
4285
4272
|
if (error) {
|
|
4286
|
-
console.error(
|
|
4273
|
+
console.error(chalk17.red(`Failed to update: ${error.message}`));
|
|
4287
4274
|
process.exit(1);
|
|
4288
4275
|
}
|
|
4289
|
-
console.log(
|
|
4276
|
+
console.log(chalk17.green(`Updated "${existing.display_name}" in the registry.`));
|
|
4290
4277
|
} else {
|
|
4291
4278
|
if (!options.display || !options.category) {
|
|
4292
|
-
console.error(
|
|
4279
|
+
console.error(chalk17.red("New tools require --display and --category."));
|
|
4293
4280
|
process.exit(1);
|
|
4294
4281
|
}
|
|
4295
4282
|
const { error } = await supabase.from("tools_registry").insert({
|
|
@@ -4303,25 +4290,25 @@ async function adminUpdateToolCommand(options) {
|
|
|
4303
4290
|
notes: options.notes ?? null
|
|
4304
4291
|
});
|
|
4305
4292
|
if (error) {
|
|
4306
|
-
console.error(
|
|
4293
|
+
console.error(chalk17.red(`Failed to create: ${error.message}`));
|
|
4307
4294
|
process.exit(1);
|
|
4308
4295
|
}
|
|
4309
|
-
console.log(
|
|
4296
|
+
console.log(chalk17.green(`Added "${options.display}" to the registry.`));
|
|
4310
4297
|
}
|
|
4311
4298
|
}
|
|
4312
4299
|
|
|
4313
4300
|
// dist/commands/admin-list-tools.js
|
|
4314
4301
|
init_auth();
|
|
4315
|
-
import
|
|
4302
|
+
import chalk18 from "chalk";
|
|
4316
4303
|
async function adminListToolsCommand() {
|
|
4317
4304
|
const { supabase } = await getAuthenticatedClient();
|
|
4318
4305
|
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("category").order("display_name");
|
|
4319
4306
|
if (error) {
|
|
4320
|
-
console.error(
|
|
4307
|
+
console.error(chalk18.red(`Failed to fetch tools: ${error.message}`));
|
|
4321
4308
|
process.exit(1);
|
|
4322
4309
|
}
|
|
4323
4310
|
if (!tools?.length) {
|
|
4324
|
-
console.log(
|
|
4311
|
+
console.log(chalk18.yellow("No tools in the registry."));
|
|
4325
4312
|
return;
|
|
4326
4313
|
}
|
|
4327
4314
|
const nameW = Math.max(16, ...tools.map((t) => t.display_name.length));
|
|
@@ -4335,7 +4322,7 @@ async function adminListToolsCommand() {
|
|
|
4335
4322
|
"Beta".padEnd(betaW),
|
|
4336
4323
|
"Last Checked"
|
|
4337
4324
|
].join(" ");
|
|
4338
|
-
console.log(
|
|
4325
|
+
console.log(chalk18.bold(header));
|
|
4339
4326
|
console.log("\u2500".repeat(header.length));
|
|
4340
4327
|
for (const tool of tools) {
|
|
4341
4328
|
const lastChecked = tool.updated_at ? formatRelative(new Date(tool.updated_at)) : "\u2014";
|
|
@@ -4348,7 +4335,7 @@ async function adminListToolsCommand() {
|
|
|
4348
4335
|
].join(" ");
|
|
4349
4336
|
console.log(row);
|
|
4350
4337
|
}
|
|
4351
|
-
console.log(
|
|
4338
|
+
console.log(chalk18.grey(`
|
|
4352
4339
|
${tools.length} tool(s) in registry.`));
|
|
4353
4340
|
}
|
|
4354
4341
|
function formatRelative(date) {
|
|
@@ -4366,7 +4353,7 @@ function formatRelative(date) {
|
|
|
4366
4353
|
|
|
4367
4354
|
// dist/commands/admin-fetch-versions.js
|
|
4368
4355
|
init_auth();
|
|
4369
|
-
import
|
|
4356
|
+
import chalk19 from "chalk";
|
|
4370
4357
|
|
|
4371
4358
|
// dist/commands/version-sources.js
|
|
4372
4359
|
var VERSION_SOURCES = {
|
|
@@ -4403,11 +4390,11 @@ async function adminFetchVersionsCommand() {
|
|
|
4403
4390
|
const { supabase } = await getAuthenticatedClient();
|
|
4404
4391
|
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("display_name");
|
|
4405
4392
|
if (error) {
|
|
4406
|
-
console.error(
|
|
4393
|
+
console.error(chalk19.red(`Failed to fetch tools: ${error.message}`));
|
|
4407
4394
|
process.exit(1);
|
|
4408
4395
|
}
|
|
4409
4396
|
if (!tools?.length) {
|
|
4410
|
-
console.log(
|
|
4397
|
+
console.log(chalk19.yellow("No tools in the registry."));
|
|
4411
4398
|
}
|
|
4412
4399
|
const { data: allProjectToolings } = await supabase.from("project_toolings").select("tool_name, detection_source").is("tool_id", null);
|
|
4413
4400
|
const registeredNames = new Set((tools ?? []).map((t) => t.name));
|
|
@@ -4418,7 +4405,7 @@ async function adminFetchVersionsCommand() {
|
|
|
4418
4405
|
}
|
|
4419
4406
|
}
|
|
4420
4407
|
if (unregisteredNames.size > 0) {
|
|
4421
|
-
console.log(
|
|
4408
|
+
console.log(chalk19.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
|
|
4422
4409
|
`));
|
|
4423
4410
|
for (const name of unregisteredNames) {
|
|
4424
4411
|
const displayName = name;
|
|
@@ -4436,10 +4423,10 @@ async function adminFetchVersionsCommand() {
|
|
|
4436
4423
|
}
|
|
4437
4424
|
}
|
|
4438
4425
|
if (!tools?.length) {
|
|
4439
|
-
console.log(
|
|
4426
|
+
console.log(chalk19.yellow("No tools to fetch."));
|
|
4440
4427
|
return;
|
|
4441
4428
|
}
|
|
4442
|
-
console.log(
|
|
4429
|
+
console.log(chalk19.bold(`Fetching versions for ${tools.length} tool(s)...
|
|
4443
4430
|
`));
|
|
4444
4431
|
const results = await Promise.all(tools.map(async (tool) => {
|
|
4445
4432
|
const source = VERSION_SOURCES[tool.name] ?? { type: "npm", package: tool.name };
|
|
@@ -4478,10 +4465,10 @@ async function adminFetchVersionsCommand() {
|
|
|
4478
4465
|
"Beta".padEnd(betaW),
|
|
4479
4466
|
"Status".padEnd(statusW)
|
|
4480
4467
|
].join(" ");
|
|
4481
|
-
console.log(
|
|
4468
|
+
console.log(chalk19.bold(header));
|
|
4482
4469
|
console.log("\u2500".repeat(header.length));
|
|
4483
4470
|
for (const result of results) {
|
|
4484
|
-
const statusColour = result.status === "updated" ?
|
|
4471
|
+
const statusColour = result.status === "updated" ? chalk19.green : result.status === "unchanged" ? chalk19.grey : result.status === "failed" ? chalk19.red : chalk19.yellow;
|
|
4485
4472
|
const row = [
|
|
4486
4473
|
result.displayName.padEnd(nameW),
|
|
4487
4474
|
(result.stable ?? "\u2014").padEnd(stableW),
|
|
@@ -4494,8 +4481,8 @@ async function adminFetchVersionsCommand() {
|
|
|
4494
4481
|
const unchanged = results.filter((r) => r.status === "unchanged").length;
|
|
4495
4482
|
const failed = results.filter((r) => r.status === "failed").length;
|
|
4496
4483
|
const noSource = results.filter((r) => r.status === "no source").length;
|
|
4497
|
-
console.log(
|
|
4498
|
-
${results.length} tool(s): `) +
|
|
4484
|
+
console.log(chalk19.grey(`
|
|
4485
|
+
${results.length} tool(s): `) + chalk19.green(`${updated} updated`) + ", " + chalk19.grey(`${unchanged} unchanged`) + ", " + chalk19.red(`${failed} failed`) + ", " + chalk19.yellow(`${noSource} no source`));
|
|
4499
4486
|
}
|
|
4500
4487
|
async function fetchVersions(source) {
|
|
4501
4488
|
const controller = new AbortController();
|
|
@@ -4553,10 +4540,10 @@ async function fetchGitHubVersions(repo, signal) {
|
|
|
4553
4540
|
init_mcp_watch();
|
|
4554
4541
|
|
|
4555
4542
|
// dist/commands/init-manifest.js
|
|
4556
|
-
import { resolve as
|
|
4543
|
+
import { resolve as resolve7, join as join16, relative as relative4, dirname as dirname4 } from "node:path";
|
|
4557
4544
|
import { readFile as readFile13, writeFile as writeFile5, mkdir as mkdir4 } from "node:fs/promises";
|
|
4558
|
-
import { existsSync as
|
|
4559
|
-
import
|
|
4545
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
4546
|
+
import chalk21 from "chalk";
|
|
4560
4547
|
var SECRET_PATTERNS = [
|
|
4561
4548
|
/_KEY$/i,
|
|
4562
4549
|
/_SECRET$/i,
|
|
@@ -4573,7 +4560,7 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
4573
4560
|
const apps = [];
|
|
4574
4561
|
for (const envName of [".env", ".env.local", ".env.example"]) {
|
|
4575
4562
|
const envPath = join16(projectRoot, envName);
|
|
4576
|
-
if (
|
|
4563
|
+
if (existsSync13(envPath)) {
|
|
4577
4564
|
const vars = await extractVarNames(envPath);
|
|
4578
4565
|
if (vars.length > 0) {
|
|
4579
4566
|
apps.push({ name: "root", envFilePath: envName, vars });
|
|
@@ -4584,11 +4571,11 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
4584
4571
|
const subdirs = ["web", "cli", "api", "app", "server", "packages"];
|
|
4585
4572
|
for (const sub of subdirs) {
|
|
4586
4573
|
const subDir = join16(projectRoot, sub);
|
|
4587
|
-
if (!
|
|
4574
|
+
if (!existsSync13(subDir))
|
|
4588
4575
|
continue;
|
|
4589
4576
|
for (const envName of [".env.local", ".env", ".env.example"]) {
|
|
4590
4577
|
const envPath = join16(subDir, envName);
|
|
4591
|
-
if (
|
|
4578
|
+
if (existsSync13(envPath)) {
|
|
4592
4579
|
const vars = await extractVarNames(envPath);
|
|
4593
4580
|
if (vars.length > 0) {
|
|
4594
4581
|
apps.push({
|
|
@@ -4648,33 +4635,33 @@ function generateManifest(apps) {
|
|
|
4648
4635
|
return lines.join("\n");
|
|
4649
4636
|
}
|
|
4650
4637
|
async function initManifestCommand() {
|
|
4651
|
-
const projectRoot =
|
|
4638
|
+
const projectRoot = resolve7(process.cwd());
|
|
4652
4639
|
const manifestPath = join16(projectRoot, "docs", "reference", "env-manifest.md");
|
|
4653
|
-
if (
|
|
4654
|
-
console.log(
|
|
4655
|
-
console.log(
|
|
4640
|
+
if (existsSync13(manifestPath)) {
|
|
4641
|
+
console.log(chalk21.yellow(`Manifest already exists: ${relative4(projectRoot, manifestPath)}`));
|
|
4642
|
+
console.log(chalk21.dim("Edit it directly to make changes."));
|
|
4656
4643
|
return;
|
|
4657
4644
|
}
|
|
4658
|
-
console.log(
|
|
4645
|
+
console.log(chalk21.blue("Scanning for .env files...\n"));
|
|
4659
4646
|
const apps = await discoverEnvFiles(projectRoot);
|
|
4660
4647
|
if (apps.length === 0) {
|
|
4661
|
-
console.log(
|
|
4662
|
-
console.log(
|
|
4648
|
+
console.log(chalk21.yellow("No .env files found. Create a manifest manually at:"));
|
|
4649
|
+
console.log(chalk21.cyan(` docs/reference/env-manifest.md`));
|
|
4663
4650
|
return;
|
|
4664
4651
|
}
|
|
4665
4652
|
for (const app of apps) {
|
|
4666
|
-
console.log(` ${
|
|
4653
|
+
console.log(` ${chalk21.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
|
|
4667
4654
|
}
|
|
4668
4655
|
const content = generateManifest(apps);
|
|
4669
4656
|
const dir = dirname4(manifestPath);
|
|
4670
|
-
if (!
|
|
4657
|
+
if (!existsSync13(dir)) {
|
|
4671
4658
|
await mkdir4(dir, { recursive: true });
|
|
4672
4659
|
}
|
|
4673
4660
|
await writeFile5(manifestPath, content, "utf-8");
|
|
4674
|
-
console.log(
|
|
4661
|
+
console.log(chalk21.green(`
|
|
4675
4662
|
Manifest created: ${relative4(projectRoot, manifestPath)}`));
|
|
4676
|
-
console.log(
|
|
4677
|
-
console.log(
|
|
4663
|
+
console.log(chalk21.dim("Review and edit the file \u2014 it is your source of truth."));
|
|
4664
|
+
console.log(chalk21.dim("Then run `md4ai scan` to verify against your environments."));
|
|
4678
4665
|
}
|
|
4679
4666
|
|
|
4680
4667
|
// dist/commands/update.js
|
|
@@ -5083,32 +5070,39 @@ init_check_update();
|
|
|
5083
5070
|
var program = new Command();
|
|
5084
5071
|
program.name("md4ai").description("MD4AI \u2014 Claude tooling visualiser").version(CURRENT_VERSION).addHelpText("after", `
|
|
5085
5072
|
Quick start:
|
|
5086
|
-
md4ai
|
|
5087
|
-
md4ai
|
|
5073
|
+
md4ai login Log in with email and password
|
|
5074
|
+
md4ai link <project-id> Link this folder to a dashboard project
|
|
5075
|
+
md4ai scan Scan project and push to dashboard
|
|
5076
|
+
|
|
5077
|
+
Daily use:
|
|
5078
|
+
md4ai Scan + start MCP monitoring (all-in-one)
|
|
5079
|
+
|
|
5080
|
+
More:
|
|
5081
|
+
md4ai config ... Configuration (tokens, settings)
|
|
5082
|
+
md4ai doppler ... Doppler secrets integration
|
|
5083
|
+
md4ai admin ... Advanced tools (simulate, print, import, etc.)`);
|
|
5088
5084
|
program.command("start").description("Check for updates, scan project, and start MCP monitoring \u2014 the all-in-one command").action(startCommand);
|
|
5089
5085
|
program.command("login").description("Log in to MD4AI with email and password").action(loginCommand);
|
|
5090
5086
|
program.command("logout").description("Log out and clear stored credentials").action(logoutCommand);
|
|
5091
5087
|
program.command("status").description("Show login status, device count, folder count, last sync").action(statusCommand);
|
|
5092
|
-
program.command("add-folder").description("Create a new Claude folder").action(addFolderCommand);
|
|
5093
|
-
program.command("add-device").description("Add a device path to a Claude folder").action(addDeviceCommand);
|
|
5094
|
-
program.command("list-devices").description("List all devices and their linked folders").action(listDevicesCommand);
|
|
5095
5088
|
program.command("link <project-id>").description("Link the current directory to a project created in the web dashboard").action(linkCommand);
|
|
5096
5089
|
program.command("scan [path]").description("Scan Claude project files and push results to your dashboard").option("--offline", "Skip pushing to Supabase").action(mapCommand);
|
|
5097
|
-
program.command("simulate <prompt>").description("Show which files Claude would load for a given prompt").action(simulateCommand);
|
|
5098
|
-
program.command("print <title>").description("Generate a printable wall-chart HTML from the last scan").action(printCommand);
|
|
5099
|
-
program.command("sync").description("Re-push latest scan data to Supabase").option("--all", "Sync all folders on all devices").action(syncCommand);
|
|
5100
|
-
program.command("import <zipfile>").description("Import a Claude setup bundle exported from the web dashboard").action(importBundleCommand);
|
|
5101
|
-
program.command("update").description("Check for updates, install, and optionally rescan and start watching").option("--post-update", "Internal: run post-update flow (called by the update command)").action(updateCommand);
|
|
5102
|
-
program.command("check-update").description("Check if a newer version of md4ai is available").action(checkForUpdate);
|
|
5103
5090
|
program.command("mcp-watch").description("Monitor MCP server status on this device (runs until Ctrl+C)").action(mcpWatchCommand);
|
|
5104
|
-
program.command("
|
|
5091
|
+
program.command("update").description("Check for updates, install, and optionally rescan and start watching").option("--post-update", "Internal: run post-update flow (called by the update command)").action(updateCommand);
|
|
5105
5092
|
var config = program.command("config").description("Manage CLI configuration");
|
|
5106
5093
|
config.command("set <key> <value>").description("Set a configuration value (e.g. vercel-token)").action(configSetCommand);
|
|
5107
5094
|
var doppler = program.command("doppler").description("Manage Doppler integration");
|
|
5108
5095
|
doppler.command("connect").description("Configure Doppler token for secrets scanning").action(dopplerConnectCommand);
|
|
5109
5096
|
doppler.command("disconnect").description("Remove stored Doppler token").action(dopplerDisconnectCommand);
|
|
5110
5097
|
doppler.command("set-project <slug>").description("Override Doppler project slug for the current linked project").action(dopplerSetProjectCommand);
|
|
5111
|
-
var admin = program.command("admin").description("
|
|
5098
|
+
var admin = program.command("admin").description("Advanced tools \u2014 simulate, print, import, device management, registry");
|
|
5099
|
+
admin.command("simulate <prompt>").description("Show which files Claude would load for a given prompt").action(simulateCommand);
|
|
5100
|
+
admin.command("print <title>").description("Generate a printable wall-chart HTML from the last scan").action(printCommand);
|
|
5101
|
+
admin.command("import <zipfile>").description("Import a Claude setup bundle exported from the web dashboard").action(importBundleCommand);
|
|
5102
|
+
admin.command("init-manifest").description("Scaffold a starter env-manifest.md from detected .env files").action(initManifestCommand);
|
|
5103
|
+
admin.command("list-devices").description("List all devices and their linked folders").action(listDevicesCommand);
|
|
5104
|
+
admin.command("add-folder").description("Create a new Claude folder").action(addFolderCommand);
|
|
5105
|
+
admin.command("add-device").description("Add a device path to a Claude folder").action(addDeviceCommand);
|
|
5112
5106
|
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);
|
|
5113
5107
|
admin.command("list-tools").description("List all tools in the master registry").action(adminListToolsCommand);
|
|
5114
5108
|
admin.command("fetch-versions").description("Fetch latest stable and beta versions from npm and GitHub").action(adminFetchVersionsCommand);
|
|
@@ -5118,7 +5112,7 @@ if (process.argv.length <= 2) {
|
|
|
5118
5112
|
} else {
|
|
5119
5113
|
program.parseAsync().then(() => {
|
|
5120
5114
|
const ran = program.args[0];
|
|
5121
|
-
const skipAutoCheck = ["update", "
|
|
5115
|
+
const skipAutoCheck = ["update", "mcp-watch", "start"];
|
|
5122
5116
|
if (!skipAutoCheck.includes(ran)) {
|
|
5123
5117
|
autoCheckForUpdate();
|
|
5124
5118
|
}
|