md4ai 0.14.1 → 0.16.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.
@@ -122,7 +122,7 @@ var CURRENT_VERSION;
122
122
  var init_check_update = __esm({
123
123
  "dist/check-update.js"() {
124
124
  "use strict";
125
- CURRENT_VERSION = true ? "0.14.1" : "0.0.0-dev";
125
+ CURRENT_VERSION = true ? "0.16.0" : "0.0.0-dev";
126
126
  }
127
127
  });
128
128
 
@@ -1109,7 +1109,9 @@ var init_tooling_detector = __esm({
1109
1109
  { name: "pnpm", command: "pnpm", args: ["--version"], conditionFile: "pnpm-lock.yaml" },
1110
1110
  { name: "supabase-cli", command: "npx", args: ["supabase", "--version"] },
1111
1111
  { name: "vercel-cli", command: "npx", args: ["vercel", "--version"] },
1112
- { name: "claude-code", command: "claude", args: ["--version"] }
1112
+ { name: "claude-code", command: "claude", args: ["--version"] },
1113
+ { name: "gh", command: "gh", args: ["--version"] },
1114
+ { name: "git", command: "git", args: ["--version"] }
1113
1115
  ];
1114
1116
  }
1115
1117
  });
@@ -1767,6 +1769,51 @@ async function discoverSkills(installPath) {
1767
1769
  skills.sort((a, b) => a.name.localeCompare(b.name));
1768
1770
  return skills;
1769
1771
  }
1772
+ async function checkPluginLatestVersions(plugins) {
1773
+ const results = [];
1774
+ for (const plugin of plugins) {
1775
+ let latestVersion = null;
1776
+ if (plugin.homepageUrl && plugin.homepageUrl.includes("github.com") && !plugin.homepageUrl.includes("/tree/main/plugins/")) {
1777
+ try {
1778
+ const match = plugin.homepageUrl.match(/github\.com\/([^/]+\/[^/]+)/);
1779
+ if (match) {
1780
+ const ownerRepo = match[1];
1781
+ const controller = new AbortController();
1782
+ const timeout = setTimeout(() => controller.abort(), 3e3);
1783
+ let res = await fetch(`https://api.github.com/repos/${ownerRepo}/releases/latest`, {
1784
+ signal: controller.signal,
1785
+ headers: { Accept: "application/vnd.github.v3+json" }
1786
+ });
1787
+ clearTimeout(timeout);
1788
+ if (res.ok) {
1789
+ const data = await res.json();
1790
+ latestVersion = data.tag_name?.replace(/^v/, "") ?? null;
1791
+ } else {
1792
+ const controller2 = new AbortController();
1793
+ const timeout2 = setTimeout(() => controller2.abort(), 3e3);
1794
+ res = await fetch(`https://api.github.com/repos/${ownerRepo}/tags?per_page=1`, {
1795
+ signal: controller2.signal,
1796
+ headers: { Accept: "application/vnd.github.v3+json" }
1797
+ });
1798
+ clearTimeout(timeout2);
1799
+ if (res.ok) {
1800
+ const data = await res.json();
1801
+ latestVersion = data[0]?.name?.replace(/^v/, "") ?? null;
1802
+ }
1803
+ }
1804
+ }
1805
+ } catch {
1806
+ }
1807
+ }
1808
+ results.push({
1809
+ pluginName: plugin.pluginName,
1810
+ installedVersion: plugin.version,
1811
+ latestVersion,
1812
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
1813
+ });
1814
+ }
1815
+ return results;
1816
+ }
1770
1817
  var init_marketplace_scanner = __esm({
1771
1818
  "dist/scanner/marketplace-scanner.js"() {
1772
1819
  "use strict";
@@ -2054,6 +2101,7 @@ async function scanProject(projectRoot, folderId) {
2054
2101
  brokenRefs.sort((a, b) => a.depth - b.depth || a.from.localeCompare(b.from));
2055
2102
  const scanData = JSON.stringify({ graph, orphans, brokenRefs, skills, staleFiles, toolings, envManifest, marketplacePlugins, doppler: doppler2 });
2056
2103
  const dataHash = createHash("sha256").update(scanData).digest("hex");
2104
+ const pluginVersions = await checkPluginLatestVersions(marketplacePlugins);
2057
2105
  return {
2058
2106
  graph,
2059
2107
  orphans,
@@ -2064,6 +2112,7 @@ async function scanProject(projectRoot, folderId) {
2064
2112
  envManifest,
2065
2113
  doppler: doppler2,
2066
2114
  marketplacePlugins,
2115
+ pluginVersions,
2067
2116
  scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
2068
2117
  dataHash
2069
2118
  };
@@ -2288,12 +2337,14 @@ var init_html_generator = __esm({
2288
2337
  });
2289
2338
 
2290
2339
  // dist/commands/push-toolings.js
2291
- async function pushToolings(supabase, folderId, toolings) {
2340
+ async function pushToolings(supabase, folderId, toolings, deviceId) {
2292
2341
  if (!toolings.length)
2293
2342
  return;
2294
2343
  const { data: registry } = await supabase.from("tools_registry").select("id, name");
2295
2344
  const registryMap = new Map((registry ?? []).map((r) => [r.name, r.id]));
2296
- for (const tooling of toolings) {
2345
+ const projectToolings = toolings.filter((t) => t.detection_source !== "cli");
2346
+ const deviceToolings = toolings.filter((t) => t.detection_source === "cli");
2347
+ for (const tooling of projectToolings) {
2297
2348
  const toolId = registryMap.get(tooling.tool_name) ?? null;
2298
2349
  await supabase.from("project_toolings").upsert({
2299
2350
  folder_id: folderId,
@@ -2304,6 +2355,19 @@ async function pushToolings(supabase, folderId, toolings) {
2304
2355
  detected_at: (/* @__PURE__ */ new Date()).toISOString()
2305
2356
  }, { onConflict: "folder_id,tool_name,detection_source" });
2306
2357
  }
2358
+ if (deviceId && deviceToolings.length) {
2359
+ for (const tooling of deviceToolings) {
2360
+ const toolId = registryMap.get(tooling.tool_name) ?? null;
2361
+ await supabase.from("device_toolings").upsert({
2362
+ device_id: deviceId,
2363
+ tool_id: toolId,
2364
+ tool_name: tooling.tool_name,
2365
+ detected_version: tooling.detected_version,
2366
+ detection_source: tooling.detection_source,
2367
+ detected_at: (/* @__PURE__ */ new Date()).toISOString()
2368
+ }, { onConflict: "device_id,tool_name,detection_source" });
2369
+ }
2370
+ }
2307
2371
  }
2308
2372
  var init_push_toolings = __esm({
2309
2373
  "dist/commands/push-toolings.js"() {
@@ -2569,12 +2633,13 @@ ${proposedFiles.length} file(s) proposed for deletion:
2569
2633
  env_manifest_json: result.envManifest,
2570
2634
  doppler_json: result.doppler,
2571
2635
  marketplace_plugins_json: result.marketplacePlugins,
2636
+ plugin_versions_json: result.pluginVersions,
2572
2637
  data_hash: result.dataHash,
2573
2638
  scanned_at: result.scannedAt,
2574
2639
  cli_version: CURRENT_VERSION
2575
2640
  }, { onConflict: "folder_id,device_id" });
2576
2641
  }
2577
- await pushToolings(supabase, folder_id, result.toolings);
2642
+ await pushToolings(supabase, folder_id, result.toolings, deviceId);
2578
2643
  const manifestForHealth = result.envManifest ?? storedManifest;
2579
2644
  if (manifestForHealth) {
2580
2645
  await pushHealthResults(supabase, folder_id, manifestForHealth, deviceId);
@@ -2689,12 +2754,13 @@ ${proposedFiles.length} file(s) proposed for deletion:
2689
2754
  env_manifest_json: result.envManifest,
2690
2755
  doppler_json: result.doppler,
2691
2756
  marketplace_plugins_json: result.marketplacePlugins,
2757
+ plugin_versions_json: result.pluginVersions,
2692
2758
  data_hash: result.dataHash,
2693
2759
  scanned_at: result.scannedAt,
2694
2760
  cli_version: CURRENT_VERSION
2695
2761
  }, { onConflict: "folder_id,device_id" });
2696
2762
  }
2697
- await pushToolings(sb, folderId, result.toolings);
2763
+ await pushToolings(sb, folderId, result.toolings, inlineDeviceId);
2698
2764
  if (result.envManifest) {
2699
2765
  await pushHealthResults(sb, folderId, result.envManifest, inlineDeviceId);
2700
2766
  }
@@ -2802,12 +2868,13 @@ async function syncCommand(options) {
2802
2868
  stale_files_json: result.staleFiles,
2803
2869
  broken_refs_json: result.brokenRefs,
2804
2870
  env_manifest_json: result.envManifest,
2871
+ plugin_versions_json: result.pluginVersions,
2805
2872
  data_hash: result.dataHash,
2806
2873
  scanned_at: result.scannedAt,
2807
2874
  cli_version: CURRENT_VERSION
2808
2875
  }, { onConflict: "folder_id,device_id" });
2809
2876
  }
2810
- await pushToolings(supabase, device.folder_id, result.toolings);
2877
+ await pushToolings(supabase, device.folder_id, result.toolings, allDeviceId);
2811
2878
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
2812
2879
  console.log(chalk15.green(` Done: ${device.device_name}`));
2813
2880
  } catch (err) {
@@ -2865,12 +2932,13 @@ async function syncCommand(options) {
2865
2932
  skills_table_json: result.skills,
2866
2933
  stale_files_json: result.staleFiles,
2867
2934
  broken_refs_json: result.brokenRefs,
2935
+ plugin_versions_json: result.pluginVersions,
2868
2936
  data_hash: result.dataHash,
2869
2937
  scanned_at: result.scannedAt,
2870
2938
  cli_version: CURRENT_VERSION
2871
2939
  }, { onConflict: "folder_id,device_id" });
2872
2940
  }
2873
- await pushToolings(supabase, device.folder_id, result.toolings);
2941
+ await pushToolings(supabase, device.folder_id, result.toolings, singleDeviceId);
2874
2942
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
2875
2943
  await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
2876
2944
  console.log(chalk15.green("Synced."));
@@ -3595,7 +3663,7 @@ async function checkPendingRescans(supabase, deviceId, deviceName) {
3595
3663
  data_hash: result.dataHash,
3596
3664
  rescan_requested_at: null
3597
3665
  }).eq("id", folder.id);
3598
- await pushToolings(supabase, folder.id, result.toolings);
3666
+ await pushToolings(supabase, folder.id, result.toolings, deviceId);
3599
3667
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
3600
3668
  } catch {
3601
3669
  await supabase.from("claude_folders").update({ rescan_requested_at: null }).eq("id", folder.id);
@@ -4082,11 +4150,12 @@ Linking "${folder.name}" to this device...
4082
4150
  env_manifest_json: result.envManifest,
4083
4151
  doppler_json: result.doppler,
4084
4152
  marketplace_plugins_json: result.marketplacePlugins,
4153
+ plugin_versions_json: result.pluginVersions,
4085
4154
  data_hash: result.dataHash,
4086
4155
  scanned_at: result.scannedAt,
4087
4156
  cli_version: CURRENT_VERSION
4088
4157
  }, { onConflict: "folder_id,device_id" });
4089
- await pushToolings(supabase, folder.id, result.toolings);
4158
+ await pushToolings(supabase, folder.id, result.toolings, deviceId);
4090
4159
  const graphPaths = result.graph.nodes.map((n) => n.filePath);
4091
4160
  const configFiles = await readClaudeConfigFiles(cwd, graphPaths);
4092
4161
  if (configFiles.length > 0) {
@@ -5037,18 +5106,29 @@ initSentry();
5037
5106
  if (process.argv.length <= 2) {
5038
5107
  void startCommand();
5039
5108
  } else {
5040
- program.parse();
5041
- const ran = program.args[0];
5042
- const skipAutoCheck = ["update", "check-update", "mcp-watch", "start"];
5043
- if (!skipAutoCheck.includes(ran)) {
5044
- autoCheckForUpdate();
5045
- }
5109
+ program.parseAsync().then(() => {
5110
+ const ran = program.args[0];
5111
+ const skipAutoCheck = ["update", "check-update", "mcp-watch", "start"];
5112
+ if (!skipAutoCheck.includes(ran)) {
5113
+ autoCheckForUpdate();
5114
+ }
5115
+ }).catch(async (err) => {
5116
+ captureException2(err);
5117
+ const msg = err instanceof Error ? err.message : String(err);
5118
+ console.error(`\x1B[31mError: ${msg}\x1B[0m`);
5119
+ await flushSentry();
5120
+ process.exit(1);
5121
+ });
5046
5122
  }
5047
5123
  process.on("uncaughtException", async (err) => {
5048
5124
  captureException2(err);
5049
5125
  await flushSentry();
5050
5126
  process.exit(1);
5051
5127
  });
5052
- process.on("unhandledRejection", (reason) => {
5128
+ process.on("unhandledRejection", async (reason) => {
5053
5129
  captureException2(reason);
5130
+ const msg = reason instanceof Error ? reason.message : String(reason);
5131
+ console.error(`\x1B[31mError: ${msg}\x1B[0m`);
5132
+ await flushSentry();
5133
+ process.exit(1);
5054
5134
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md4ai",
3
- "version": "0.14.1",
3
+ "version": "0.16.0",
4
4
  "description": "CLI for MD4AI — scan Claude projects and sync to your dashboard",
5
5
  "type": "module",
6
6
  "bin": {