md4ai 0.14.0 → 0.15.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.0" : "0.0.0-dev";
125
+ CURRENT_VERSION = true ? "0.15.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
  });
@@ -1151,11 +1153,23 @@ var init_auth2 = __esm({
1151
1153
  });
1152
1154
 
1153
1155
  // dist/vercel/discover-projects.js
1154
- import { readFile as readFile6, glob } from "node:fs/promises";
1156
+ import { readFile as readFile6, readdir as readdir2 } from "node:fs/promises";
1155
1157
  import { join as join8, dirname as dirname2, relative } from "node:path";
1156
1158
  async function discoverVercelProjects(projectRoot) {
1157
1159
  const found = /* @__PURE__ */ new Map();
1158
- for await (const filePath of glob(join8(projectRoot, "**/.vercel/project.json"))) {
1160
+ let entries;
1161
+ try {
1162
+ entries = await readdir2(projectRoot, { recursive: true, withFileTypes: true });
1163
+ } catch {
1164
+ return [];
1165
+ }
1166
+ for (const entry of entries) {
1167
+ if (!entry.isFile() || entry.name !== "project.json")
1168
+ continue;
1169
+ const parentName = typeof entry.parentPath === "string" ? entry.parentPath : entry.path;
1170
+ if (!parentName.endsWith(".vercel"))
1171
+ continue;
1172
+ const filePath = join8(parentName, entry.name);
1159
1173
  try {
1160
1174
  const data = await readFile6(filePath, "utf-8");
1161
1175
  const parsed = JSON.parse(data);
@@ -1250,7 +1264,7 @@ var init_fetch_env_vars = __esm({
1250
1264
  });
1251
1265
 
1252
1266
  // dist/scanner/env-manifest-scanner.js
1253
- import { readFile as readFile7, glob as glob2 } from "node:fs/promises";
1267
+ import { readFile as readFile7, readdir as readdir3 } from "node:fs/promises";
1254
1268
  import { execFile } from "node:child_process";
1255
1269
  import { promisify } from "node:util";
1256
1270
  import { join as join9, relative as relative2 } from "node:path";
@@ -1519,21 +1533,23 @@ async function parseWorkflowSecrets(projectRoot) {
1519
1533
  return [];
1520
1534
  const refs = [];
1521
1535
  const secretRe = /\$\{\{\s*secrets\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;
1522
- for (const ext of ["*.yml", "*.yaml"]) {
1523
- for await (const filePath of glob2(join9(workflowsDir, ext))) {
1524
- const content = await readFile7(filePath, "utf-8");
1525
- const secrets = /* @__PURE__ */ new Set();
1526
- let m;
1527
- while ((m = secretRe.exec(content)) !== null) {
1528
- secrets.add(m[1]);
1529
- }
1530
- secretRe.lastIndex = 0;
1531
- if (secrets.size > 0) {
1532
- refs.push({
1533
- workflowFile: relative2(projectRoot, filePath),
1534
- secretsUsed: [...secrets].sort()
1535
- });
1536
- }
1536
+ const files = await readdir3(workflowsDir);
1537
+ for (const file of files) {
1538
+ if (!file.endsWith(".yml") && !file.endsWith(".yaml"))
1539
+ continue;
1540
+ const filePath = join9(workflowsDir, file);
1541
+ const content = await readFile7(filePath, "utf-8");
1542
+ const secrets = /* @__PURE__ */ new Set();
1543
+ let m;
1544
+ while ((m = secretRe.exec(content)) !== null) {
1545
+ secrets.add(m[1]);
1546
+ }
1547
+ secretRe.lastIndex = 0;
1548
+ if (secrets.size > 0) {
1549
+ refs.push({
1550
+ workflowFile: relative2(projectRoot, filePath),
1551
+ secretsUsed: [...secrets].sort()
1552
+ });
1537
1553
  }
1538
1554
  }
1539
1555
  return refs;
@@ -1660,7 +1676,7 @@ var init_env_manifest_scanner = __esm({
1660
1676
  });
1661
1677
 
1662
1678
  // dist/scanner/marketplace-scanner.js
1663
- import { readFile as readFile8, readdir as readdir2, stat } from "node:fs/promises";
1679
+ import { readFile as readFile8, readdir as readdir4, stat } from "node:fs/promises";
1664
1680
  import { join as join10, resolve as resolve3 } from "node:path";
1665
1681
  import { existsSync as existsSync5 } from "node:fs";
1666
1682
  import { homedir as homedir6 } from "node:os";
@@ -1729,7 +1745,7 @@ async function discoverSkills(installPath) {
1729
1745
  return [];
1730
1746
  const skills = [];
1731
1747
  try {
1732
- const entries = await readdir2(skillsDir, { withFileTypes: true });
1748
+ const entries = await readdir4(skillsDir, { withFileTypes: true });
1733
1749
  for (const entry of entries) {
1734
1750
  if (!entry.isDirectory())
1735
1751
  continue;
@@ -1986,7 +2002,7 @@ var init_doppler_scanner = __esm({
1986
2002
  });
1987
2003
 
1988
2004
  // dist/scanner/index.js
1989
- import { readdir as readdir3 } from "node:fs/promises";
2005
+ import { readdir as readdir5 } from "node:fs/promises";
1990
2006
  import { join as join12, relative as relative3 } from "node:path";
1991
2007
  import { existsSync as existsSync7 } from "node:fs";
1992
2008
  import { homedir as homedir7 } from "node:os";
@@ -2073,7 +2089,7 @@ async function discoverFiles(projectRoot) {
2073
2089
  return [...new Set(files)];
2074
2090
  }
2075
2091
  async function walkDir(dir, projectRoot, files) {
2076
- const entries = await readdir3(dir, { withFileTypes: true });
2092
+ const entries = await readdir5(dir, { withFileTypes: true });
2077
2093
  for (const entry of entries) {
2078
2094
  const fullPath = join12(dir, entry.name);
2079
2095
  if (entry.isDirectory()) {
@@ -2274,12 +2290,14 @@ var init_html_generator = __esm({
2274
2290
  });
2275
2291
 
2276
2292
  // dist/commands/push-toolings.js
2277
- async function pushToolings(supabase, folderId, toolings) {
2293
+ async function pushToolings(supabase, folderId, toolings, deviceId) {
2278
2294
  if (!toolings.length)
2279
2295
  return;
2280
2296
  const { data: registry } = await supabase.from("tools_registry").select("id, name");
2281
2297
  const registryMap = new Map((registry ?? []).map((r) => [r.name, r.id]));
2282
- for (const tooling of toolings) {
2298
+ const projectToolings = toolings.filter((t) => t.detection_source !== "cli");
2299
+ const deviceToolings = toolings.filter((t) => t.detection_source === "cli");
2300
+ for (const tooling of projectToolings) {
2283
2301
  const toolId = registryMap.get(tooling.tool_name) ?? null;
2284
2302
  await supabase.from("project_toolings").upsert({
2285
2303
  folder_id: folderId,
@@ -2290,6 +2308,19 @@ async function pushToolings(supabase, folderId, toolings) {
2290
2308
  detected_at: (/* @__PURE__ */ new Date()).toISOString()
2291
2309
  }, { onConflict: "folder_id,tool_name,detection_source" });
2292
2310
  }
2311
+ if (deviceId && deviceToolings.length) {
2312
+ for (const tooling of deviceToolings) {
2313
+ const toolId = registryMap.get(tooling.tool_name) ?? null;
2314
+ await supabase.from("device_toolings").upsert({
2315
+ device_id: deviceId,
2316
+ tool_id: toolId,
2317
+ tool_name: tooling.tool_name,
2318
+ detected_version: tooling.detected_version,
2319
+ detection_source: tooling.detection_source,
2320
+ detected_at: (/* @__PURE__ */ new Date()).toISOString()
2321
+ }, { onConflict: "device_id,tool_name,detection_source" });
2322
+ }
2323
+ }
2293
2324
  }
2294
2325
  var init_push_toolings = __esm({
2295
2326
  "dist/commands/push-toolings.js"() {
@@ -2560,7 +2591,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2560
2591
  cli_version: CURRENT_VERSION
2561
2592
  }, { onConflict: "folder_id,device_id" });
2562
2593
  }
2563
- await pushToolings(supabase, folder_id, result.toolings);
2594
+ await pushToolings(supabase, folder_id, result.toolings, deviceId);
2564
2595
  const manifestForHealth = result.envManifest ?? storedManifest;
2565
2596
  if (manifestForHealth) {
2566
2597
  await pushHealthResults(supabase, folder_id, manifestForHealth, deviceId);
@@ -2680,7 +2711,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2680
2711
  cli_version: CURRENT_VERSION
2681
2712
  }, { onConflict: "folder_id,device_id" });
2682
2713
  }
2683
- await pushToolings(sb, folderId, result.toolings);
2714
+ await pushToolings(sb, folderId, result.toolings, inlineDeviceId);
2684
2715
  if (result.envManifest) {
2685
2716
  await pushHealthResults(sb, folderId, result.envManifest, inlineDeviceId);
2686
2717
  }
@@ -2793,7 +2824,7 @@ async function syncCommand(options) {
2793
2824
  cli_version: CURRENT_VERSION
2794
2825
  }, { onConflict: "folder_id,device_id" });
2795
2826
  }
2796
- await pushToolings(supabase, device.folder_id, result.toolings);
2827
+ await pushToolings(supabase, device.folder_id, result.toolings, allDeviceId);
2797
2828
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
2798
2829
  console.log(chalk15.green(` Done: ${device.device_name}`));
2799
2830
  } catch (err) {
@@ -2856,7 +2887,7 @@ async function syncCommand(options) {
2856
2887
  cli_version: CURRENT_VERSION
2857
2888
  }, { onConflict: "folder_id,device_id" });
2858
2889
  }
2859
- await pushToolings(supabase, device.folder_id, result.toolings);
2890
+ await pushToolings(supabase, device.folder_id, result.toolings, singleDeviceId);
2860
2891
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
2861
2892
  await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
2862
2893
  console.log(chalk15.green("Synced."));
@@ -2878,7 +2909,7 @@ import { readFile as readFile12 } from "node:fs/promises";
2878
2909
  import { join as join15 } from "node:path";
2879
2910
  import { homedir as homedir9 } from "node:os";
2880
2911
  import { existsSync as existsSync13 } from "node:fs";
2881
- import { readdir as readdir4 } from "node:fs/promises";
2912
+ import { readdir as readdir6 } from "node:fs/promises";
2882
2913
  async function readJsonSafe(path) {
2883
2914
  try {
2884
2915
  if (!existsSync13(path))
@@ -2954,14 +2985,14 @@ async function readAllMcpConfigs() {
2954
2985
  const pluginsBase = join15(home, ".claude", "plugins", "marketplaces");
2955
2986
  if (existsSync13(pluginsBase)) {
2956
2987
  try {
2957
- const marketplaces = await readdir4(pluginsBase, { withFileTypes: true });
2988
+ const marketplaces = await readdir6(pluginsBase, { withFileTypes: true });
2958
2989
  for (const mp of marketplaces) {
2959
2990
  if (!mp.isDirectory())
2960
2991
  continue;
2961
2992
  const extDir = join15(pluginsBase, mp.name, "external_plugins");
2962
2993
  if (!existsSync13(extDir))
2963
2994
  continue;
2964
- const plugins = await readdir4(extDir, { withFileTypes: true });
2995
+ const plugins = await readdir6(extDir, { withFileTypes: true });
2965
2996
  for (const plugin of plugins) {
2966
2997
  if (!plugin.isDirectory())
2967
2998
  continue;
@@ -2975,16 +3006,16 @@ async function readAllMcpConfigs() {
2975
3006
  const cacheBase = join15(home, ".claude", "plugins", "cache");
2976
3007
  if (existsSync13(cacheBase)) {
2977
3008
  try {
2978
- const registries = await readdir4(cacheBase, { withFileTypes: true });
3009
+ const registries = await readdir6(cacheBase, { withFileTypes: true });
2979
3010
  for (const reg of registries) {
2980
3011
  if (!reg.isDirectory())
2981
3012
  continue;
2982
3013
  const regDir = join15(cacheBase, reg.name);
2983
- const plugins = await readdir4(regDir, { withFileTypes: true });
3014
+ const plugins = await readdir6(regDir, { withFileTypes: true });
2984
3015
  for (const plugin of plugins) {
2985
3016
  if (!plugin.isDirectory())
2986
3017
  continue;
2987
- const versionDirs = await readdir4(join15(regDir, plugin.name), { withFileTypes: true });
3018
+ const versionDirs = await readdir6(join15(regDir, plugin.name), { withFileTypes: true });
2988
3019
  for (const ver of versionDirs) {
2989
3020
  if (!ver.isDirectory())
2990
3021
  continue;
@@ -3581,7 +3612,7 @@ async function checkPendingRescans(supabase, deviceId, deviceName) {
3581
3612
  data_hash: result.dataHash,
3582
3613
  rescan_requested_at: null
3583
3614
  }).eq("id", folder.id);
3584
- await pushToolings(supabase, folder.id, result.toolings);
3615
+ await pushToolings(supabase, folder.id, result.toolings, deviceId);
3585
3616
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
3586
3617
  } catch {
3587
3618
  await supabase.from("claude_folders").update({ rescan_requested_at: null }).eq("id", folder.id);
@@ -4072,7 +4103,7 @@ Linking "${folder.name}" to this device...
4072
4103
  scanned_at: result.scannedAt,
4073
4104
  cli_version: CURRENT_VERSION
4074
4105
  }, { onConflict: "folder_id,device_id" });
4075
- await pushToolings(supabase, folder.id, result.toolings);
4106
+ await pushToolings(supabase, folder.id, result.toolings, deviceId);
4076
4107
  const graphPaths = result.graph.nodes.map((n) => n.filePath);
4077
4108
  const configFiles = await readClaudeConfigFiles(cwd, graphPaths);
4078
4109
  if (configFiles.length > 0) {
@@ -5023,18 +5054,29 @@ initSentry();
5023
5054
  if (process.argv.length <= 2) {
5024
5055
  void startCommand();
5025
5056
  } else {
5026
- program.parse();
5027
- const ran = program.args[0];
5028
- const skipAutoCheck = ["update", "check-update", "mcp-watch", "start"];
5029
- if (!skipAutoCheck.includes(ran)) {
5030
- autoCheckForUpdate();
5031
- }
5057
+ program.parseAsync().then(() => {
5058
+ const ran = program.args[0];
5059
+ const skipAutoCheck = ["update", "check-update", "mcp-watch", "start"];
5060
+ if (!skipAutoCheck.includes(ran)) {
5061
+ autoCheckForUpdate();
5062
+ }
5063
+ }).catch(async (err) => {
5064
+ captureException2(err);
5065
+ const msg = err instanceof Error ? err.message : String(err);
5066
+ console.error(`\x1B[31mError: ${msg}\x1B[0m`);
5067
+ await flushSentry();
5068
+ process.exit(1);
5069
+ });
5032
5070
  }
5033
5071
  process.on("uncaughtException", async (err) => {
5034
5072
  captureException2(err);
5035
5073
  await flushSentry();
5036
5074
  process.exit(1);
5037
5075
  });
5038
- process.on("unhandledRejection", (reason) => {
5076
+ process.on("unhandledRejection", async (reason) => {
5039
5077
  captureException2(reason);
5078
+ const msg = reason instanceof Error ? reason.message : String(reason);
5079
+ console.error(`\x1B[31mError: ${msg}\x1B[0m`);
5080
+ await flushSentry();
5081
+ process.exit(1);
5040
5082
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md4ai",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "CLI for MD4AI — scan Claude projects and sync to your dashboard",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,7 @@
31
31
  "directory": "cli"
32
32
  },
33
33
  "engines": {
34
- "node": ">=22"
34
+ "node": ">=20"
35
35
  },
36
36
  "dependencies": {
37
37
  "@inquirer/prompts": "^8.3.0",