md4ai 0.17.3 → 0.18.0

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