md4ai 0.17.2 → 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 +330 -281
- 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
|
|
|
@@ -365,14 +355,26 @@ import chalk5 from "chalk";
|
|
|
365
355
|
import { confirm, input as input2, password as password2 } from "@inquirer/prompts";
|
|
366
356
|
async function getAuthenticatedClient() {
|
|
367
357
|
const creds = await loadCredentials();
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const reason = !creds?.accessToken ? "Not logged in." : "Session expired.";
|
|
371
|
-
console.log(chalk5.yellow(`${reason}`));
|
|
358
|
+
if (!creds?.accessToken) {
|
|
359
|
+
console.log(chalk5.yellow("Not logged in."));
|
|
372
360
|
const shouldLogin = await confirm({ message: "Would you like to log in now?" });
|
|
373
|
-
if (!shouldLogin)
|
|
361
|
+
if (!shouldLogin)
|
|
374
362
|
process.exit(0);
|
|
363
|
+
return promptLogin();
|
|
364
|
+
}
|
|
365
|
+
if (creds.refreshToken) {
|
|
366
|
+
const refreshed = await refreshSession();
|
|
367
|
+
if (refreshed) {
|
|
368
|
+
updateDeviceCliVersion(refreshed.supabase, refreshed.userId).catch(() => {
|
|
369
|
+
});
|
|
370
|
+
return refreshed;
|
|
375
371
|
}
|
|
372
|
+
}
|
|
373
|
+
if (Date.now() > creds.expiresAt) {
|
|
374
|
+
console.log(chalk5.yellow("Session expired."));
|
|
375
|
+
const shouldLogin = await confirm({ message: "Would you like to log in now?" });
|
|
376
|
+
if (!shouldLogin)
|
|
377
|
+
process.exit(0);
|
|
376
378
|
return promptLogin();
|
|
377
379
|
}
|
|
378
380
|
const anonKey = getAnonKey();
|
|
@@ -2069,6 +2071,11 @@ var init_doppler_scanner = __esm({
|
|
|
2069
2071
|
});
|
|
2070
2072
|
|
|
2071
2073
|
// dist/scanner/index.js
|
|
2074
|
+
var scanner_exports = {};
|
|
2075
|
+
__export(scanner_exports, {
|
|
2076
|
+
readClaudeConfigFiles: () => readClaudeConfigFiles,
|
|
2077
|
+
scanProject: () => scanProject
|
|
2078
|
+
});
|
|
2072
2079
|
import { readdir as readdir5 } from "node:fs/promises";
|
|
2073
2080
|
import { join as join12, relative as relative3 } from "node:path";
|
|
2074
2081
|
import { existsSync as existsSync7 } from "node:fs";
|
|
@@ -2619,7 +2626,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2619
2626
|
}
|
|
2620
2627
|
}
|
|
2621
2628
|
if (deviceId) {
|
|
2622
|
-
|
|
2629
|
+
const scanPayload = {
|
|
2623
2630
|
folder_id,
|
|
2624
2631
|
device_id: deviceId,
|
|
2625
2632
|
user_id: userId,
|
|
@@ -2635,7 +2642,14 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2635
2642
|
data_hash: result.dataHash,
|
|
2636
2643
|
scanned_at: result.scannedAt,
|
|
2637
2644
|
cli_version: CURRENT_VERSION
|
|
2638
|
-
}
|
|
2645
|
+
};
|
|
2646
|
+
const { data: updated, error: updateErr } = await supabase.from("device_scans").update(scanPayload).eq("folder_id", folder_id).eq("device_id", deviceId).select("id");
|
|
2647
|
+
if (updateErr || !updated?.length) {
|
|
2648
|
+
const { error: insertErr } = await supabase.from("device_scans").insert(scanPayload);
|
|
2649
|
+
if (insertErr) {
|
|
2650
|
+
console.error(chalk12.red(` Failed to save scan data: ${insertErr.message}`));
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2639
2653
|
}
|
|
2640
2654
|
await pushToolings(supabase, folder_id, result.toolings, deviceId);
|
|
2641
2655
|
const manifestForHealth = result.envManifest ?? storedManifest;
|
|
@@ -2785,139 +2799,15 @@ var init_map = __esm({
|
|
|
2785
2799
|
}
|
|
2786
2800
|
});
|
|
2787
2801
|
|
|
2788
|
-
// dist/commands/sync.js
|
|
2789
|
-
var sync_exports = {};
|
|
2790
|
-
__export(sync_exports, {
|
|
2791
|
-
syncCommand: () => syncCommand
|
|
2792
|
-
});
|
|
2793
|
-
import { resolve as resolve5 } from "node:path";
|
|
2794
|
-
import { existsSync as existsSync11 } from "node:fs";
|
|
2795
|
-
import chalk15 from "chalk";
|
|
2796
|
-
function isValidProjectPath(p) {
|
|
2797
|
-
const resolved = resolve5(p);
|
|
2798
|
-
return resolved.startsWith("/") && !resolved.includes("..") && existsSync11(resolved);
|
|
2799
|
-
}
|
|
2800
|
-
async function syncCommand(options) {
|
|
2801
|
-
const { supabase, userId } = await getAuthenticatedClient();
|
|
2802
|
-
if (options.all) {
|
|
2803
|
-
const currentDeviceName = detectDeviceName();
|
|
2804
|
-
const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path").eq("device_name", currentDeviceName);
|
|
2805
|
-
if (error || !devices?.length) {
|
|
2806
|
-
console.log(chalk15.yellow(`
|
|
2807
|
-
No projects linked on ${currentDeviceName} yet.
|
|
2808
|
-
`));
|
|
2809
|
-
console.log(chalk15.dim(" To link a project, cd into its folder and run:"));
|
|
2810
|
-
console.log(chalk15.cyan(" md4ai scan"));
|
|
2811
|
-
console.log(chalk15.dim(" Or link an existing project from the dashboard:"));
|
|
2812
|
-
console.log(chalk15.cyan(" md4ai link <project-id>\n"));
|
|
2813
|
-
return;
|
|
2814
|
-
}
|
|
2815
|
-
for (const device of devices) {
|
|
2816
|
-
if (!isValidProjectPath(device.path)) {
|
|
2817
|
-
console.error(chalk15.red(` Skipping invalid path: ${device.path}`));
|
|
2818
|
-
continue;
|
|
2819
|
-
}
|
|
2820
|
-
console.log(chalk15.blue(`Syncing: ${device.path}`));
|
|
2821
|
-
const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
2822
|
-
if (proposedAll?.length) {
|
|
2823
|
-
console.log(chalk15.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
2824
|
-
}
|
|
2825
|
-
try {
|
|
2826
|
-
const result = await scanProject(device.path, device.folder_id);
|
|
2827
|
-
const { data: allDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
|
|
2828
|
-
const allDeviceId = allDeviceRow?.id;
|
|
2829
|
-
if (allDeviceId) {
|
|
2830
|
-
await supabase.from("device_scans").upsert({
|
|
2831
|
-
folder_id: device.folder_id,
|
|
2832
|
-
device_id: allDeviceId,
|
|
2833
|
-
user_id: userId,
|
|
2834
|
-
graph_json: result.graph,
|
|
2835
|
-
orphans_json: result.orphans,
|
|
2836
|
-
skills_table_json: result.skills,
|
|
2837
|
-
stale_files_json: result.staleFiles,
|
|
2838
|
-
broken_refs_json: result.brokenRefs,
|
|
2839
|
-
env_manifest_json: result.envManifest,
|
|
2840
|
-
plugin_versions_json: result.pluginVersions,
|
|
2841
|
-
data_hash: result.dataHash,
|
|
2842
|
-
scanned_at: result.scannedAt,
|
|
2843
|
-
cli_version: CURRENT_VERSION
|
|
2844
|
-
}, { onConflict: "folder_id,device_id" });
|
|
2845
|
-
}
|
|
2846
|
-
await pushToolings(supabase, device.folder_id, result.toolings, allDeviceId);
|
|
2847
|
-
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
2848
|
-
console.log(chalk15.green(` Done: ${device.device_name}`));
|
|
2849
|
-
} catch (err) {
|
|
2850
|
-
console.error(chalk15.red(` Failed: ${device.path}: ${err}`));
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2854
|
-
console.log(chalk15.green("\nAll devices synced."));
|
|
2855
|
-
} else {
|
|
2856
|
-
const state = await loadState();
|
|
2857
|
-
if (!state.lastFolderId) {
|
|
2858
|
-
console.error(chalk15.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
|
|
2859
|
-
process.exit(1);
|
|
2860
|
-
}
|
|
2861
|
-
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();
|
|
2862
|
-
if (!device) {
|
|
2863
|
-
console.error(chalk15.red("Could not find last synced device/folder."));
|
|
2864
|
-
process.exit(1);
|
|
2865
|
-
}
|
|
2866
|
-
if (!isValidProjectPath(device.path)) {
|
|
2867
|
-
console.error(chalk15.red(`Invalid project path: ${device.path}`));
|
|
2868
|
-
process.exit(1);
|
|
2869
|
-
}
|
|
2870
|
-
console.log(chalk15.blue(`Syncing: ${device.path}`));
|
|
2871
|
-
const { data: proposedSingle } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
2872
|
-
if (proposedSingle?.length) {
|
|
2873
|
-
console.log(chalk15.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
2874
|
-
}
|
|
2875
|
-
const result = await scanProject(device.path, device.folder_id);
|
|
2876
|
-
const { data: singleDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
|
|
2877
|
-
const singleDeviceId = singleDeviceRow?.id;
|
|
2878
|
-
if (singleDeviceId) {
|
|
2879
|
-
await supabase.from("device_scans").upsert({
|
|
2880
|
-
folder_id: device.folder_id,
|
|
2881
|
-
device_id: singleDeviceId,
|
|
2882
|
-
user_id: userId,
|
|
2883
|
-
graph_json: result.graph,
|
|
2884
|
-
orphans_json: result.orphans,
|
|
2885
|
-
skills_table_json: result.skills,
|
|
2886
|
-
stale_files_json: result.staleFiles,
|
|
2887
|
-
broken_refs_json: result.brokenRefs,
|
|
2888
|
-
plugin_versions_json: result.pluginVersions,
|
|
2889
|
-
data_hash: result.dataHash,
|
|
2890
|
-
scanned_at: result.scannedAt,
|
|
2891
|
-
cli_version: CURRENT_VERSION
|
|
2892
|
-
}, { onConflict: "folder_id,device_id" });
|
|
2893
|
-
}
|
|
2894
|
-
await pushToolings(supabase, device.folder_id, result.toolings, singleDeviceId);
|
|
2895
|
-
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
2896
|
-
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2897
|
-
console.log(chalk15.green("Synced."));
|
|
2898
|
-
}
|
|
2899
|
-
}
|
|
2900
|
-
var init_sync = __esm({
|
|
2901
|
-
"dist/commands/sync.js"() {
|
|
2902
|
-
"use strict";
|
|
2903
|
-
init_auth();
|
|
2904
|
-
init_config();
|
|
2905
|
-
init_scanner();
|
|
2906
|
-
init_push_toolings();
|
|
2907
|
-
init_check_update();
|
|
2908
|
-
init_device_utils();
|
|
2909
|
-
}
|
|
2910
|
-
});
|
|
2911
|
-
|
|
2912
2802
|
// dist/mcp/read-configs.js
|
|
2913
2803
|
import { readFile as readFile12 } from "node:fs/promises";
|
|
2914
2804
|
import { join as join15 } from "node:path";
|
|
2915
2805
|
import { homedir as homedir9 } from "node:os";
|
|
2916
|
-
import { existsSync as
|
|
2806
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
2917
2807
|
import { readdir as readdir6 } from "node:fs/promises";
|
|
2918
2808
|
async function readJsonSafe(path) {
|
|
2919
2809
|
try {
|
|
2920
|
-
if (!
|
|
2810
|
+
if (!existsSync12(path))
|
|
2921
2811
|
return null;
|
|
2922
2812
|
const raw = await readFile12(path, "utf-8");
|
|
2923
2813
|
return JSON.parse(raw);
|
|
@@ -2988,14 +2878,14 @@ async function readAllMcpConfigs() {
|
|
|
2988
2878
|
const cwdMcp = await readJsonSafe(join15(process.cwd(), ".mcp.json"));
|
|
2989
2879
|
entries.push(...parseServers(cwdMcp, "project"));
|
|
2990
2880
|
const pluginsBase = join15(home, ".claude", "plugins", "marketplaces");
|
|
2991
|
-
if (
|
|
2881
|
+
if (existsSync12(pluginsBase)) {
|
|
2992
2882
|
try {
|
|
2993
2883
|
const marketplaces = await readdir6(pluginsBase, { withFileTypes: true });
|
|
2994
2884
|
for (const mp of marketplaces) {
|
|
2995
2885
|
if (!mp.isDirectory())
|
|
2996
2886
|
continue;
|
|
2997
2887
|
const extDir = join15(pluginsBase, mp.name, "external_plugins");
|
|
2998
|
-
if (!
|
|
2888
|
+
if (!existsSync12(extDir))
|
|
2999
2889
|
continue;
|
|
3000
2890
|
const plugins = await readdir6(extDir, { withFileTypes: true });
|
|
3001
2891
|
for (const plugin of plugins) {
|
|
@@ -3009,7 +2899,7 @@ async function readAllMcpConfigs() {
|
|
|
3009
2899
|
}
|
|
3010
2900
|
}
|
|
3011
2901
|
const cacheBase = join15(home, ".claude", "plugins", "cache");
|
|
3012
|
-
if (
|
|
2902
|
+
if (existsSync12(cacheBase)) {
|
|
3013
2903
|
try {
|
|
3014
2904
|
const registries = await readdir6(cacheBase, { withFileTypes: true });
|
|
3015
2905
|
for (const reg of registries) {
|
|
@@ -3174,7 +3064,7 @@ var mcp_watch_exports = {};
|
|
|
3174
3064
|
__export(mcp_watch_exports, {
|
|
3175
3065
|
mcpWatchCommand: () => mcpWatchCommand
|
|
3176
3066
|
});
|
|
3177
|
-
import
|
|
3067
|
+
import chalk20 from "chalk";
|
|
3178
3068
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
3179
3069
|
import { createHash as createHash2 } from "node:crypto";
|
|
3180
3070
|
function detectTty() {
|
|
@@ -3328,20 +3218,20 @@ function buildRows(configs, httpResults, cdpStatus) {
|
|
|
3328
3218
|
}
|
|
3329
3219
|
function printTable(rows, deviceName, watcherPid, cdpResult) {
|
|
3330
3220
|
process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
|
|
3331
|
-
console.log(
|
|
3332
|
-
MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) +
|
|
3333
|
-
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
|
|
3334
3224
|
`));
|
|
3335
3225
|
if (cdpResult) {
|
|
3336
3226
|
if (cdpResult.status === "reachable") {
|
|
3337
3227
|
const browserInfo = cdpResult.browser ? ` (${cdpResult.browser})` : "";
|
|
3338
|
-
console.log(
|
|
3339
|
-
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"));
|
|
3340
3230
|
} else {
|
|
3341
|
-
console.log(
|
|
3342
|
-
console.log(
|
|
3343
|
-
console.log(
|
|
3344
|
-
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"));
|
|
3345
3235
|
}
|
|
3346
3236
|
console.log("");
|
|
3347
3237
|
}
|
|
@@ -3362,41 +3252,41 @@ function printTable(rows, deviceName, watcherPid, cdpResult) {
|
|
|
3362
3252
|
sessionNum++;
|
|
3363
3253
|
const totalMem = servers.reduce((sum, s) => sum + (s.memory_mb ?? 0), 0);
|
|
3364
3254
|
const label = byTty.size === 1 ? "Claude Code session" : `Claude Code session ${sessionNum}`;
|
|
3365
|
-
console.log(
|
|
3255
|
+
console.log(chalk20.green(` ${label}`) + chalk20.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB \xB7 terminal ${tty}`));
|
|
3366
3256
|
for (const s of servers) {
|
|
3367
3257
|
const uptime = formatUptime(s.uptime_seconds ?? 0);
|
|
3368
|
-
const source =
|
|
3369
|
-
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}`);
|
|
3370
3260
|
}
|
|
3371
3261
|
console.log("");
|
|
3372
3262
|
}
|
|
3373
3263
|
if (byTty.size > 1) {
|
|
3374
|
-
console.log(
|
|
3264
|
+
console.log(chalk20.dim(" Each open Claude Code window runs its own set of MCP servers.\n"));
|
|
3375
3265
|
}
|
|
3376
3266
|
}
|
|
3377
3267
|
if (runningHttp.length > 0) {
|
|
3378
|
-
console.log(
|
|
3268
|
+
console.log(chalk20.blue(` Remote Services (${runningHttp.length})`) + chalk20.dim(" \u2014 HTTP endpoints reachable"));
|
|
3379
3269
|
for (const s of runningHttp) {
|
|
3380
|
-
console.log(` ${
|
|
3270
|
+
console.log(` ${chalk20.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk20.dim((s.http_url ?? "").padEnd(30))}`);
|
|
3381
3271
|
}
|
|
3382
3272
|
console.log("");
|
|
3383
3273
|
}
|
|
3384
3274
|
if (stopped.length > 0 || errored.length > 0) {
|
|
3385
3275
|
const notRunning = [...stopped, ...errored];
|
|
3386
|
-
console.log(
|
|
3276
|
+
console.log(chalk20.yellow(` Not Running (${notRunning.length})`) + chalk20.dim(" \u2014 configured but no process detected"));
|
|
3387
3277
|
for (const s of notRunning) {
|
|
3388
|
-
const icon = s.status === "error" ?
|
|
3389
|
-
const source =
|
|
3390
|
-
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}`) : "";
|
|
3391
3281
|
console.log(` ${icon} ${s.server_name.padEnd(20)} ${source}${detail}`);
|
|
3392
3282
|
}
|
|
3393
3283
|
console.log("");
|
|
3394
3284
|
}
|
|
3395
3285
|
if (rows.length === 0) {
|
|
3396
|
-
console.log(
|
|
3397
|
-
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"));
|
|
3398
3288
|
}
|
|
3399
|
-
console.log(
|
|
3289
|
+
console.log(chalk20.bgYellow.black.bold(" \u26A0 DO NOT CLOSE THIS WINDOW \u2014 it feeds live data to the dashboard \u26A0 "));
|
|
3400
3290
|
console.log("");
|
|
3401
3291
|
}
|
|
3402
3292
|
function formatUptime(seconds) {
|
|
@@ -3426,7 +3316,7 @@ async function mcpWatchCommand() {
|
|
|
3426
3316
|
const { data: existingWatchers } = await supabase.from("mcp_watchers").select("pid, tty, cli_version, started_at").eq("device_id", deviceId);
|
|
3427
3317
|
if (existingWatchers && existingWatchers.length > 0) {
|
|
3428
3318
|
console.log("");
|
|
3429
|
-
console.log(
|
|
3319
|
+
console.log(chalk20.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
|
|
3430
3320
|
for (const w of existingWatchers) {
|
|
3431
3321
|
try {
|
|
3432
3322
|
if (typeof w.pid === "number" && w.pid > 1 && w.pid !== process.pid) {
|
|
@@ -3437,15 +3327,15 @@ async function mcpWatchCommand() {
|
|
|
3437
3327
|
}
|
|
3438
3328
|
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId);
|
|
3439
3329
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
3440
|
-
console.log(
|
|
3330
|
+
console.log(chalk20.dim(" Previous watcher stopped.\n"));
|
|
3441
3331
|
}
|
|
3442
3332
|
process.stdout.write(`\x1B]0;MCP mon\x07`);
|
|
3443
|
-
console.log(
|
|
3333
|
+
console.log(chalk20.blue(`Starting MCP monitor for ${deviceName}...`));
|
|
3444
3334
|
console.log("");
|
|
3445
|
-
console.log(
|
|
3446
|
-
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}`));
|
|
3447
3337
|
console.log("");
|
|
3448
|
-
console.log(
|
|
3338
|
+
console.log(chalk20.yellow(" Please note, closing this window will stop ALL watch reports."));
|
|
3449
3339
|
console.log("");
|
|
3450
3340
|
await supabase.from("mcp_watchers").upsert({
|
|
3451
3341
|
device_id: deviceId,
|
|
@@ -3475,7 +3365,7 @@ async function mcpWatchCommand() {
|
|
|
3475
3365
|
return;
|
|
3476
3366
|
}
|
|
3477
3367
|
if (deleteError) {
|
|
3478
|
-
console.error(
|
|
3368
|
+
console.error(chalk20.red(` [debug] Failed to delete old status: ${deleteError.message}`));
|
|
3479
3369
|
}
|
|
3480
3370
|
if (rows.length > 0) {
|
|
3481
3371
|
const { error: insertError } = await supabase.from("mcp_server_status").insert(rows.map((row) => ({
|
|
@@ -3488,7 +3378,7 @@ async function mcpWatchCommand() {
|
|
|
3488
3378
|
return;
|
|
3489
3379
|
}
|
|
3490
3380
|
if (insertError) {
|
|
3491
|
-
console.error(
|
|
3381
|
+
console.error(chalk20.red(` [debug] Failed to write MCP status: ${insertError.message}`));
|
|
3492
3382
|
}
|
|
3493
3383
|
}
|
|
3494
3384
|
const { error: heartbeatError } = await supabase.from("mcp_watchers").update({ last_heartbeat: now }).eq("device_id", deviceId).eq("pid", myPid);
|
|
@@ -3506,11 +3396,11 @@ async function mcpWatchCommand() {
|
|
|
3506
3396
|
return;
|
|
3507
3397
|
}
|
|
3508
3398
|
jwtRefreshAttempted = true;
|
|
3509
|
-
console.log(
|
|
3399
|
+
console.log(chalk20.yellow("\n Session token expired \u2014 attempting to refresh..."));
|
|
3510
3400
|
const refreshed = await refreshSession();
|
|
3511
3401
|
if (refreshed) {
|
|
3512
3402
|
supabase = refreshed.supabase;
|
|
3513
|
-
console.log(
|
|
3403
|
+
console.log(chalk20.green(" Session refreshed successfully. Resuming monitoring.\n"));
|
|
3514
3404
|
jwtRefreshAttempted = false;
|
|
3515
3405
|
await supabase.from("mcp_watchers").upsert({
|
|
3516
3406
|
device_id: deviceId,
|
|
@@ -3528,14 +3418,14 @@ async function mcpWatchCommand() {
|
|
|
3528
3418
|
function printSessionExpired() {
|
|
3529
3419
|
process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
|
|
3530
3420
|
console.log("");
|
|
3531
|
-
console.log(
|
|
3421
|
+
console.log(chalk20.bgRed.white.bold(" \u2717 SESSION EXPIRED \u2014 this watcher is no longer sending data to the dashboard \u2717 "));
|
|
3532
3422
|
console.log("");
|
|
3533
|
-
console.log(
|
|
3534
|
-
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.'));
|
|
3535
3425
|
console.log("");
|
|
3536
|
-
console.log(
|
|
3537
|
-
console.log(
|
|
3538
|
-
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"));
|
|
3539
3429
|
console.log("");
|
|
3540
3430
|
}
|
|
3541
3431
|
let interval;
|
|
@@ -3578,7 +3468,7 @@ async function mcpWatchCommand() {
|
|
|
3578
3468
|
clearInterval(interval);
|
|
3579
3469
|
clearInterval(envInterval);
|
|
3580
3470
|
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).eq("pid", myPid);
|
|
3581
|
-
console.log(
|
|
3471
|
+
console.log(chalk20.dim("\nMCP monitor stopped."));
|
|
3582
3472
|
process.exit(0);
|
|
3583
3473
|
};
|
|
3584
3474
|
process.on("SIGINT", () => {
|
|
@@ -3593,12 +3483,13 @@ async function checkPendingRescans(supabase, deviceId, deviceName) {
|
|
|
3593
3483
|
if (!paths?.length)
|
|
3594
3484
|
return;
|
|
3595
3485
|
const folderIds = paths.map((p) => p.folder_id);
|
|
3596
|
-
const { data: folders } = await supabase.from("claude_folders").select("id,
|
|
3486
|
+
const { data: folders } = await supabase.from("claude_folders").select("id, rescan_requested_at").in("id", folderIds).not("rescan_requested_at", "is", null);
|
|
3597
3487
|
if (!folders?.length)
|
|
3598
3488
|
return;
|
|
3599
3489
|
for (const folder of folders) {
|
|
3600
3490
|
const requested = new Date(folder.rescan_requested_at).getTime();
|
|
3601
|
-
const
|
|
3491
|
+
const { data: scanRow } = await supabase.from("device_scans").select("scanned_at").eq("folder_id", folder.id).eq("device_id", deviceId).maybeSingle();
|
|
3492
|
+
const scanned = scanRow?.scanned_at ? new Date(scanRow.scanned_at).getTime() : 0;
|
|
3602
3493
|
if (requested <= scanned)
|
|
3603
3494
|
continue;
|
|
3604
3495
|
const dp = paths.find((p) => p.folder_id === folder.id);
|
|
@@ -3606,24 +3497,46 @@ async function checkPendingRescans(supabase, deviceId, deviceName) {
|
|
|
3606
3497
|
continue;
|
|
3607
3498
|
try {
|
|
3608
3499
|
const result = await scanProject(dp.path);
|
|
3609
|
-
await supabase.
|
|
3500
|
+
const userId = (await supabase.auth.getUser()).data.user.id;
|
|
3501
|
+
const scanPayload = {
|
|
3610
3502
|
folder_id: folder.id,
|
|
3611
3503
|
device_id: deviceId,
|
|
3612
|
-
user_id:
|
|
3504
|
+
user_id: userId,
|
|
3613
3505
|
graph_json: result.graph,
|
|
3614
3506
|
orphans_json: result.orphans,
|
|
3615
3507
|
skills_table_json: result.skills,
|
|
3616
3508
|
stale_files_json: result.staleFiles,
|
|
3617
3509
|
broken_refs_json: result.brokenRefs,
|
|
3618
3510
|
env_manifest_json: result.envManifest,
|
|
3511
|
+
doppler_json: result.doppler,
|
|
3512
|
+
marketplace_plugins_json: result.marketplacePlugins,
|
|
3513
|
+
plugin_versions_json: result.pluginVersions,
|
|
3619
3514
|
data_hash: result.dataHash,
|
|
3620
3515
|
scanned_at: result.scannedAt,
|
|
3621
3516
|
cli_version: CURRENT_VERSION
|
|
3622
|
-
}
|
|
3517
|
+
};
|
|
3518
|
+
const { data: updated } = await supabase.from("device_scans").update(scanPayload).eq("folder_id", folder.id).eq("device_id", deviceId).select("id");
|
|
3519
|
+
if (!updated?.length) {
|
|
3520
|
+
await supabase.from("device_scans").insert(scanPayload);
|
|
3521
|
+
}
|
|
3623
3522
|
await supabase.from("claude_folders").update({
|
|
3624
3523
|
rescan_requested_at: null
|
|
3625
3524
|
}).eq("id", folder.id);
|
|
3626
3525
|
await pushToolings(supabase, folder.id, result.toolings, deviceId);
|
|
3526
|
+
const graphPaths = result.graph.nodes.map((n) => n.filePath);
|
|
3527
|
+
const { readClaudeConfigFiles: readClaudeConfigFiles2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
|
|
3528
|
+
const configFiles = await readClaudeConfigFiles2(dp.path, graphPaths);
|
|
3529
|
+
for (const cf of configFiles) {
|
|
3530
|
+
await supabase.from("folder_files").upsert({
|
|
3531
|
+
folder_id: folder.id,
|
|
3532
|
+
file_path: cf.filePath,
|
|
3533
|
+
content: cf.content,
|
|
3534
|
+
size_bytes: cf.sizeBytes,
|
|
3535
|
+
last_modified: cf.lastModified,
|
|
3536
|
+
synced_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3537
|
+
device_id: deviceId
|
|
3538
|
+
}, { onConflict: "folder_id,file_path,device_id" });
|
|
3539
|
+
}
|
|
3627
3540
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
|
|
3628
3541
|
} catch {
|
|
3629
3542
|
await supabase.from("claude_folders").update({ rescan_requested_at: null }).eq("id", folder.id);
|
|
@@ -3648,6 +3561,130 @@ var init_mcp_watch = __esm({
|
|
|
3648
3561
|
}
|
|
3649
3562
|
});
|
|
3650
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
|
+
|
|
3651
3688
|
// dist/index.js
|
|
3652
3689
|
import { Command } from "commander";
|
|
3653
3690
|
|
|
@@ -4012,9 +4049,6 @@ function generatePrintHtml(result, title) {
|
|
|
4012
4049
|
</html>`;
|
|
4013
4050
|
}
|
|
4014
4051
|
|
|
4015
|
-
// dist/index.js
|
|
4016
|
-
init_sync();
|
|
4017
|
-
|
|
4018
4052
|
// dist/commands/link.js
|
|
4019
4053
|
init_auth();
|
|
4020
4054
|
init_config();
|
|
@@ -4022,23 +4056,23 @@ init_scanner();
|
|
|
4022
4056
|
init_push_toolings();
|
|
4023
4057
|
init_device_utils();
|
|
4024
4058
|
init_check_update();
|
|
4025
|
-
import { resolve as
|
|
4026
|
-
import
|
|
4059
|
+
import { resolve as resolve5 } from "node:path";
|
|
4060
|
+
import chalk15 from "chalk";
|
|
4027
4061
|
async function linkCommand(projectId) {
|
|
4028
4062
|
const shouldContinue = await promptUpdateIfAvailable(["link", projectId]);
|
|
4029
4063
|
if (!shouldContinue)
|
|
4030
4064
|
return;
|
|
4031
4065
|
const { supabase, userId } = await getAuthenticatedClient();
|
|
4032
|
-
const cwd =
|
|
4066
|
+
const cwd = resolve5(process.cwd());
|
|
4033
4067
|
const deviceName = detectDeviceName();
|
|
4034
4068
|
const osType = detectOs();
|
|
4035
4069
|
const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
|
|
4036
4070
|
if (folderErr || !folder) {
|
|
4037
|
-
console.error(
|
|
4038
|
-
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."));
|
|
4039
4073
|
process.exit(1);
|
|
4040
4074
|
}
|
|
4041
|
-
console.log(
|
|
4075
|
+
console.log(chalk15.blue(`
|
|
4042
4076
|
Linking "${folder.name}" to this device...
|
|
4043
4077
|
`));
|
|
4044
4078
|
console.log(` Project: ${folder.name}`);
|
|
@@ -4064,12 +4098,12 @@ Linking "${folder.name}" to this device...
|
|
|
4064
4098
|
path: cwd
|
|
4065
4099
|
});
|
|
4066
4100
|
if (pathErr) {
|
|
4067
|
-
console.error(
|
|
4101
|
+
console.error(chalk15.red(`Failed to link: ${pathErr.message}`));
|
|
4068
4102
|
process.exit(1);
|
|
4069
4103
|
}
|
|
4070
4104
|
}
|
|
4071
|
-
console.log(
|
|
4072
|
-
console.log(
|
|
4105
|
+
console.log(chalk15.green("\nLinked successfully."));
|
|
4106
|
+
console.log(chalk15.blue("\nRunning initial scan...\n"));
|
|
4073
4107
|
const result = await scanProject(cwd, folder.id);
|
|
4074
4108
|
console.log(` Files: ${result.graph.nodes.length}`);
|
|
4075
4109
|
console.log(` References:${result.graph.edges.length}`);
|
|
@@ -4110,7 +4144,7 @@ Linking "${folder.name}" to this device...
|
|
|
4110
4144
|
device_id: deviceId
|
|
4111
4145
|
}, { onConflict: "folder_id,file_path,device_id" });
|
|
4112
4146
|
}
|
|
4113
|
-
console.log(
|
|
4147
|
+
console.log(chalk15.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
4114
4148
|
}
|
|
4115
4149
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
|
|
4116
4150
|
await saveState({
|
|
@@ -4119,28 +4153,28 @@ Linking "${folder.name}" to this device...
|
|
|
4119
4153
|
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4120
4154
|
});
|
|
4121
4155
|
const projectUrl = `https://www.md4ai.com/project/${folder.id}`;
|
|
4122
|
-
console.log(
|
|
4123
|
-
console.log(
|
|
4156
|
+
console.log(chalk15.green("\nDone! Project linked and scanned."));
|
|
4157
|
+
console.log(chalk15.cyan(`
|
|
4124
4158
|
${projectUrl}
|
|
4125
4159
|
`));
|
|
4126
|
-
console.log(
|
|
4160
|
+
console.log(chalk15.grey('Run "md4ai scan" to rescan at any time.'));
|
|
4127
4161
|
}
|
|
4128
4162
|
|
|
4129
4163
|
// dist/commands/import-bundle.js
|
|
4130
4164
|
import { readFile as readFile11, writeFile as writeFile4, mkdir as mkdir3, stat as fsStat } from "node:fs/promises";
|
|
4131
|
-
import { dirname as dirname3, resolve as
|
|
4132
|
-
import { existsSync as
|
|
4133
|
-
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";
|
|
4134
4168
|
import { confirm as confirm3, input as input6 } from "@inquirer/prompts";
|
|
4135
4169
|
var MAX_BUNDLE_SIZE_BYTES = 50 * 1024 * 1024;
|
|
4136
4170
|
async function importBundleCommand(zipPath) {
|
|
4137
|
-
if (!
|
|
4138
|
-
console.error(
|
|
4171
|
+
if (!existsSync11(zipPath)) {
|
|
4172
|
+
console.error(chalk16.red(`File not found: ${zipPath}`));
|
|
4139
4173
|
process.exit(1);
|
|
4140
4174
|
}
|
|
4141
4175
|
const fileStat = await fsStat(zipPath);
|
|
4142
4176
|
if (fileStat.size > MAX_BUNDLE_SIZE_BYTES) {
|
|
4143
|
-
console.error(
|
|
4177
|
+
console.error(chalk16.red(`Bundle too large (${Math.round(fileStat.size / 1024 / 1024)} MB). Maximum is 50 MB.`));
|
|
4144
4178
|
process.exit(1);
|
|
4145
4179
|
}
|
|
4146
4180
|
const JSZip = (await import("jszip")).default;
|
|
@@ -4148,11 +4182,11 @@ async function importBundleCommand(zipPath) {
|
|
|
4148
4182
|
const zip = await JSZip.loadAsync(zipData);
|
|
4149
4183
|
const manifestFile = zip.file("manifest.json");
|
|
4150
4184
|
if (!manifestFile) {
|
|
4151
|
-
console.error(
|
|
4185
|
+
console.error(chalk16.red("Invalid bundle: missing manifest.json"));
|
|
4152
4186
|
process.exit(1);
|
|
4153
4187
|
}
|
|
4154
4188
|
const manifest = JSON.parse(await manifestFile.async("string"));
|
|
4155
|
-
console.log(
|
|
4189
|
+
console.log(chalk16.blue(`
|
|
4156
4190
|
Bundle: ${manifest.folderName}`));
|
|
4157
4191
|
console.log(` Exported by: ${manifest.ownerEmail}`);
|
|
4158
4192
|
console.log(` Exported at: ${manifest.exportedAt}`);
|
|
@@ -4166,10 +4200,10 @@ Bundle: ${manifest.folderName}`));
|
|
|
4166
4200
|
}
|
|
4167
4201
|
}
|
|
4168
4202
|
if (files.length === 0) {
|
|
4169
|
-
console.log(
|
|
4203
|
+
console.log(chalk16.yellow("No Claude config files found in bundle."));
|
|
4170
4204
|
return;
|
|
4171
4205
|
}
|
|
4172
|
-
console.log(
|
|
4206
|
+
console.log(chalk16.blue(`
|
|
4173
4207
|
Files to extract:`));
|
|
4174
4208
|
for (const f of files) {
|
|
4175
4209
|
console.log(` ${f.filePath}`);
|
|
@@ -4182,39 +4216,39 @@ Files to extract:`));
|
|
|
4182
4216
|
message: `Extract ${files.length} file(s) to ${targetDir}?`
|
|
4183
4217
|
});
|
|
4184
4218
|
if (!proceed) {
|
|
4185
|
-
console.log(
|
|
4219
|
+
console.log(chalk16.yellow("Cancelled."));
|
|
4186
4220
|
return;
|
|
4187
4221
|
}
|
|
4188
|
-
const resolvedTarget =
|
|
4222
|
+
const resolvedTarget = resolve6(targetDir);
|
|
4189
4223
|
for (const file of files) {
|
|
4190
|
-
const fullPath =
|
|
4224
|
+
const fullPath = resolve6(targetDir, file.filePath);
|
|
4191
4225
|
if (!fullPath.startsWith(resolvedTarget + "/") && fullPath !== resolvedTarget) {
|
|
4192
|
-
console.error(
|
|
4226
|
+
console.error(chalk16.red(` Blocked path traversal: ${file.filePath}`));
|
|
4193
4227
|
continue;
|
|
4194
4228
|
}
|
|
4195
4229
|
const dir = dirname3(fullPath);
|
|
4196
|
-
if (!
|
|
4230
|
+
if (!existsSync11(dir)) {
|
|
4197
4231
|
await mkdir3(dir, { recursive: true });
|
|
4198
4232
|
}
|
|
4199
4233
|
await writeFile4(fullPath, file.content, "utf-8");
|
|
4200
|
-
console.log(
|
|
4234
|
+
console.log(chalk16.green(` \u2713 ${file.filePath}`));
|
|
4201
4235
|
}
|
|
4202
|
-
console.log(
|
|
4236
|
+
console.log(chalk16.green(`
|
|
4203
4237
|
Done! ${files.length} file(s) extracted to ${targetDir}`));
|
|
4204
4238
|
}
|
|
4205
4239
|
|
|
4206
4240
|
// dist/commands/admin-update-tool.js
|
|
4207
4241
|
init_auth();
|
|
4208
|
-
import
|
|
4242
|
+
import chalk17 from "chalk";
|
|
4209
4243
|
var VALID_CATEGORIES = ["framework", "runtime", "cli", "mcp", "package", "database", "other"];
|
|
4210
4244
|
async function adminUpdateToolCommand(options) {
|
|
4211
4245
|
const { supabase } = await getAuthenticatedClient();
|
|
4212
4246
|
if (!options.name) {
|
|
4213
|
-
console.error(
|
|
4247
|
+
console.error(chalk17.red("--name is required."));
|
|
4214
4248
|
process.exit(1);
|
|
4215
4249
|
}
|
|
4216
4250
|
if (options.category && !VALID_CATEGORIES.includes(options.category)) {
|
|
4217
|
-
console.error(
|
|
4251
|
+
console.error(chalk17.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
|
|
4218
4252
|
process.exit(1);
|
|
4219
4253
|
}
|
|
4220
4254
|
const { data: existing } = await supabase.from("tools_registry").select("id, name, display_name, category").eq("name", options.name).maybeSingle();
|
|
@@ -4236,13 +4270,13 @@ async function adminUpdateToolCommand(options) {
|
|
|
4236
4270
|
updates.notes = options.notes;
|
|
4237
4271
|
const { error } = await supabase.from("tools_registry").update(updates).eq("id", existing.id);
|
|
4238
4272
|
if (error) {
|
|
4239
|
-
console.error(
|
|
4273
|
+
console.error(chalk17.red(`Failed to update: ${error.message}`));
|
|
4240
4274
|
process.exit(1);
|
|
4241
4275
|
}
|
|
4242
|
-
console.log(
|
|
4276
|
+
console.log(chalk17.green(`Updated "${existing.display_name}" in the registry.`));
|
|
4243
4277
|
} else {
|
|
4244
4278
|
if (!options.display || !options.category) {
|
|
4245
|
-
console.error(
|
|
4279
|
+
console.error(chalk17.red("New tools require --display and --category."));
|
|
4246
4280
|
process.exit(1);
|
|
4247
4281
|
}
|
|
4248
4282
|
const { error } = await supabase.from("tools_registry").insert({
|
|
@@ -4256,25 +4290,25 @@ async function adminUpdateToolCommand(options) {
|
|
|
4256
4290
|
notes: options.notes ?? null
|
|
4257
4291
|
});
|
|
4258
4292
|
if (error) {
|
|
4259
|
-
console.error(
|
|
4293
|
+
console.error(chalk17.red(`Failed to create: ${error.message}`));
|
|
4260
4294
|
process.exit(1);
|
|
4261
4295
|
}
|
|
4262
|
-
console.log(
|
|
4296
|
+
console.log(chalk17.green(`Added "${options.display}" to the registry.`));
|
|
4263
4297
|
}
|
|
4264
4298
|
}
|
|
4265
4299
|
|
|
4266
4300
|
// dist/commands/admin-list-tools.js
|
|
4267
4301
|
init_auth();
|
|
4268
|
-
import
|
|
4302
|
+
import chalk18 from "chalk";
|
|
4269
4303
|
async function adminListToolsCommand() {
|
|
4270
4304
|
const { supabase } = await getAuthenticatedClient();
|
|
4271
4305
|
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("category").order("display_name");
|
|
4272
4306
|
if (error) {
|
|
4273
|
-
console.error(
|
|
4307
|
+
console.error(chalk18.red(`Failed to fetch tools: ${error.message}`));
|
|
4274
4308
|
process.exit(1);
|
|
4275
4309
|
}
|
|
4276
4310
|
if (!tools?.length) {
|
|
4277
|
-
console.log(
|
|
4311
|
+
console.log(chalk18.yellow("No tools in the registry."));
|
|
4278
4312
|
return;
|
|
4279
4313
|
}
|
|
4280
4314
|
const nameW = Math.max(16, ...tools.map((t) => t.display_name.length));
|
|
@@ -4288,7 +4322,7 @@ async function adminListToolsCommand() {
|
|
|
4288
4322
|
"Beta".padEnd(betaW),
|
|
4289
4323
|
"Last Checked"
|
|
4290
4324
|
].join(" ");
|
|
4291
|
-
console.log(
|
|
4325
|
+
console.log(chalk18.bold(header));
|
|
4292
4326
|
console.log("\u2500".repeat(header.length));
|
|
4293
4327
|
for (const tool of tools) {
|
|
4294
4328
|
const lastChecked = tool.updated_at ? formatRelative(new Date(tool.updated_at)) : "\u2014";
|
|
@@ -4301,7 +4335,7 @@ async function adminListToolsCommand() {
|
|
|
4301
4335
|
].join(" ");
|
|
4302
4336
|
console.log(row);
|
|
4303
4337
|
}
|
|
4304
|
-
console.log(
|
|
4338
|
+
console.log(chalk18.grey(`
|
|
4305
4339
|
${tools.length} tool(s) in registry.`));
|
|
4306
4340
|
}
|
|
4307
4341
|
function formatRelative(date) {
|
|
@@ -4319,7 +4353,7 @@ function formatRelative(date) {
|
|
|
4319
4353
|
|
|
4320
4354
|
// dist/commands/admin-fetch-versions.js
|
|
4321
4355
|
init_auth();
|
|
4322
|
-
import
|
|
4356
|
+
import chalk19 from "chalk";
|
|
4323
4357
|
|
|
4324
4358
|
// dist/commands/version-sources.js
|
|
4325
4359
|
var VERSION_SOURCES = {
|
|
@@ -4340,7 +4374,15 @@ var VERSION_SOURCES = {
|
|
|
4340
4374
|
"turborepo": { type: "npm", package: "turbo" },
|
|
4341
4375
|
"npm": { type: "npm", package: "npm" },
|
|
4342
4376
|
"node": { type: "github", repo: "nodejs/node" },
|
|
4343
|
-
"supabase-cli": { type: "npm", package: "supabase" }
|
|
4377
|
+
"supabase-cli": { type: "npm", package: "supabase" },
|
|
4378
|
+
"vercel-cli": { type: "npm", package: "vercel" },
|
|
4379
|
+
"@sentry/node": { type: "npm", package: "@sentry/node" },
|
|
4380
|
+
"react-markdown": { type: "npm", package: "react-markdown" },
|
|
4381
|
+
"rehype-sanitize": { type: "npm", package: "rehype-sanitize" },
|
|
4382
|
+
"@react-email/render": { type: "npm", package: "@react-email/render" },
|
|
4383
|
+
"@anthropic-ai/sdk": { type: "npm", package: "@anthropic-ai/sdk" },
|
|
4384
|
+
"@xyflow/react": { type: "npm", package: "@xyflow/react" },
|
|
4385
|
+
"fflate": { type: "npm", package: "fflate" }
|
|
4344
4386
|
};
|
|
4345
4387
|
|
|
4346
4388
|
// dist/commands/admin-fetch-versions.js
|
|
@@ -4348,11 +4390,11 @@ async function adminFetchVersionsCommand() {
|
|
|
4348
4390
|
const { supabase } = await getAuthenticatedClient();
|
|
4349
4391
|
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("display_name");
|
|
4350
4392
|
if (error) {
|
|
4351
|
-
console.error(
|
|
4393
|
+
console.error(chalk19.red(`Failed to fetch tools: ${error.message}`));
|
|
4352
4394
|
process.exit(1);
|
|
4353
4395
|
}
|
|
4354
4396
|
if (!tools?.length) {
|
|
4355
|
-
console.log(
|
|
4397
|
+
console.log(chalk19.yellow("No tools in the registry."));
|
|
4356
4398
|
}
|
|
4357
4399
|
const { data: allProjectToolings } = await supabase.from("project_toolings").select("tool_name, detection_source").is("tool_id", null);
|
|
4358
4400
|
const registeredNames = new Set((tools ?? []).map((t) => t.name));
|
|
@@ -4363,7 +4405,7 @@ async function adminFetchVersionsCommand() {
|
|
|
4363
4405
|
}
|
|
4364
4406
|
}
|
|
4365
4407
|
if (unregisteredNames.size > 0) {
|
|
4366
|
-
console.log(
|
|
4408
|
+
console.log(chalk19.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
|
|
4367
4409
|
`));
|
|
4368
4410
|
for (const name of unregisteredNames) {
|
|
4369
4411
|
const displayName = name;
|
|
@@ -4381,10 +4423,10 @@ async function adminFetchVersionsCommand() {
|
|
|
4381
4423
|
}
|
|
4382
4424
|
}
|
|
4383
4425
|
if (!tools?.length) {
|
|
4384
|
-
console.log(
|
|
4426
|
+
console.log(chalk19.yellow("No tools to fetch."));
|
|
4385
4427
|
return;
|
|
4386
4428
|
}
|
|
4387
|
-
console.log(
|
|
4429
|
+
console.log(chalk19.bold(`Fetching versions for ${tools.length} tool(s)...
|
|
4388
4430
|
`));
|
|
4389
4431
|
const results = await Promise.all(tools.map(async (tool) => {
|
|
4390
4432
|
const source = VERSION_SOURCES[tool.name] ?? { type: "npm", package: tool.name };
|
|
@@ -4423,10 +4465,10 @@ async function adminFetchVersionsCommand() {
|
|
|
4423
4465
|
"Beta".padEnd(betaW),
|
|
4424
4466
|
"Status".padEnd(statusW)
|
|
4425
4467
|
].join(" ");
|
|
4426
|
-
console.log(
|
|
4468
|
+
console.log(chalk19.bold(header));
|
|
4427
4469
|
console.log("\u2500".repeat(header.length));
|
|
4428
4470
|
for (const result of results) {
|
|
4429
|
-
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;
|
|
4430
4472
|
const row = [
|
|
4431
4473
|
result.displayName.padEnd(nameW),
|
|
4432
4474
|
(result.stable ?? "\u2014").padEnd(stableW),
|
|
@@ -4439,8 +4481,8 @@ async function adminFetchVersionsCommand() {
|
|
|
4439
4481
|
const unchanged = results.filter((r) => r.status === "unchanged").length;
|
|
4440
4482
|
const failed = results.filter((r) => r.status === "failed").length;
|
|
4441
4483
|
const noSource = results.filter((r) => r.status === "no source").length;
|
|
4442
|
-
console.log(
|
|
4443
|
-
${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`));
|
|
4444
4486
|
}
|
|
4445
4487
|
async function fetchVersions(source) {
|
|
4446
4488
|
const controller = new AbortController();
|
|
@@ -4498,10 +4540,10 @@ async function fetchGitHubVersions(repo, signal) {
|
|
|
4498
4540
|
init_mcp_watch();
|
|
4499
4541
|
|
|
4500
4542
|
// dist/commands/init-manifest.js
|
|
4501
|
-
import { resolve as
|
|
4543
|
+
import { resolve as resolve7, join as join16, relative as relative4, dirname as dirname4 } from "node:path";
|
|
4502
4544
|
import { readFile as readFile13, writeFile as writeFile5, mkdir as mkdir4 } from "node:fs/promises";
|
|
4503
|
-
import { existsSync as
|
|
4504
|
-
import
|
|
4545
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
4546
|
+
import chalk21 from "chalk";
|
|
4505
4547
|
var SECRET_PATTERNS = [
|
|
4506
4548
|
/_KEY$/i,
|
|
4507
4549
|
/_SECRET$/i,
|
|
@@ -4518,7 +4560,7 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
4518
4560
|
const apps = [];
|
|
4519
4561
|
for (const envName of [".env", ".env.local", ".env.example"]) {
|
|
4520
4562
|
const envPath = join16(projectRoot, envName);
|
|
4521
|
-
if (
|
|
4563
|
+
if (existsSync13(envPath)) {
|
|
4522
4564
|
const vars = await extractVarNames(envPath);
|
|
4523
4565
|
if (vars.length > 0) {
|
|
4524
4566
|
apps.push({ name: "root", envFilePath: envName, vars });
|
|
@@ -4529,11 +4571,11 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
4529
4571
|
const subdirs = ["web", "cli", "api", "app", "server", "packages"];
|
|
4530
4572
|
for (const sub of subdirs) {
|
|
4531
4573
|
const subDir = join16(projectRoot, sub);
|
|
4532
|
-
if (!
|
|
4574
|
+
if (!existsSync13(subDir))
|
|
4533
4575
|
continue;
|
|
4534
4576
|
for (const envName of [".env.local", ".env", ".env.example"]) {
|
|
4535
4577
|
const envPath = join16(subDir, envName);
|
|
4536
|
-
if (
|
|
4578
|
+
if (existsSync13(envPath)) {
|
|
4537
4579
|
const vars = await extractVarNames(envPath);
|
|
4538
4580
|
if (vars.length > 0) {
|
|
4539
4581
|
apps.push({
|
|
@@ -4593,33 +4635,33 @@ function generateManifest(apps) {
|
|
|
4593
4635
|
return lines.join("\n");
|
|
4594
4636
|
}
|
|
4595
4637
|
async function initManifestCommand() {
|
|
4596
|
-
const projectRoot =
|
|
4638
|
+
const projectRoot = resolve7(process.cwd());
|
|
4597
4639
|
const manifestPath = join16(projectRoot, "docs", "reference", "env-manifest.md");
|
|
4598
|
-
if (
|
|
4599
|
-
console.log(
|
|
4600
|
-
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."));
|
|
4601
4643
|
return;
|
|
4602
4644
|
}
|
|
4603
|
-
console.log(
|
|
4645
|
+
console.log(chalk21.blue("Scanning for .env files...\n"));
|
|
4604
4646
|
const apps = await discoverEnvFiles(projectRoot);
|
|
4605
4647
|
if (apps.length === 0) {
|
|
4606
|
-
console.log(
|
|
4607
|
-
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`));
|
|
4608
4650
|
return;
|
|
4609
4651
|
}
|
|
4610
4652
|
for (const app of apps) {
|
|
4611
|
-
console.log(` ${
|
|
4653
|
+
console.log(` ${chalk21.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
|
|
4612
4654
|
}
|
|
4613
4655
|
const content = generateManifest(apps);
|
|
4614
4656
|
const dir = dirname4(manifestPath);
|
|
4615
|
-
if (!
|
|
4657
|
+
if (!existsSync13(dir)) {
|
|
4616
4658
|
await mkdir4(dir, { recursive: true });
|
|
4617
4659
|
}
|
|
4618
4660
|
await writeFile5(manifestPath, content, "utf-8");
|
|
4619
|
-
console.log(
|
|
4661
|
+
console.log(chalk21.green(`
|
|
4620
4662
|
Manifest created: ${relative4(projectRoot, manifestPath)}`));
|
|
4621
|
-
console.log(
|
|
4622
|
-
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."));
|
|
4623
4665
|
}
|
|
4624
4666
|
|
|
4625
4667
|
// dist/commands/update.js
|
|
@@ -5028,32 +5070,39 @@ init_check_update();
|
|
|
5028
5070
|
var program = new Command();
|
|
5029
5071
|
program.name("md4ai").description("MD4AI \u2014 Claude tooling visualiser").version(CURRENT_VERSION).addHelpText("after", `
|
|
5030
5072
|
Quick start:
|
|
5031
|
-
md4ai
|
|
5032
|
-
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.)`);
|
|
5033
5084
|
program.command("start").description("Check for updates, scan project, and start MCP monitoring \u2014 the all-in-one command").action(startCommand);
|
|
5034
5085
|
program.command("login").description("Log in to MD4AI with email and password").action(loginCommand);
|
|
5035
5086
|
program.command("logout").description("Log out and clear stored credentials").action(logoutCommand);
|
|
5036
5087
|
program.command("status").description("Show login status, device count, folder count, last sync").action(statusCommand);
|
|
5037
|
-
program.command("add-folder").description("Create a new Claude folder").action(addFolderCommand);
|
|
5038
|
-
program.command("add-device").description("Add a device path to a Claude folder").action(addDeviceCommand);
|
|
5039
|
-
program.command("list-devices").description("List all devices and their linked folders").action(listDevicesCommand);
|
|
5040
5088
|
program.command("link <project-id>").description("Link the current directory to a project created in the web dashboard").action(linkCommand);
|
|
5041
5089
|
program.command("scan [path]").description("Scan Claude project files and push results to your dashboard").option("--offline", "Skip pushing to Supabase").action(mapCommand);
|
|
5042
|
-
program.command("simulate <prompt>").description("Show which files Claude would load for a given prompt").action(simulateCommand);
|
|
5043
|
-
program.command("print <title>").description("Generate a printable wall-chart HTML from the last scan").action(printCommand);
|
|
5044
|
-
program.command("sync").description("Re-push latest scan data to Supabase").option("--all", "Sync all folders on all devices").action(syncCommand);
|
|
5045
|
-
program.command("import <zipfile>").description("Import a Claude setup bundle exported from the web dashboard").action(importBundleCommand);
|
|
5046
|
-
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);
|
|
5047
|
-
program.command("check-update").description("Check if a newer version of md4ai is available").action(checkForUpdate);
|
|
5048
5090
|
program.command("mcp-watch").description("Monitor MCP server status on this device (runs until Ctrl+C)").action(mcpWatchCommand);
|
|
5049
|
-
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);
|
|
5050
5092
|
var config = program.command("config").description("Manage CLI configuration");
|
|
5051
5093
|
config.command("set <key> <value>").description("Set a configuration value (e.g. vercel-token)").action(configSetCommand);
|
|
5052
5094
|
var doppler = program.command("doppler").description("Manage Doppler integration");
|
|
5053
5095
|
doppler.command("connect").description("Configure Doppler token for secrets scanning").action(dopplerConnectCommand);
|
|
5054
5096
|
doppler.command("disconnect").description("Remove stored Doppler token").action(dopplerDisconnectCommand);
|
|
5055
5097
|
doppler.command("set-project <slug>").description("Override Doppler project slug for the current linked project").action(dopplerSetProjectCommand);
|
|
5056
|
-
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);
|
|
5057
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);
|
|
5058
5107
|
admin.command("list-tools").description("List all tools in the master registry").action(adminListToolsCommand);
|
|
5059
5108
|
admin.command("fetch-versions").description("Fetch latest stable and beta versions from npm and GitHub").action(adminFetchVersionsCommand);
|
|
@@ -5063,7 +5112,7 @@ if (process.argv.length <= 2) {
|
|
|
5063
5112
|
} else {
|
|
5064
5113
|
program.parseAsync().then(() => {
|
|
5065
5114
|
const ran = program.args[0];
|
|
5066
|
-
const skipAutoCheck = ["update", "
|
|
5115
|
+
const skipAutoCheck = ["update", "mcp-watch", "start"];
|
|
5067
5116
|
if (!skipAutoCheck.includes(ran)) {
|
|
5068
5117
|
autoCheckForUpdate();
|
|
5069
5118
|
}
|