md4ai 0.17.3 → 0.18.1
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 +288 -275
- 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.1" : "0.0.0-dev";
|
|
126
116
|
}
|
|
127
117
|
});
|
|
128
118
|
|
|
@@ -634,7 +624,7 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
634
624
|
import { statSync } from "node:fs";
|
|
635
625
|
function getGitLastModified(filePath, cwd) {
|
|
636
626
|
try {
|
|
637
|
-
const result = execFileSync2("git", ["log", "-1", "--format=%cI", "--", filePath], { cwd, encoding: "utf-8", timeout: 5e3 }).trim();
|
|
627
|
+
const result = execFileSync2("git", ["log", "-1", "--format=%cI", "--", filePath], { cwd, encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
638
628
|
return result || null;
|
|
639
629
|
} catch {
|
|
640
630
|
try {
|
|
@@ -646,7 +636,7 @@ function getGitLastModified(filePath, cwd) {
|
|
|
646
636
|
}
|
|
647
637
|
function getGitCreationDate(filePath, cwd) {
|
|
648
638
|
try {
|
|
649
|
-
const result = execFileSync2("git", ["log", "--diff-filter=A", "--format=%cI", "--", filePath], { cwd, encoding: "utf-8", timeout: 5e3 }).trim();
|
|
639
|
+
const result = execFileSync2("git", ["log", "--diff-filter=A", "--format=%cI", "--", filePath], { cwd, encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
650
640
|
return result || null;
|
|
651
641
|
} catch {
|
|
652
642
|
try {
|
|
@@ -2087,7 +2077,7 @@ __export(scanner_exports, {
|
|
|
2087
2077
|
scanProject: () => scanProject
|
|
2088
2078
|
});
|
|
2089
2079
|
import { readdir as readdir5 } from "node:fs/promises";
|
|
2090
|
-
import { join as join12, relative as relative3 } from "node:path";
|
|
2080
|
+
import { join as join12, relative as relative3, resolve as resolve4 } from "node:path";
|
|
2091
2081
|
import { existsSync as existsSync7 } from "node:fs";
|
|
2092
2082
|
import { homedir as homedir7 } from "node:os";
|
|
2093
2083
|
import { createHash } from "node:crypto";
|
|
@@ -2097,7 +2087,7 @@ async function scanProject(projectRoot, folderId) {
|
|
|
2097
2087
|
const allRefs = [];
|
|
2098
2088
|
const allBrokenRefs = [];
|
|
2099
2089
|
for (const file of allFiles) {
|
|
2100
|
-
const fullPath = file.startsWith("/") ? file :
|
|
2090
|
+
const fullPath = file.startsWith("/") ? file : resolveFilePath(file, projectRoot);
|
|
2101
2091
|
try {
|
|
2102
2092
|
const { refs, brokenRefs: brokenRefs2 } = await parseFileReferences(fullPath, projectRoot);
|
|
2103
2093
|
allRefs.push(...refs);
|
|
@@ -2172,6 +2162,17 @@ async function discoverFiles(projectRoot) {
|
|
|
2172
2162
|
if (existsSync7(plansDir)) {
|
|
2173
2163
|
await walkDir(plansDir, projectRoot, files);
|
|
2174
2164
|
}
|
|
2165
|
+
const absRoot = resolve4(projectRoot);
|
|
2166
|
+
const projectHash = absRoot.replace(/\//g, "-");
|
|
2167
|
+
const memoryDir = join12(homedir7(), ".claude", "projects", projectHash, "memory");
|
|
2168
|
+
if (existsSync7(memoryDir)) {
|
|
2169
|
+
const memEntries = await readdir5(memoryDir, { withFileTypes: true });
|
|
2170
|
+
for (const entry of memEntries) {
|
|
2171
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
2172
|
+
files.push(`.claude/memory/${entry.name}`);
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2175
2176
|
return [...new Set(files)];
|
|
2176
2177
|
}
|
|
2177
2178
|
async function walkDir(dir, projectRoot, files) {
|
|
@@ -2215,7 +2216,7 @@ async function readClaudeConfigFiles(projectRoot, graphFilePaths) {
|
|
|
2215
2216
|
if (relPath.startsWith("~") || relPath.startsWith("/"))
|
|
2216
2217
|
continue;
|
|
2217
2218
|
seen.add(relPath);
|
|
2218
|
-
const fullPath =
|
|
2219
|
+
const fullPath = resolveFilePath(relPath, projectRoot);
|
|
2219
2220
|
if (!existsSync15(fullPath))
|
|
2220
2221
|
continue;
|
|
2221
2222
|
try {
|
|
@@ -2228,11 +2229,19 @@ async function readClaudeConfigFiles(projectRoot, graphFilePaths) {
|
|
|
2228
2229
|
}
|
|
2229
2230
|
return files;
|
|
2230
2231
|
}
|
|
2232
|
+
function resolveFilePath(relPath, projectRoot) {
|
|
2233
|
+
if (relPath.startsWith(".claude/memory/")) {
|
|
2234
|
+
const absRoot = resolve4(projectRoot);
|
|
2235
|
+
const projectHash = absRoot.replace(/\//g, "-");
|
|
2236
|
+
return join12(homedir7(), ".claude", "projects", projectHash, "memory", relPath.replace(".claude/memory/", ""));
|
|
2237
|
+
}
|
|
2238
|
+
return join12(projectRoot, relPath);
|
|
2239
|
+
}
|
|
2231
2240
|
function detectStaleFiles(allFiles, projectRoot) {
|
|
2232
2241
|
const stale = [];
|
|
2233
2242
|
const now = Date.now();
|
|
2234
2243
|
for (const file of allFiles) {
|
|
2235
|
-
const fullPath =
|
|
2244
|
+
const fullPath = resolveFilePath(file, projectRoot);
|
|
2236
2245
|
const lastMod = getGitLastModified(fullPath, projectRoot);
|
|
2237
2246
|
if (lastMod) {
|
|
2238
2247
|
const days = Math.floor((now - new Date(lastMod).getTime()) / (1e3 * 60 * 60 * 24));
|
|
@@ -2521,7 +2530,7 @@ var map_exports = {};
|
|
|
2521
2530
|
__export(map_exports, {
|
|
2522
2531
|
mapCommand: () => mapCommand
|
|
2523
2532
|
});
|
|
2524
|
-
import { resolve as
|
|
2533
|
+
import { resolve as resolve5, basename as basename2 } from "node:path";
|
|
2525
2534
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
2526
2535
|
import { existsSync as existsSync8 } from "node:fs";
|
|
2527
2536
|
import chalk12 from "chalk";
|
|
@@ -2533,7 +2542,7 @@ async function mapCommand(path, options) {
|
|
|
2533
2542
|
const shouldContinue = await promptUpdateIfAvailable(args);
|
|
2534
2543
|
if (!shouldContinue)
|
|
2535
2544
|
return;
|
|
2536
|
-
const projectRoot =
|
|
2545
|
+
const projectRoot = resolve5(path ?? process.cwd());
|
|
2537
2546
|
if (!existsSync8(projectRoot)) {
|
|
2538
2547
|
console.error(chalk12.red(`Path not found: ${projectRoot}`));
|
|
2539
2548
|
process.exit(1);
|
|
@@ -2571,11 +2580,11 @@ async function mapCommand(path, options) {
|
|
|
2571
2580
|
console.log(chalk12.red(` ... and ${result.brokenRefs.length - 5} more`));
|
|
2572
2581
|
}
|
|
2573
2582
|
}
|
|
2574
|
-
const outputDir =
|
|
2583
|
+
const outputDir = resolve5(projectRoot, "output");
|
|
2575
2584
|
if (!existsSync8(outputDir)) {
|
|
2576
2585
|
await mkdir2(outputDir, { recursive: true });
|
|
2577
2586
|
}
|
|
2578
|
-
const htmlPath =
|
|
2587
|
+
const htmlPath = resolve5(outputDir, "index.html");
|
|
2579
2588
|
const html = generateOfflineHtml(result, projectRoot);
|
|
2580
2589
|
await writeFile2(htmlPath, html, "utf-8");
|
|
2581
2590
|
console.log(chalk12.green(`
|
|
@@ -2601,9 +2610,9 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
2601
2610
|
value: f
|
|
2602
2611
|
}))
|
|
2603
2612
|
});
|
|
2604
|
-
const resolvedRoot =
|
|
2613
|
+
const resolvedRoot = resolve5(projectRoot);
|
|
2605
2614
|
for (const file of toDelete) {
|
|
2606
|
-
const fullPath =
|
|
2615
|
+
const fullPath = resolve5(projectRoot, file.file_path);
|
|
2607
2616
|
if (!fullPath.startsWith(resolvedRoot + "/")) {
|
|
2608
2617
|
console.error(chalk12.red(` Blocked path traversal: ${file.file_path}`));
|
|
2609
2618
|
continue;
|
|
@@ -2809,139 +2818,15 @@ var init_map = __esm({
|
|
|
2809
2818
|
}
|
|
2810
2819
|
});
|
|
2811
2820
|
|
|
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
2821
|
// dist/mcp/read-configs.js
|
|
2937
2822
|
import { readFile as readFile12 } from "node:fs/promises";
|
|
2938
2823
|
import { join as join15 } from "node:path";
|
|
2939
2824
|
import { homedir as homedir9 } from "node:os";
|
|
2940
|
-
import { existsSync as
|
|
2825
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
2941
2826
|
import { readdir as readdir6 } from "node:fs/promises";
|
|
2942
2827
|
async function readJsonSafe(path) {
|
|
2943
2828
|
try {
|
|
2944
|
-
if (!
|
|
2829
|
+
if (!existsSync12(path))
|
|
2945
2830
|
return null;
|
|
2946
2831
|
const raw = await readFile12(path, "utf-8");
|
|
2947
2832
|
return JSON.parse(raw);
|
|
@@ -3012,14 +2897,14 @@ async function readAllMcpConfigs() {
|
|
|
3012
2897
|
const cwdMcp = await readJsonSafe(join15(process.cwd(), ".mcp.json"));
|
|
3013
2898
|
entries.push(...parseServers(cwdMcp, "project"));
|
|
3014
2899
|
const pluginsBase = join15(home, ".claude", "plugins", "marketplaces");
|
|
3015
|
-
if (
|
|
2900
|
+
if (existsSync12(pluginsBase)) {
|
|
3016
2901
|
try {
|
|
3017
2902
|
const marketplaces = await readdir6(pluginsBase, { withFileTypes: true });
|
|
3018
2903
|
for (const mp of marketplaces) {
|
|
3019
2904
|
if (!mp.isDirectory())
|
|
3020
2905
|
continue;
|
|
3021
2906
|
const extDir = join15(pluginsBase, mp.name, "external_plugins");
|
|
3022
|
-
if (!
|
|
2907
|
+
if (!existsSync12(extDir))
|
|
3023
2908
|
continue;
|
|
3024
2909
|
const plugins = await readdir6(extDir, { withFileTypes: true });
|
|
3025
2910
|
for (const plugin of plugins) {
|
|
@@ -3033,7 +2918,7 @@ async function readAllMcpConfigs() {
|
|
|
3033
2918
|
}
|
|
3034
2919
|
}
|
|
3035
2920
|
const cacheBase = join15(home, ".claude", "plugins", "cache");
|
|
3036
|
-
if (
|
|
2921
|
+
if (existsSync12(cacheBase)) {
|
|
3037
2922
|
try {
|
|
3038
2923
|
const registries = await readdir6(cacheBase, { withFileTypes: true });
|
|
3039
2924
|
for (const reg of registries) {
|
|
@@ -3198,7 +3083,7 @@ var mcp_watch_exports = {};
|
|
|
3198
3083
|
__export(mcp_watch_exports, {
|
|
3199
3084
|
mcpWatchCommand: () => mcpWatchCommand
|
|
3200
3085
|
});
|
|
3201
|
-
import
|
|
3086
|
+
import chalk20 from "chalk";
|
|
3202
3087
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
3203
3088
|
import { createHash as createHash2 } from "node:crypto";
|
|
3204
3089
|
function detectTty() {
|
|
@@ -3352,20 +3237,20 @@ function buildRows(configs, httpResults, cdpStatus) {
|
|
|
3352
3237
|
}
|
|
3353
3238
|
function printTable(rows, deviceName, watcherPid, cdpResult) {
|
|
3354
3239
|
process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
|
|
3355
|
-
console.log(
|
|
3356
|
-
MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) +
|
|
3357
|
-
console.log(
|
|
3240
|
+
console.log(chalk20.bold.cyan(`
|
|
3241
|
+
MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) + chalk20.dim(` (PID ${watcherPid})`));
|
|
3242
|
+
console.log(chalk20.dim(` ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \xB7 refreshes every 30s \xB7 Ctrl+C to stop
|
|
3358
3243
|
`));
|
|
3359
3244
|
if (cdpResult) {
|
|
3360
3245
|
if (cdpResult.status === "reachable") {
|
|
3361
3246
|
const browserInfo = cdpResult.browser ? ` (${cdpResult.browser})` : "";
|
|
3362
|
-
console.log(
|
|
3363
|
-
console.log(
|
|
3247
|
+
console.log(chalk20.green(" \u2714 Chrome browser connected") + chalk20.dim(browserInfo));
|
|
3248
|
+
console.log(chalk20.dim(" Claude Code can control Chrome via DevTools Protocol on localhost:9222"));
|
|
3364
3249
|
} else {
|
|
3365
|
-
console.log(
|
|
3366
|
-
console.log(
|
|
3367
|
-
console.log(
|
|
3368
|
-
console.log(
|
|
3250
|
+
console.log(chalk20.red(" \u2717 Chrome browser not connected"));
|
|
3251
|
+
console.log(chalk20.dim(" Claude Code cannot reach Chrome. To fix:"));
|
|
3252
|
+
console.log(chalk20.dim(" 1. Open Chrome with: google-chrome --remote-debugging-port=9222"));
|
|
3253
|
+
console.log(chalk20.dim(" 2. Or add --remote-debugging-port=9222 to your Chrome shortcut"));
|
|
3369
3254
|
}
|
|
3370
3255
|
console.log("");
|
|
3371
3256
|
}
|
|
@@ -3386,41 +3271,41 @@ function printTable(rows, deviceName, watcherPid, cdpResult) {
|
|
|
3386
3271
|
sessionNum++;
|
|
3387
3272
|
const totalMem = servers.reduce((sum, s) => sum + (s.memory_mb ?? 0), 0);
|
|
3388
3273
|
const label = byTty.size === 1 ? "Claude Code session" : `Claude Code session ${sessionNum}`;
|
|
3389
|
-
console.log(
|
|
3274
|
+
console.log(chalk20.green(` ${label}`) + chalk20.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB \xB7 terminal ${tty}`));
|
|
3390
3275
|
for (const s of servers) {
|
|
3391
3276
|
const uptime = formatUptime(s.uptime_seconds ?? 0);
|
|
3392
|
-
const source =
|
|
3393
|
-
console.log(` ${
|
|
3277
|
+
const source = chalk20.dim(`[${s.config_source}]`);
|
|
3278
|
+
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
3279
|
}
|
|
3395
3280
|
console.log("");
|
|
3396
3281
|
}
|
|
3397
3282
|
if (byTty.size > 1) {
|
|
3398
|
-
console.log(
|
|
3283
|
+
console.log(chalk20.dim(" Each open Claude Code window runs its own set of MCP servers.\n"));
|
|
3399
3284
|
}
|
|
3400
3285
|
}
|
|
3401
3286
|
if (runningHttp.length > 0) {
|
|
3402
|
-
console.log(
|
|
3287
|
+
console.log(chalk20.blue(` Remote Services (${runningHttp.length})`) + chalk20.dim(" \u2014 HTTP endpoints reachable"));
|
|
3403
3288
|
for (const s of runningHttp) {
|
|
3404
|
-
console.log(` ${
|
|
3289
|
+
console.log(` ${chalk20.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk20.dim((s.http_url ?? "").padEnd(30))}`);
|
|
3405
3290
|
}
|
|
3406
3291
|
console.log("");
|
|
3407
3292
|
}
|
|
3408
3293
|
if (stopped.length > 0 || errored.length > 0) {
|
|
3409
3294
|
const notRunning = [...stopped, ...errored];
|
|
3410
|
-
console.log(
|
|
3295
|
+
console.log(chalk20.yellow(` Not Running (${notRunning.length})`) + chalk20.dim(" \u2014 configured but no process detected"));
|
|
3411
3296
|
for (const s of notRunning) {
|
|
3412
|
-
const icon = s.status === "error" ?
|
|
3413
|
-
const source =
|
|
3414
|
-
const detail = s.error_detail ?
|
|
3297
|
+
const icon = s.status === "error" ? chalk20.red("\u2717") : chalk20.yellow("\u25CB");
|
|
3298
|
+
const source = chalk20.dim(`[${s.config_source}]`);
|
|
3299
|
+
const detail = s.error_detail ? chalk20.dim(` \u2014 ${s.error_detail}`) : "";
|
|
3415
3300
|
console.log(` ${icon} ${s.server_name.padEnd(20)} ${source}${detail}`);
|
|
3416
3301
|
}
|
|
3417
3302
|
console.log("");
|
|
3418
3303
|
}
|
|
3419
3304
|
if (rows.length === 0) {
|
|
3420
|
-
console.log(
|
|
3421
|
-
console.log(
|
|
3305
|
+
console.log(chalk20.yellow(" No MCP servers configured."));
|
|
3306
|
+
console.log(chalk20.dim(" Configure servers in ~/.claude/mcp.json or .mcp.json\n"));
|
|
3422
3307
|
}
|
|
3423
|
-
console.log(
|
|
3308
|
+
console.log(chalk20.bgYellow.black.bold(" \u26A0 DO NOT CLOSE THIS WINDOW \u2014 it feeds live data to the dashboard \u26A0 "));
|
|
3424
3309
|
console.log("");
|
|
3425
3310
|
}
|
|
3426
3311
|
function formatUptime(seconds) {
|
|
@@ -3450,7 +3335,7 @@ async function mcpWatchCommand() {
|
|
|
3450
3335
|
const { data: existingWatchers } = await supabase.from("mcp_watchers").select("pid, tty, cli_version, started_at").eq("device_id", deviceId);
|
|
3451
3336
|
if (existingWatchers && existingWatchers.length > 0) {
|
|
3452
3337
|
console.log("");
|
|
3453
|
-
console.log(
|
|
3338
|
+
console.log(chalk20.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
|
|
3454
3339
|
for (const w of existingWatchers) {
|
|
3455
3340
|
try {
|
|
3456
3341
|
if (typeof w.pid === "number" && w.pid > 1 && w.pid !== process.pid) {
|
|
@@ -3461,15 +3346,15 @@ async function mcpWatchCommand() {
|
|
|
3461
3346
|
}
|
|
3462
3347
|
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId);
|
|
3463
3348
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
3464
|
-
console.log(
|
|
3349
|
+
console.log(chalk20.dim(" Previous watcher stopped.\n"));
|
|
3465
3350
|
}
|
|
3466
3351
|
process.stdout.write(`\x1B]0;MCP mon\x07`);
|
|
3467
|
-
console.log(
|
|
3352
|
+
console.log(chalk20.blue(`Starting MCP monitor for ${deviceName}...`));
|
|
3468
3353
|
console.log("");
|
|
3469
|
-
console.log(
|
|
3470
|
-
console.log(
|
|
3354
|
+
console.log(chalk20.dim(" View this in the online dashboard at:"));
|
|
3355
|
+
console.log(chalk20.cyan(` https://www.md4ai.com/device/${deviceId}`));
|
|
3471
3356
|
console.log("");
|
|
3472
|
-
console.log(
|
|
3357
|
+
console.log(chalk20.yellow(" Please note, closing this window will stop ALL watch reports."));
|
|
3473
3358
|
console.log("");
|
|
3474
3359
|
await supabase.from("mcp_watchers").upsert({
|
|
3475
3360
|
device_id: deviceId,
|
|
@@ -3499,7 +3384,7 @@ async function mcpWatchCommand() {
|
|
|
3499
3384
|
return;
|
|
3500
3385
|
}
|
|
3501
3386
|
if (deleteError) {
|
|
3502
|
-
console.error(
|
|
3387
|
+
console.error(chalk20.red(` [debug] Failed to delete old status: ${deleteError.message}`));
|
|
3503
3388
|
}
|
|
3504
3389
|
if (rows.length > 0) {
|
|
3505
3390
|
const { error: insertError } = await supabase.from("mcp_server_status").insert(rows.map((row) => ({
|
|
@@ -3512,7 +3397,7 @@ async function mcpWatchCommand() {
|
|
|
3512
3397
|
return;
|
|
3513
3398
|
}
|
|
3514
3399
|
if (insertError) {
|
|
3515
|
-
console.error(
|
|
3400
|
+
console.error(chalk20.red(` [debug] Failed to write MCP status: ${insertError.message}`));
|
|
3516
3401
|
}
|
|
3517
3402
|
}
|
|
3518
3403
|
const { error: heartbeatError } = await supabase.from("mcp_watchers").update({ last_heartbeat: now }).eq("device_id", deviceId).eq("pid", myPid);
|
|
@@ -3530,11 +3415,11 @@ async function mcpWatchCommand() {
|
|
|
3530
3415
|
return;
|
|
3531
3416
|
}
|
|
3532
3417
|
jwtRefreshAttempted = true;
|
|
3533
|
-
console.log(
|
|
3418
|
+
console.log(chalk20.yellow("\n Session token expired \u2014 attempting to refresh..."));
|
|
3534
3419
|
const refreshed = await refreshSession();
|
|
3535
3420
|
if (refreshed) {
|
|
3536
3421
|
supabase = refreshed.supabase;
|
|
3537
|
-
console.log(
|
|
3422
|
+
console.log(chalk20.green(" Session refreshed successfully. Resuming monitoring.\n"));
|
|
3538
3423
|
jwtRefreshAttempted = false;
|
|
3539
3424
|
await supabase.from("mcp_watchers").upsert({
|
|
3540
3425
|
device_id: deviceId,
|
|
@@ -3552,14 +3437,14 @@ async function mcpWatchCommand() {
|
|
|
3552
3437
|
function printSessionExpired() {
|
|
3553
3438
|
process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
|
|
3554
3439
|
console.log("");
|
|
3555
|
-
console.log(
|
|
3440
|
+
console.log(chalk20.bgRed.white.bold(" \u2717 SESSION EXPIRED \u2014 this watcher is no longer sending data to the dashboard \u2717 "));
|
|
3556
3441
|
console.log("");
|
|
3557
|
-
console.log(
|
|
3558
|
-
console.log(
|
|
3442
|
+
console.log(chalk20.yellow(" Your authentication token has expired and could not be refreshed."));
|
|
3443
|
+
console.log(chalk20.yellow(' The dashboard will show "No watchers" because this process can no longer update it.'));
|
|
3559
3444
|
console.log("");
|
|
3560
|
-
console.log(
|
|
3561
|
-
console.log(
|
|
3562
|
-
console.log(
|
|
3445
|
+
console.log(chalk20.white(" To fix this:"));
|
|
3446
|
+
console.log(chalk20.cyan(" 1. Run: md4ai login"));
|
|
3447
|
+
console.log(chalk20.cyan(" 2. Then restart the watcher: md4ai mcp-watch"));
|
|
3563
3448
|
console.log("");
|
|
3564
3449
|
}
|
|
3565
3450
|
let interval;
|
|
@@ -3602,7 +3487,7 @@ async function mcpWatchCommand() {
|
|
|
3602
3487
|
clearInterval(interval);
|
|
3603
3488
|
clearInterval(envInterval);
|
|
3604
3489
|
await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).eq("pid", myPid);
|
|
3605
|
-
console.log(
|
|
3490
|
+
console.log(chalk20.dim("\nMCP monitor stopped."));
|
|
3606
3491
|
process.exit(0);
|
|
3607
3492
|
};
|
|
3608
3493
|
process.on("SIGINT", () => {
|
|
@@ -3695,6 +3580,130 @@ var init_mcp_watch = __esm({
|
|
|
3695
3580
|
}
|
|
3696
3581
|
});
|
|
3697
3582
|
|
|
3583
|
+
// dist/commands/sync.js
|
|
3584
|
+
var sync_exports = {};
|
|
3585
|
+
__export(sync_exports, {
|
|
3586
|
+
syncCommand: () => syncCommand
|
|
3587
|
+
});
|
|
3588
|
+
import { resolve as resolve9 } from "node:path";
|
|
3589
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
3590
|
+
import chalk22 from "chalk";
|
|
3591
|
+
function isValidProjectPath(p) {
|
|
3592
|
+
const resolved = resolve9(p);
|
|
3593
|
+
return resolved.startsWith("/") && !resolved.includes("..") && existsSync14(resolved);
|
|
3594
|
+
}
|
|
3595
|
+
async function syncCommand(options) {
|
|
3596
|
+
const { supabase, userId } = await getAuthenticatedClient();
|
|
3597
|
+
if (options.all) {
|
|
3598
|
+
const currentDeviceName = detectDeviceName();
|
|
3599
|
+
const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path").eq("device_name", currentDeviceName);
|
|
3600
|
+
if (error || !devices?.length) {
|
|
3601
|
+
console.log(chalk22.yellow(`
|
|
3602
|
+
No projects linked on ${currentDeviceName} yet.
|
|
3603
|
+
`));
|
|
3604
|
+
console.log(chalk22.dim(" To link a project, cd into its folder and run:"));
|
|
3605
|
+
console.log(chalk22.cyan(" md4ai scan"));
|
|
3606
|
+
console.log(chalk22.dim(" Or link an existing project from the dashboard:"));
|
|
3607
|
+
console.log(chalk22.cyan(" md4ai link <project-id>\n"));
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
for (const device of devices) {
|
|
3611
|
+
if (!isValidProjectPath(device.path)) {
|
|
3612
|
+
console.error(chalk22.red(` Skipping invalid path: ${device.path}`));
|
|
3613
|
+
continue;
|
|
3614
|
+
}
|
|
3615
|
+
console.log(chalk22.blue(`Syncing: ${device.path}`));
|
|
3616
|
+
const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
3617
|
+
if (proposedAll?.length) {
|
|
3618
|
+
console.log(chalk22.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
3619
|
+
}
|
|
3620
|
+
try {
|
|
3621
|
+
const result = await scanProject(device.path, device.folder_id);
|
|
3622
|
+
const { data: allDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
|
|
3623
|
+
const allDeviceId = allDeviceRow?.id;
|
|
3624
|
+
if (allDeviceId) {
|
|
3625
|
+
await supabase.from("device_scans").upsert({
|
|
3626
|
+
folder_id: device.folder_id,
|
|
3627
|
+
device_id: allDeviceId,
|
|
3628
|
+
user_id: userId,
|
|
3629
|
+
graph_json: result.graph,
|
|
3630
|
+
orphans_json: result.orphans,
|
|
3631
|
+
skills_table_json: result.skills,
|
|
3632
|
+
stale_files_json: result.staleFiles,
|
|
3633
|
+
broken_refs_json: result.brokenRefs,
|
|
3634
|
+
env_manifest_json: result.envManifest,
|
|
3635
|
+
plugin_versions_json: result.pluginVersions,
|
|
3636
|
+
data_hash: result.dataHash,
|
|
3637
|
+
scanned_at: result.scannedAt,
|
|
3638
|
+
cli_version: CURRENT_VERSION
|
|
3639
|
+
}, { onConflict: "folder_id,device_id" });
|
|
3640
|
+
}
|
|
3641
|
+
await pushToolings(supabase, device.folder_id, result.toolings, allDeviceId);
|
|
3642
|
+
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
3643
|
+
console.log(chalk22.green(` Done: ${device.device_name}`));
|
|
3644
|
+
} catch (err) {
|
|
3645
|
+
console.error(chalk22.red(` Failed: ${device.path}: ${err}`));
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3649
|
+
console.log(chalk22.green("\nAll devices synced."));
|
|
3650
|
+
} else {
|
|
3651
|
+
const state = await loadState();
|
|
3652
|
+
if (!state.lastFolderId) {
|
|
3653
|
+
console.error(chalk22.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
|
|
3654
|
+
process.exit(1);
|
|
3655
|
+
}
|
|
3656
|
+
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();
|
|
3657
|
+
if (!device) {
|
|
3658
|
+
console.error(chalk22.red("Could not find last synced device/folder."));
|
|
3659
|
+
process.exit(1);
|
|
3660
|
+
}
|
|
3661
|
+
if (!isValidProjectPath(device.path)) {
|
|
3662
|
+
console.error(chalk22.red(`Invalid project path: ${device.path}`));
|
|
3663
|
+
process.exit(1);
|
|
3664
|
+
}
|
|
3665
|
+
console.log(chalk22.blue(`Syncing: ${device.path}`));
|
|
3666
|
+
const { data: proposedSingle } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
|
|
3667
|
+
if (proposedSingle?.length) {
|
|
3668
|
+
console.log(chalk22.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
|
|
3669
|
+
}
|
|
3670
|
+
const result = await scanProject(device.path, device.folder_id);
|
|
3671
|
+
const { data: singleDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
|
|
3672
|
+
const singleDeviceId = singleDeviceRow?.id;
|
|
3673
|
+
if (singleDeviceId) {
|
|
3674
|
+
await supabase.from("device_scans").upsert({
|
|
3675
|
+
folder_id: device.folder_id,
|
|
3676
|
+
device_id: singleDeviceId,
|
|
3677
|
+
user_id: userId,
|
|
3678
|
+
graph_json: result.graph,
|
|
3679
|
+
orphans_json: result.orphans,
|
|
3680
|
+
skills_table_json: result.skills,
|
|
3681
|
+
stale_files_json: result.staleFiles,
|
|
3682
|
+
broken_refs_json: result.brokenRefs,
|
|
3683
|
+
plugin_versions_json: result.pluginVersions,
|
|
3684
|
+
data_hash: result.dataHash,
|
|
3685
|
+
scanned_at: result.scannedAt,
|
|
3686
|
+
cli_version: CURRENT_VERSION
|
|
3687
|
+
}, { onConflict: "folder_id,device_id" });
|
|
3688
|
+
}
|
|
3689
|
+
await pushToolings(supabase, device.folder_id, result.toolings, singleDeviceId);
|
|
3690
|
+
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
|
|
3691
|
+
await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3692
|
+
console.log(chalk22.green("Synced."));
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
var init_sync = __esm({
|
|
3696
|
+
"dist/commands/sync.js"() {
|
|
3697
|
+
"use strict";
|
|
3698
|
+
init_auth();
|
|
3699
|
+
init_config();
|
|
3700
|
+
init_scanner();
|
|
3701
|
+
init_push_toolings();
|
|
3702
|
+
init_check_update();
|
|
3703
|
+
init_device_utils();
|
|
3704
|
+
}
|
|
3705
|
+
});
|
|
3706
|
+
|
|
3698
3707
|
// dist/index.js
|
|
3699
3708
|
import { Command } from "commander";
|
|
3700
3709
|
|
|
@@ -4059,9 +4068,6 @@ function generatePrintHtml(result, title) {
|
|
|
4059
4068
|
</html>`;
|
|
4060
4069
|
}
|
|
4061
4070
|
|
|
4062
|
-
// dist/index.js
|
|
4063
|
-
init_sync();
|
|
4064
|
-
|
|
4065
4071
|
// dist/commands/link.js
|
|
4066
4072
|
init_auth();
|
|
4067
4073
|
init_config();
|
|
@@ -4070,7 +4076,7 @@ init_push_toolings();
|
|
|
4070
4076
|
init_device_utils();
|
|
4071
4077
|
init_check_update();
|
|
4072
4078
|
import { resolve as resolve6 } from "node:path";
|
|
4073
|
-
import
|
|
4079
|
+
import chalk15 from "chalk";
|
|
4074
4080
|
async function linkCommand(projectId) {
|
|
4075
4081
|
const shouldContinue = await promptUpdateIfAvailable(["link", projectId]);
|
|
4076
4082
|
if (!shouldContinue)
|
|
@@ -4081,11 +4087,11 @@ async function linkCommand(projectId) {
|
|
|
4081
4087
|
const osType = detectOs();
|
|
4082
4088
|
const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
|
|
4083
4089
|
if (folderErr || !folder) {
|
|
4084
|
-
console.error(
|
|
4085
|
-
console.error(
|
|
4090
|
+
console.error(chalk15.red("Project not found, or you do not have access."));
|
|
4091
|
+
console.error(chalk15.yellow("Check the project ID in the MD4AI web dashboard."));
|
|
4086
4092
|
process.exit(1);
|
|
4087
4093
|
}
|
|
4088
|
-
console.log(
|
|
4094
|
+
console.log(chalk15.blue(`
|
|
4089
4095
|
Linking "${folder.name}" to this device...
|
|
4090
4096
|
`));
|
|
4091
4097
|
console.log(` Project: ${folder.name}`);
|
|
@@ -4111,12 +4117,12 @@ Linking "${folder.name}" to this device...
|
|
|
4111
4117
|
path: cwd
|
|
4112
4118
|
});
|
|
4113
4119
|
if (pathErr) {
|
|
4114
|
-
console.error(
|
|
4120
|
+
console.error(chalk15.red(`Failed to link: ${pathErr.message}`));
|
|
4115
4121
|
process.exit(1);
|
|
4116
4122
|
}
|
|
4117
4123
|
}
|
|
4118
|
-
console.log(
|
|
4119
|
-
console.log(
|
|
4124
|
+
console.log(chalk15.green("\nLinked successfully."));
|
|
4125
|
+
console.log(chalk15.blue("\nRunning initial scan...\n"));
|
|
4120
4126
|
const result = await scanProject(cwd, folder.id);
|
|
4121
4127
|
console.log(` Files: ${result.graph.nodes.length}`);
|
|
4122
4128
|
console.log(` References:${result.graph.edges.length}`);
|
|
@@ -4157,7 +4163,7 @@ Linking "${folder.name}" to this device...
|
|
|
4157
4163
|
device_id: deviceId
|
|
4158
4164
|
}, { onConflict: "folder_id,file_path,device_id" });
|
|
4159
4165
|
}
|
|
4160
|
-
console.log(
|
|
4166
|
+
console.log(chalk15.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
4161
4167
|
}
|
|
4162
4168
|
await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
|
|
4163
4169
|
await saveState({
|
|
@@ -4166,28 +4172,28 @@ Linking "${folder.name}" to this device...
|
|
|
4166
4172
|
lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4167
4173
|
});
|
|
4168
4174
|
const projectUrl = `https://www.md4ai.com/project/${folder.id}`;
|
|
4169
|
-
console.log(
|
|
4170
|
-
console.log(
|
|
4175
|
+
console.log(chalk15.green("\nDone! Project linked and scanned."));
|
|
4176
|
+
console.log(chalk15.cyan(`
|
|
4171
4177
|
${projectUrl}
|
|
4172
4178
|
`));
|
|
4173
|
-
console.log(
|
|
4179
|
+
console.log(chalk15.grey('Run "md4ai scan" to rescan at any time.'));
|
|
4174
4180
|
}
|
|
4175
4181
|
|
|
4176
4182
|
// dist/commands/import-bundle.js
|
|
4177
4183
|
import { readFile as readFile11, writeFile as writeFile4, mkdir as mkdir3, stat as fsStat } from "node:fs/promises";
|
|
4178
4184
|
import { dirname as dirname3, resolve as resolve7 } from "node:path";
|
|
4179
|
-
import { existsSync as
|
|
4180
|
-
import
|
|
4185
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
4186
|
+
import chalk16 from "chalk";
|
|
4181
4187
|
import { confirm as confirm3, input as input6 } from "@inquirer/prompts";
|
|
4182
4188
|
var MAX_BUNDLE_SIZE_BYTES = 50 * 1024 * 1024;
|
|
4183
4189
|
async function importBundleCommand(zipPath) {
|
|
4184
|
-
if (!
|
|
4185
|
-
console.error(
|
|
4190
|
+
if (!existsSync11(zipPath)) {
|
|
4191
|
+
console.error(chalk16.red(`File not found: ${zipPath}`));
|
|
4186
4192
|
process.exit(1);
|
|
4187
4193
|
}
|
|
4188
4194
|
const fileStat = await fsStat(zipPath);
|
|
4189
4195
|
if (fileStat.size > MAX_BUNDLE_SIZE_BYTES) {
|
|
4190
|
-
console.error(
|
|
4196
|
+
console.error(chalk16.red(`Bundle too large (${Math.round(fileStat.size / 1024 / 1024)} MB). Maximum is 50 MB.`));
|
|
4191
4197
|
process.exit(1);
|
|
4192
4198
|
}
|
|
4193
4199
|
const JSZip = (await import("jszip")).default;
|
|
@@ -4195,11 +4201,11 @@ async function importBundleCommand(zipPath) {
|
|
|
4195
4201
|
const zip = await JSZip.loadAsync(zipData);
|
|
4196
4202
|
const manifestFile = zip.file("manifest.json");
|
|
4197
4203
|
if (!manifestFile) {
|
|
4198
|
-
console.error(
|
|
4204
|
+
console.error(chalk16.red("Invalid bundle: missing manifest.json"));
|
|
4199
4205
|
process.exit(1);
|
|
4200
4206
|
}
|
|
4201
4207
|
const manifest = JSON.parse(await manifestFile.async("string"));
|
|
4202
|
-
console.log(
|
|
4208
|
+
console.log(chalk16.blue(`
|
|
4203
4209
|
Bundle: ${manifest.folderName}`));
|
|
4204
4210
|
console.log(` Exported by: ${manifest.ownerEmail}`);
|
|
4205
4211
|
console.log(` Exported at: ${manifest.exportedAt}`);
|
|
@@ -4213,10 +4219,10 @@ Bundle: ${manifest.folderName}`));
|
|
|
4213
4219
|
}
|
|
4214
4220
|
}
|
|
4215
4221
|
if (files.length === 0) {
|
|
4216
|
-
console.log(
|
|
4222
|
+
console.log(chalk16.yellow("No Claude config files found in bundle."));
|
|
4217
4223
|
return;
|
|
4218
4224
|
}
|
|
4219
|
-
console.log(
|
|
4225
|
+
console.log(chalk16.blue(`
|
|
4220
4226
|
Files to extract:`));
|
|
4221
4227
|
for (const f of files) {
|
|
4222
4228
|
console.log(` ${f.filePath}`);
|
|
@@ -4229,39 +4235,39 @@ Files to extract:`));
|
|
|
4229
4235
|
message: `Extract ${files.length} file(s) to ${targetDir}?`
|
|
4230
4236
|
});
|
|
4231
4237
|
if (!proceed) {
|
|
4232
|
-
console.log(
|
|
4238
|
+
console.log(chalk16.yellow("Cancelled."));
|
|
4233
4239
|
return;
|
|
4234
4240
|
}
|
|
4235
4241
|
const resolvedTarget = resolve7(targetDir);
|
|
4236
4242
|
for (const file of files) {
|
|
4237
4243
|
const fullPath = resolve7(targetDir, file.filePath);
|
|
4238
4244
|
if (!fullPath.startsWith(resolvedTarget + "/") && fullPath !== resolvedTarget) {
|
|
4239
|
-
console.error(
|
|
4245
|
+
console.error(chalk16.red(` Blocked path traversal: ${file.filePath}`));
|
|
4240
4246
|
continue;
|
|
4241
4247
|
}
|
|
4242
4248
|
const dir = dirname3(fullPath);
|
|
4243
|
-
if (!
|
|
4249
|
+
if (!existsSync11(dir)) {
|
|
4244
4250
|
await mkdir3(dir, { recursive: true });
|
|
4245
4251
|
}
|
|
4246
4252
|
await writeFile4(fullPath, file.content, "utf-8");
|
|
4247
|
-
console.log(
|
|
4253
|
+
console.log(chalk16.green(` \u2713 ${file.filePath}`));
|
|
4248
4254
|
}
|
|
4249
|
-
console.log(
|
|
4255
|
+
console.log(chalk16.green(`
|
|
4250
4256
|
Done! ${files.length} file(s) extracted to ${targetDir}`));
|
|
4251
4257
|
}
|
|
4252
4258
|
|
|
4253
4259
|
// dist/commands/admin-update-tool.js
|
|
4254
4260
|
init_auth();
|
|
4255
|
-
import
|
|
4261
|
+
import chalk17 from "chalk";
|
|
4256
4262
|
var VALID_CATEGORIES = ["framework", "runtime", "cli", "mcp", "package", "database", "other"];
|
|
4257
4263
|
async function adminUpdateToolCommand(options) {
|
|
4258
4264
|
const { supabase } = await getAuthenticatedClient();
|
|
4259
4265
|
if (!options.name) {
|
|
4260
|
-
console.error(
|
|
4266
|
+
console.error(chalk17.red("--name is required."));
|
|
4261
4267
|
process.exit(1);
|
|
4262
4268
|
}
|
|
4263
4269
|
if (options.category && !VALID_CATEGORIES.includes(options.category)) {
|
|
4264
|
-
console.error(
|
|
4270
|
+
console.error(chalk17.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
|
|
4265
4271
|
process.exit(1);
|
|
4266
4272
|
}
|
|
4267
4273
|
const { data: existing } = await supabase.from("tools_registry").select("id, name, display_name, category").eq("name", options.name).maybeSingle();
|
|
@@ -4283,13 +4289,13 @@ async function adminUpdateToolCommand(options) {
|
|
|
4283
4289
|
updates.notes = options.notes;
|
|
4284
4290
|
const { error } = await supabase.from("tools_registry").update(updates).eq("id", existing.id);
|
|
4285
4291
|
if (error) {
|
|
4286
|
-
console.error(
|
|
4292
|
+
console.error(chalk17.red(`Failed to update: ${error.message}`));
|
|
4287
4293
|
process.exit(1);
|
|
4288
4294
|
}
|
|
4289
|
-
console.log(
|
|
4295
|
+
console.log(chalk17.green(`Updated "${existing.display_name}" in the registry.`));
|
|
4290
4296
|
} else {
|
|
4291
4297
|
if (!options.display || !options.category) {
|
|
4292
|
-
console.error(
|
|
4298
|
+
console.error(chalk17.red("New tools require --display and --category."));
|
|
4293
4299
|
process.exit(1);
|
|
4294
4300
|
}
|
|
4295
4301
|
const { error } = await supabase.from("tools_registry").insert({
|
|
@@ -4303,25 +4309,25 @@ async function adminUpdateToolCommand(options) {
|
|
|
4303
4309
|
notes: options.notes ?? null
|
|
4304
4310
|
});
|
|
4305
4311
|
if (error) {
|
|
4306
|
-
console.error(
|
|
4312
|
+
console.error(chalk17.red(`Failed to create: ${error.message}`));
|
|
4307
4313
|
process.exit(1);
|
|
4308
4314
|
}
|
|
4309
|
-
console.log(
|
|
4315
|
+
console.log(chalk17.green(`Added "${options.display}" to the registry.`));
|
|
4310
4316
|
}
|
|
4311
4317
|
}
|
|
4312
4318
|
|
|
4313
4319
|
// dist/commands/admin-list-tools.js
|
|
4314
4320
|
init_auth();
|
|
4315
|
-
import
|
|
4321
|
+
import chalk18 from "chalk";
|
|
4316
4322
|
async function adminListToolsCommand() {
|
|
4317
4323
|
const { supabase } = await getAuthenticatedClient();
|
|
4318
4324
|
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("category").order("display_name");
|
|
4319
4325
|
if (error) {
|
|
4320
|
-
console.error(
|
|
4326
|
+
console.error(chalk18.red(`Failed to fetch tools: ${error.message}`));
|
|
4321
4327
|
process.exit(1);
|
|
4322
4328
|
}
|
|
4323
4329
|
if (!tools?.length) {
|
|
4324
|
-
console.log(
|
|
4330
|
+
console.log(chalk18.yellow("No tools in the registry."));
|
|
4325
4331
|
return;
|
|
4326
4332
|
}
|
|
4327
4333
|
const nameW = Math.max(16, ...tools.map((t) => t.display_name.length));
|
|
@@ -4335,7 +4341,7 @@ async function adminListToolsCommand() {
|
|
|
4335
4341
|
"Beta".padEnd(betaW),
|
|
4336
4342
|
"Last Checked"
|
|
4337
4343
|
].join(" ");
|
|
4338
|
-
console.log(
|
|
4344
|
+
console.log(chalk18.bold(header));
|
|
4339
4345
|
console.log("\u2500".repeat(header.length));
|
|
4340
4346
|
for (const tool of tools) {
|
|
4341
4347
|
const lastChecked = tool.updated_at ? formatRelative(new Date(tool.updated_at)) : "\u2014";
|
|
@@ -4348,7 +4354,7 @@ async function adminListToolsCommand() {
|
|
|
4348
4354
|
].join(" ");
|
|
4349
4355
|
console.log(row);
|
|
4350
4356
|
}
|
|
4351
|
-
console.log(
|
|
4357
|
+
console.log(chalk18.grey(`
|
|
4352
4358
|
${tools.length} tool(s) in registry.`));
|
|
4353
4359
|
}
|
|
4354
4360
|
function formatRelative(date) {
|
|
@@ -4366,7 +4372,7 @@ function formatRelative(date) {
|
|
|
4366
4372
|
|
|
4367
4373
|
// dist/commands/admin-fetch-versions.js
|
|
4368
4374
|
init_auth();
|
|
4369
|
-
import
|
|
4375
|
+
import chalk19 from "chalk";
|
|
4370
4376
|
|
|
4371
4377
|
// dist/commands/version-sources.js
|
|
4372
4378
|
var VERSION_SOURCES = {
|
|
@@ -4403,11 +4409,11 @@ async function adminFetchVersionsCommand() {
|
|
|
4403
4409
|
const { supabase } = await getAuthenticatedClient();
|
|
4404
4410
|
const { data: tools, error } = await supabase.from("tools_registry").select("*").order("display_name");
|
|
4405
4411
|
if (error) {
|
|
4406
|
-
console.error(
|
|
4412
|
+
console.error(chalk19.red(`Failed to fetch tools: ${error.message}`));
|
|
4407
4413
|
process.exit(1);
|
|
4408
4414
|
}
|
|
4409
4415
|
if (!tools?.length) {
|
|
4410
|
-
console.log(
|
|
4416
|
+
console.log(chalk19.yellow("No tools in the registry."));
|
|
4411
4417
|
}
|
|
4412
4418
|
const { data: allProjectToolings } = await supabase.from("project_toolings").select("tool_name, detection_source").is("tool_id", null);
|
|
4413
4419
|
const registeredNames = new Set((tools ?? []).map((t) => t.name));
|
|
@@ -4418,7 +4424,7 @@ async function adminFetchVersionsCommand() {
|
|
|
4418
4424
|
}
|
|
4419
4425
|
}
|
|
4420
4426
|
if (unregisteredNames.size > 0) {
|
|
4421
|
-
console.log(
|
|
4427
|
+
console.log(chalk19.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
|
|
4422
4428
|
`));
|
|
4423
4429
|
for (const name of unregisteredNames) {
|
|
4424
4430
|
const displayName = name;
|
|
@@ -4436,10 +4442,10 @@ async function adminFetchVersionsCommand() {
|
|
|
4436
4442
|
}
|
|
4437
4443
|
}
|
|
4438
4444
|
if (!tools?.length) {
|
|
4439
|
-
console.log(
|
|
4445
|
+
console.log(chalk19.yellow("No tools to fetch."));
|
|
4440
4446
|
return;
|
|
4441
4447
|
}
|
|
4442
|
-
console.log(
|
|
4448
|
+
console.log(chalk19.bold(`Fetching versions for ${tools.length} tool(s)...
|
|
4443
4449
|
`));
|
|
4444
4450
|
const results = await Promise.all(tools.map(async (tool) => {
|
|
4445
4451
|
const source = VERSION_SOURCES[tool.name] ?? { type: "npm", package: tool.name };
|
|
@@ -4478,10 +4484,10 @@ async function adminFetchVersionsCommand() {
|
|
|
4478
4484
|
"Beta".padEnd(betaW),
|
|
4479
4485
|
"Status".padEnd(statusW)
|
|
4480
4486
|
].join(" ");
|
|
4481
|
-
console.log(
|
|
4487
|
+
console.log(chalk19.bold(header));
|
|
4482
4488
|
console.log("\u2500".repeat(header.length));
|
|
4483
4489
|
for (const result of results) {
|
|
4484
|
-
const statusColour = result.status === "updated" ?
|
|
4490
|
+
const statusColour = result.status === "updated" ? chalk19.green : result.status === "unchanged" ? chalk19.grey : result.status === "failed" ? chalk19.red : chalk19.yellow;
|
|
4485
4491
|
const row = [
|
|
4486
4492
|
result.displayName.padEnd(nameW),
|
|
4487
4493
|
(result.stable ?? "\u2014").padEnd(stableW),
|
|
@@ -4494,8 +4500,8 @@ async function adminFetchVersionsCommand() {
|
|
|
4494
4500
|
const unchanged = results.filter((r) => r.status === "unchanged").length;
|
|
4495
4501
|
const failed = results.filter((r) => r.status === "failed").length;
|
|
4496
4502
|
const noSource = results.filter((r) => r.status === "no source").length;
|
|
4497
|
-
console.log(
|
|
4498
|
-
${results.length} tool(s): `) +
|
|
4503
|
+
console.log(chalk19.grey(`
|
|
4504
|
+
${results.length} tool(s): `) + chalk19.green(`${updated} updated`) + ", " + chalk19.grey(`${unchanged} unchanged`) + ", " + chalk19.red(`${failed} failed`) + ", " + chalk19.yellow(`${noSource} no source`));
|
|
4499
4505
|
}
|
|
4500
4506
|
async function fetchVersions(source) {
|
|
4501
4507
|
const controller = new AbortController();
|
|
@@ -4555,8 +4561,8 @@ init_mcp_watch();
|
|
|
4555
4561
|
// dist/commands/init-manifest.js
|
|
4556
4562
|
import { resolve as resolve8, join as join16, relative as relative4, dirname as dirname4 } from "node:path";
|
|
4557
4563
|
import { readFile as readFile13, writeFile as writeFile5, mkdir as mkdir4 } from "node:fs/promises";
|
|
4558
|
-
import { existsSync as
|
|
4559
|
-
import
|
|
4564
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
4565
|
+
import chalk21 from "chalk";
|
|
4560
4566
|
var SECRET_PATTERNS = [
|
|
4561
4567
|
/_KEY$/i,
|
|
4562
4568
|
/_SECRET$/i,
|
|
@@ -4573,7 +4579,7 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
4573
4579
|
const apps = [];
|
|
4574
4580
|
for (const envName of [".env", ".env.local", ".env.example"]) {
|
|
4575
4581
|
const envPath = join16(projectRoot, envName);
|
|
4576
|
-
if (
|
|
4582
|
+
if (existsSync13(envPath)) {
|
|
4577
4583
|
const vars = await extractVarNames(envPath);
|
|
4578
4584
|
if (vars.length > 0) {
|
|
4579
4585
|
apps.push({ name: "root", envFilePath: envName, vars });
|
|
@@ -4584,11 +4590,11 @@ async function discoverEnvFiles(projectRoot) {
|
|
|
4584
4590
|
const subdirs = ["web", "cli", "api", "app", "server", "packages"];
|
|
4585
4591
|
for (const sub of subdirs) {
|
|
4586
4592
|
const subDir = join16(projectRoot, sub);
|
|
4587
|
-
if (!
|
|
4593
|
+
if (!existsSync13(subDir))
|
|
4588
4594
|
continue;
|
|
4589
4595
|
for (const envName of [".env.local", ".env", ".env.example"]) {
|
|
4590
4596
|
const envPath = join16(subDir, envName);
|
|
4591
|
-
if (
|
|
4597
|
+
if (existsSync13(envPath)) {
|
|
4592
4598
|
const vars = await extractVarNames(envPath);
|
|
4593
4599
|
if (vars.length > 0) {
|
|
4594
4600
|
apps.push({
|
|
@@ -4650,31 +4656,31 @@ function generateManifest(apps) {
|
|
|
4650
4656
|
async function initManifestCommand() {
|
|
4651
4657
|
const projectRoot = resolve8(process.cwd());
|
|
4652
4658
|
const manifestPath = join16(projectRoot, "docs", "reference", "env-manifest.md");
|
|
4653
|
-
if (
|
|
4654
|
-
console.log(
|
|
4655
|
-
console.log(
|
|
4659
|
+
if (existsSync13(manifestPath)) {
|
|
4660
|
+
console.log(chalk21.yellow(`Manifest already exists: ${relative4(projectRoot, manifestPath)}`));
|
|
4661
|
+
console.log(chalk21.dim("Edit it directly to make changes."));
|
|
4656
4662
|
return;
|
|
4657
4663
|
}
|
|
4658
|
-
console.log(
|
|
4664
|
+
console.log(chalk21.blue("Scanning for .env files...\n"));
|
|
4659
4665
|
const apps = await discoverEnvFiles(projectRoot);
|
|
4660
4666
|
if (apps.length === 0) {
|
|
4661
|
-
console.log(
|
|
4662
|
-
console.log(
|
|
4667
|
+
console.log(chalk21.yellow("No .env files found. Create a manifest manually at:"));
|
|
4668
|
+
console.log(chalk21.cyan(` docs/reference/env-manifest.md`));
|
|
4663
4669
|
return;
|
|
4664
4670
|
}
|
|
4665
4671
|
for (const app of apps) {
|
|
4666
|
-
console.log(` ${
|
|
4672
|
+
console.log(` ${chalk21.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
|
|
4667
4673
|
}
|
|
4668
4674
|
const content = generateManifest(apps);
|
|
4669
4675
|
const dir = dirname4(manifestPath);
|
|
4670
|
-
if (!
|
|
4676
|
+
if (!existsSync13(dir)) {
|
|
4671
4677
|
await mkdir4(dir, { recursive: true });
|
|
4672
4678
|
}
|
|
4673
4679
|
await writeFile5(manifestPath, content, "utf-8");
|
|
4674
|
-
console.log(
|
|
4680
|
+
console.log(chalk21.green(`
|
|
4675
4681
|
Manifest created: ${relative4(projectRoot, manifestPath)}`));
|
|
4676
|
-
console.log(
|
|
4677
|
-
console.log(
|
|
4682
|
+
console.log(chalk21.dim("Review and edit the file \u2014 it is your source of truth."));
|
|
4683
|
+
console.log(chalk21.dim("Then run `md4ai scan` to verify against your environments."));
|
|
4678
4684
|
}
|
|
4679
4685
|
|
|
4680
4686
|
// dist/commands/update.js
|
|
@@ -4857,7 +4863,7 @@ init_check_update();
|
|
|
4857
4863
|
init_map();
|
|
4858
4864
|
init_mcp_watch();
|
|
4859
4865
|
import chalk24 from "chalk";
|
|
4860
|
-
import { resolve as
|
|
4866
|
+
import { resolve as resolve10 } from "node:path";
|
|
4861
4867
|
async function fetchLatestVersion2() {
|
|
4862
4868
|
try {
|
|
4863
4869
|
const controller = new AbortController();
|
|
@@ -4886,7 +4892,7 @@ function isNewer3(a, b) {
|
|
|
4886
4892
|
return false;
|
|
4887
4893
|
}
|
|
4888
4894
|
async function startCommand() {
|
|
4889
|
-
const projectRoot =
|
|
4895
|
+
const projectRoot = resolve10(process.cwd());
|
|
4890
4896
|
console.log("");
|
|
4891
4897
|
console.log(chalk24.bold.cyan(` MD4AI v${CURRENT_VERSION}`));
|
|
4892
4898
|
console.log(chalk24.dim(` ${projectRoot}`));
|
|
@@ -5083,32 +5089,39 @@ init_check_update();
|
|
|
5083
5089
|
var program = new Command();
|
|
5084
5090
|
program.name("md4ai").description("MD4AI \u2014 Claude tooling visualiser").version(CURRENT_VERSION).addHelpText("after", `
|
|
5085
5091
|
Quick start:
|
|
5086
|
-
md4ai
|
|
5087
|
-
md4ai
|
|
5092
|
+
md4ai login Log in with email and password
|
|
5093
|
+
md4ai link <project-id> Link this folder to a dashboard project
|
|
5094
|
+
md4ai scan Scan project and push to dashboard
|
|
5095
|
+
|
|
5096
|
+
Daily use:
|
|
5097
|
+
md4ai Scan + start MCP monitoring (all-in-one)
|
|
5098
|
+
|
|
5099
|
+
More:
|
|
5100
|
+
md4ai config ... Configuration (tokens, settings)
|
|
5101
|
+
md4ai doppler ... Doppler secrets integration
|
|
5102
|
+
md4ai admin ... Advanced tools (simulate, print, import, etc.)`);
|
|
5088
5103
|
program.command("start").description("Check for updates, scan project, and start MCP monitoring \u2014 the all-in-one command").action(startCommand);
|
|
5089
5104
|
program.command("login").description("Log in to MD4AI with email and password").action(loginCommand);
|
|
5090
5105
|
program.command("logout").description("Log out and clear stored credentials").action(logoutCommand);
|
|
5091
5106
|
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
5107
|
program.command("link <project-id>").description("Link the current directory to a project created in the web dashboard").action(linkCommand);
|
|
5096
5108
|
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
5109
|
program.command("mcp-watch").description("Monitor MCP server status on this device (runs until Ctrl+C)").action(mcpWatchCommand);
|
|
5104
|
-
program.command("
|
|
5110
|
+
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
5111
|
var config = program.command("config").description("Manage CLI configuration");
|
|
5106
5112
|
config.command("set <key> <value>").description("Set a configuration value (e.g. vercel-token)").action(configSetCommand);
|
|
5107
5113
|
var doppler = program.command("doppler").description("Manage Doppler integration");
|
|
5108
5114
|
doppler.command("connect").description("Configure Doppler token for secrets scanning").action(dopplerConnectCommand);
|
|
5109
5115
|
doppler.command("disconnect").description("Remove stored Doppler token").action(dopplerDisconnectCommand);
|
|
5110
5116
|
doppler.command("set-project <slug>").description("Override Doppler project slug for the current linked project").action(dopplerSetProjectCommand);
|
|
5111
|
-
var admin = program.command("admin").description("
|
|
5117
|
+
var admin = program.command("admin").description("Advanced tools \u2014 simulate, print, import, device management, registry");
|
|
5118
|
+
admin.command("simulate <prompt>").description("Show which files Claude would load for a given prompt").action(simulateCommand);
|
|
5119
|
+
admin.command("print <title>").description("Generate a printable wall-chart HTML from the last scan").action(printCommand);
|
|
5120
|
+
admin.command("import <zipfile>").description("Import a Claude setup bundle exported from the web dashboard").action(importBundleCommand);
|
|
5121
|
+
admin.command("init-manifest").description("Scaffold a starter env-manifest.md from detected .env files").action(initManifestCommand);
|
|
5122
|
+
admin.command("list-devices").description("List all devices and their linked folders").action(listDevicesCommand);
|
|
5123
|
+
admin.command("add-folder").description("Create a new Claude folder").action(addFolderCommand);
|
|
5124
|
+
admin.command("add-device").description("Add a device path to a Claude folder").action(addDeviceCommand);
|
|
5112
5125
|
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
5126
|
admin.command("list-tools").description("List all tools in the master registry").action(adminListToolsCommand);
|
|
5114
5127
|
admin.command("fetch-versions").description("Fetch latest stable and beta versions from npm and GitHub").action(adminFetchVersionsCommand);
|
|
@@ -5118,7 +5131,7 @@ if (process.argv.length <= 2) {
|
|
|
5118
5131
|
} else {
|
|
5119
5132
|
program.parseAsync().then(() => {
|
|
5120
5133
|
const ran = program.args[0];
|
|
5121
|
-
const skipAutoCheck = ["update", "
|
|
5134
|
+
const skipAutoCheck = ["update", "mcp-watch", "start"];
|
|
5122
5135
|
if (!skipAutoCheck.includes(ran)) {
|
|
5123
5136
|
autoCheckForUpdate();
|
|
5124
5137
|
}
|