md4ai 0.13.3 → 0.14.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.
Files changed (2) hide show
  1. package/dist/index.bundled.js +185 -96
  2. package/package.json +2 -2
@@ -11,6 +11,7 @@ var __export = (target, all) => {
11
11
 
12
12
  // dist/check-update.js
13
13
  import chalk from "chalk";
14
+ import { execFileSync, spawn } from "node:child_process";
14
15
  async function fetchLatest() {
15
16
  try {
16
17
  const controller = new AbortController();
@@ -27,6 +28,17 @@ async function fetchLatest() {
27
28
  return null;
28
29
  }
29
30
  }
31
+ function runNpmInstall() {
32
+ try {
33
+ execFileSync("npm", ["install", "-g", "md4ai"], {
34
+ stdio: "inherit",
35
+ timeout: 6e4
36
+ });
37
+ return true;
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
30
42
  function printUpdateBanner(latest) {
31
43
  console.log("");
32
44
  console.log(chalk.yellow("\u250C\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\u2510"));
@@ -69,11 +81,48 @@ function isNewer(a, b) {
69
81
  }
70
82
  return false;
71
83
  }
84
+ async function promptUpdateIfAvailable(originalArgs) {
85
+ try {
86
+ const latest = await fetchLatest();
87
+ if (!latest || !isNewer(latest, CURRENT_VERSION)) {
88
+ return true;
89
+ }
90
+ console.log("");
91
+ console.log(chalk.yellow("\u250C\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\u2510"));
92
+ console.log(chalk.yellow("\u2502") + chalk.bold(" Update available! ") + chalk.dim(`${CURRENT_VERSION}`) + chalk.white(" \u2192 ") + chalk.green.bold(`${latest}`) + " " + chalk.yellow("\u2502"));
93
+ 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"));
94
+ console.log("");
95
+ const { confirm: confirm4 } = await import("@inquirer/prompts");
96
+ const wantUpdate = await confirm4({
97
+ message: `Update to v${latest} before continuing?`,
98
+ default: true
99
+ });
100
+ if (!wantUpdate) {
101
+ console.log(chalk.dim("Continuing with current version...\n"));
102
+ return true;
103
+ }
104
+ console.log(chalk.blue("\n Installing update...\n"));
105
+ const ok = runNpmInstall();
106
+ if (!ok) {
107
+ console.log(chalk.red("\n Update failed. Continuing with current version.\n"));
108
+ return true;
109
+ }
110
+ console.log(chalk.green("\n Updated successfully! Re-running command...\n"));
111
+ const child = spawn("md4ai", originalArgs, {
112
+ stdio: "inherit",
113
+ shell: false
114
+ });
115
+ child.on("exit", (code) => process.exit(code ?? 0));
116
+ return false;
117
+ } catch {
118
+ return true;
119
+ }
120
+ }
72
121
  var CURRENT_VERSION;
73
122
  var init_check_update = __esm({
74
123
  "dist/check-update.js"() {
75
124
  "use strict";
76
- CURRENT_VERSION = true ? "0.13.3" : "0.0.0-dev";
125
+ CURRENT_VERSION = true ? "0.14.1" : "0.0.0-dev";
77
126
  }
78
127
  });
79
128
 
@@ -254,6 +303,59 @@ var init_login = __esm({
254
303
  }
255
304
  });
256
305
 
306
+ // dist/device-utils.js
307
+ import { hostname, platform } from "node:os";
308
+ function detectOs() {
309
+ const p = platform();
310
+ if (p === "win32")
311
+ return "windows";
312
+ if (p === "darwin")
313
+ return "macos";
314
+ if (p === "linux")
315
+ return "linux";
316
+ return "other";
317
+ }
318
+ function detectDeviceName() {
319
+ const os = detectOs();
320
+ const host = hostname().split(".")[0];
321
+ const osLabel = os.charAt(0).toUpperCase() + os.slice(1);
322
+ return `${host}-${osLabel}`;
323
+ }
324
+ async function resolveDeviceId(supabase, userId) {
325
+ const deviceName = detectDeviceName();
326
+ const osType = detectOs();
327
+ await supabase.from("devices").upsert({
328
+ user_id: userId,
329
+ device_name: deviceName,
330
+ os_type: osType,
331
+ last_cli_version: CURRENT_VERSION,
332
+ last_cli_seen_at: (/* @__PURE__ */ new Date()).toISOString()
333
+ }, { onConflict: "user_id,device_name" });
334
+ const { data, error } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", deviceName).single();
335
+ if (error || !data) {
336
+ throw new Error(`Failed to resolve device ID: ${error?.message ?? "not found"}`);
337
+ }
338
+ return data.id;
339
+ }
340
+ async function updateDeviceCliVersion(supabase, userId) {
341
+ const deviceName = detectDeviceName();
342
+ const osType = detectOs();
343
+ await supabase.from("devices").upsert({
344
+ user_id: userId,
345
+ device_name: deviceName,
346
+ os_type: osType,
347
+ last_cli_version: CURRENT_VERSION,
348
+ last_cli_seen_at: (/* @__PURE__ */ new Date()).toISOString()
349
+ }, { onConflict: "user_id,device_name" }).then(() => {
350
+ });
351
+ }
352
+ var init_device_utils = __esm({
353
+ "dist/device-utils.js"() {
354
+ "use strict";
355
+ init_check_update();
356
+ }
357
+ });
358
+
257
359
  // dist/auth.js
258
360
  import chalk5 from "chalk";
259
361
  import { confirm, input as input2, password as password2 } from "@inquirer/prompts";
@@ -271,6 +373,8 @@ async function getAuthenticatedClient() {
271
373
  }
272
374
  const anonKey = getAnonKey();
273
375
  const supabase = createSupabaseClient(anonKey, creds.accessToken);
376
+ updateDeviceCliVersion(supabase, creds.userId).catch(() => {
377
+ });
274
378
  return { supabase, userId: creds.userId };
275
379
  }
276
380
  async function refreshSession() {
@@ -349,6 +453,7 @@ var init_auth = __esm({
349
453
  "use strict";
350
454
  init_dist();
351
455
  init_config();
456
+ init_device_utils();
352
457
  }
353
458
  });
354
459
 
@@ -509,11 +614,11 @@ var init_file_parser = __esm({
509
614
  });
510
615
 
511
616
  // dist/scanner/git-dates.js
512
- import { execFileSync } from "node:child_process";
617
+ import { execFileSync as execFileSync2 } from "node:child_process";
513
618
  import { statSync } from "node:fs";
514
619
  function getGitLastModified(filePath, cwd) {
515
620
  try {
516
- const result = execFileSync("git", ["log", "-1", "--format=%cI", "--", filePath], { cwd, encoding: "utf-8", timeout: 5e3 }).trim();
621
+ const result = execFileSync2("git", ["log", "-1", "--format=%cI", "--", filePath], { cwd, encoding: "utf-8", timeout: 5e3 }).trim();
517
622
  return result || null;
518
623
  } catch {
519
624
  try {
@@ -525,7 +630,7 @@ function getGitLastModified(filePath, cwd) {
525
630
  }
526
631
  function getGitCreationDate(filePath, cwd) {
527
632
  try {
528
- const result = execFileSync("git", ["log", "--diff-filter=A", "--format=%cI", "--", filePath], { cwd, encoding: "utf-8", timeout: 5e3 }).trim();
633
+ const result = execFileSync2("git", ["log", "--diff-filter=A", "--format=%cI", "--", filePath], { cwd, encoding: "utf-8", timeout: 5e3 }).trim();
529
634
  return result || null;
530
635
  } catch {
531
636
  try {
@@ -671,7 +776,7 @@ var init_orphan_detector = __esm({
671
776
  });
672
777
 
673
778
  // dist/scanner/skills-parser.js
674
- import { execFileSync as execFileSync2 } from "node:child_process";
779
+ import { execFileSync as execFileSync3 } from "node:child_process";
675
780
  import { readFile as readFile3 } from "node:fs/promises";
676
781
  import { existsSync as existsSync2 } from "node:fs";
677
782
  import { join as join5 } from "node:path";
@@ -679,7 +784,7 @@ import { homedir as homedir3 } from "node:os";
679
784
  async function parseSkills(projectRoot) {
680
785
  const skills = /* @__PURE__ */ new Map();
681
786
  try {
682
- const output = execFileSync2("claude", ["plugin", "list"], { encoding: "utf-8", timeout: 1e4 }).trim();
787
+ const output = execFileSync3("claude", ["plugin", "list"], { encoding: "utf-8", timeout: 1e4 }).trim();
683
788
  if (output) {
684
789
  for (const line of output.split("\n")) {
685
790
  const name = line.trim().split(/\s+/)[0];
@@ -764,7 +869,7 @@ var init_skills_parser = __esm({
764
869
  import { readFile as readFile4, readdir } from "node:fs/promises";
765
870
  import { existsSync as existsSync3 } from "node:fs";
766
871
  import { join as join6 } from "node:path";
767
- import { execFileSync as execFileSync3 } from "node:child_process";
872
+ import { execFileSync as execFileSync4 } from "node:child_process";
768
873
  import { homedir as homedir4 } from "node:os";
769
874
  async function detectToolings(projectRoot) {
770
875
  const toolings = [];
@@ -932,7 +1037,7 @@ function detectFromCli(projectRoot) {
932
1037
  continue;
933
1038
  }
934
1039
  try {
935
- const output = execFileSync3(command, args, {
1040
+ const output = execFileSync4(command, args, {
936
1041
  encoding: "utf-8",
937
1042
  timeout: 3e3,
938
1043
  stdio: ["pipe", "pipe", "pipe"]
@@ -1046,11 +1151,23 @@ var init_auth2 = __esm({
1046
1151
  });
1047
1152
 
1048
1153
  // dist/vercel/discover-projects.js
1049
- import { readFile as readFile6, glob } from "node:fs/promises";
1154
+ import { readFile as readFile6, readdir as readdir2 } from "node:fs/promises";
1050
1155
  import { join as join8, dirname as dirname2, relative } from "node:path";
1051
1156
  async function discoverVercelProjects(projectRoot) {
1052
1157
  const found = /* @__PURE__ */ new Map();
1053
- for await (const filePath of glob(join8(projectRoot, "**/.vercel/project.json"))) {
1158
+ let entries;
1159
+ try {
1160
+ entries = await readdir2(projectRoot, { recursive: true, withFileTypes: true });
1161
+ } catch {
1162
+ return [];
1163
+ }
1164
+ for (const entry of entries) {
1165
+ if (!entry.isFile() || entry.name !== "project.json")
1166
+ continue;
1167
+ const parentName = typeof entry.parentPath === "string" ? entry.parentPath : entry.path;
1168
+ if (!parentName.endsWith(".vercel"))
1169
+ continue;
1170
+ const filePath = join8(parentName, entry.name);
1054
1171
  try {
1055
1172
  const data = await readFile6(filePath, "utf-8");
1056
1173
  const parsed = JSON.parse(data);
@@ -1145,7 +1262,7 @@ var init_fetch_env_vars = __esm({
1145
1262
  });
1146
1263
 
1147
1264
  // dist/scanner/env-manifest-scanner.js
1148
- import { readFile as readFile7, glob as glob2 } from "node:fs/promises";
1265
+ import { readFile as readFile7, readdir as readdir3 } from "node:fs/promises";
1149
1266
  import { execFile } from "node:child_process";
1150
1267
  import { promisify } from "node:util";
1151
1268
  import { join as join9, relative as relative2 } from "node:path";
@@ -1414,21 +1531,23 @@ async function parseWorkflowSecrets(projectRoot) {
1414
1531
  return [];
1415
1532
  const refs = [];
1416
1533
  const secretRe = /\$\{\{\s*secrets\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;
1417
- for (const ext of ["*.yml", "*.yaml"]) {
1418
- for await (const filePath of glob2(join9(workflowsDir, ext))) {
1419
- const content = await readFile7(filePath, "utf-8");
1420
- const secrets = /* @__PURE__ */ new Set();
1421
- let m;
1422
- while ((m = secretRe.exec(content)) !== null) {
1423
- secrets.add(m[1]);
1424
- }
1425
- secretRe.lastIndex = 0;
1426
- if (secrets.size > 0) {
1427
- refs.push({
1428
- workflowFile: relative2(projectRoot, filePath),
1429
- secretsUsed: [...secrets].sort()
1430
- });
1431
- }
1534
+ const files = await readdir3(workflowsDir);
1535
+ for (const file of files) {
1536
+ if (!file.endsWith(".yml") && !file.endsWith(".yaml"))
1537
+ continue;
1538
+ const filePath = join9(workflowsDir, file);
1539
+ const content = await readFile7(filePath, "utf-8");
1540
+ const secrets = /* @__PURE__ */ new Set();
1541
+ let m;
1542
+ while ((m = secretRe.exec(content)) !== null) {
1543
+ secrets.add(m[1]);
1544
+ }
1545
+ secretRe.lastIndex = 0;
1546
+ if (secrets.size > 0) {
1547
+ refs.push({
1548
+ workflowFile: relative2(projectRoot, filePath),
1549
+ secretsUsed: [...secrets].sort()
1550
+ });
1432
1551
  }
1433
1552
  }
1434
1553
  return refs;
@@ -1555,7 +1674,7 @@ var init_env_manifest_scanner = __esm({
1555
1674
  });
1556
1675
 
1557
1676
  // dist/scanner/marketplace-scanner.js
1558
- import { readFile as readFile8, readdir as readdir2, stat } from "node:fs/promises";
1677
+ import { readFile as readFile8, readdir as readdir4, stat } from "node:fs/promises";
1559
1678
  import { join as join10, resolve as resolve3 } from "node:path";
1560
1679
  import { existsSync as existsSync5 } from "node:fs";
1561
1680
  import { homedir as homedir6 } from "node:os";
@@ -1624,7 +1743,7 @@ async function discoverSkills(installPath) {
1624
1743
  return [];
1625
1744
  const skills = [];
1626
1745
  try {
1627
- const entries = await readdir2(skillsDir, { withFileTypes: true });
1746
+ const entries = await readdir4(skillsDir, { withFileTypes: true });
1628
1747
  for (const entry of entries) {
1629
1748
  if (!entry.isDirectory())
1630
1749
  continue;
@@ -1881,7 +2000,7 @@ var init_doppler_scanner = __esm({
1881
2000
  });
1882
2001
 
1883
2002
  // dist/scanner/index.js
1884
- import { readdir as readdir3 } from "node:fs/promises";
2003
+ import { readdir as readdir5 } from "node:fs/promises";
1885
2004
  import { join as join12, relative as relative3 } from "node:path";
1886
2005
  import { existsSync as existsSync7 } from "node:fs";
1887
2006
  import { homedir as homedir7 } from "node:os";
@@ -1968,7 +2087,7 @@ async function discoverFiles(projectRoot) {
1968
2087
  return [...new Set(files)];
1969
2088
  }
1970
2089
  async function walkDir(dir, projectRoot, files) {
1971
- const entries = await readdir3(dir, { withFileTypes: true });
2090
+ const entries = await readdir5(dir, { withFileTypes: true });
1972
2091
  for (const entry of entries) {
1973
2092
  const fullPath = join12(dir, entry.name);
1974
2093
  if (entry.isDirectory()) {
@@ -2294,44 +2413,6 @@ var init_push_health_results = __esm({
2294
2413
  }
2295
2414
  });
2296
2415
 
2297
- // dist/device-utils.js
2298
- import { hostname as hostname2, platform as platform2 } from "node:os";
2299
- function detectOs2() {
2300
- const p = platform2();
2301
- if (p === "win32")
2302
- return "windows";
2303
- if (p === "darwin")
2304
- return "macos";
2305
- if (p === "linux")
2306
- return "linux";
2307
- return "other";
2308
- }
2309
- function detectDeviceName() {
2310
- const os = detectOs2();
2311
- const host = hostname2().split(".")[0];
2312
- const osLabel = os.charAt(0).toUpperCase() + os.slice(1);
2313
- return `${host}-${osLabel}`;
2314
- }
2315
- async function resolveDeviceId(supabase, userId) {
2316
- const deviceName = detectDeviceName();
2317
- const osType = detectOs2();
2318
- await supabase.from("devices").upsert({
2319
- user_id: userId,
2320
- device_name: deviceName,
2321
- os_type: osType
2322
- }, { onConflict: "user_id,device_name" });
2323
- const { data, error } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", deviceName).single();
2324
- if (error || !data) {
2325
- throw new Error(`Failed to resolve device ID: ${error?.message ?? "not found"}`);
2326
- }
2327
- return data.id;
2328
- }
2329
- var init_device_utils = __esm({
2330
- "dist/device-utils.js"() {
2331
- "use strict";
2332
- }
2333
- });
2334
-
2335
2416
  // dist/commands/map.js
2336
2417
  var map_exports = {};
2337
2418
  __export(map_exports, {
@@ -2343,7 +2424,12 @@ import { existsSync as existsSync8 } from "node:fs";
2343
2424
  import chalk12 from "chalk";
2344
2425
  import { select as select4, input as input5, confirm as confirm2 } from "@inquirer/prompts";
2345
2426
  async function mapCommand(path, options) {
2346
- await checkForUpdate();
2427
+ const args = path ? ["scan", path] : ["scan"];
2428
+ if (options.offline)
2429
+ args.push("--offline");
2430
+ const shouldContinue = await promptUpdateIfAvailable(args);
2431
+ if (!shouldContinue)
2432
+ return;
2347
2433
  const projectRoot = resolve4(path ?? process.cwd());
2348
2434
  if (!existsSync8(projectRoot)) {
2349
2435
  console.error(chalk12.red(`Path not found: ${projectRoot}`));
@@ -2555,7 +2641,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2555
2641
  folderId = chosen;
2556
2642
  }
2557
2643
  const deviceName = detectDeviceName();
2558
- const osType = detectOs2();
2644
+ const osType = detectOs();
2559
2645
  await sb.from("devices").upsert({
2560
2646
  user_id: userId2,
2561
2647
  device_name: deviceName,
@@ -2806,7 +2892,7 @@ import { readFile as readFile12 } from "node:fs/promises";
2806
2892
  import { join as join15 } from "node:path";
2807
2893
  import { homedir as homedir9 } from "node:os";
2808
2894
  import { existsSync as existsSync13 } from "node:fs";
2809
- import { readdir as readdir4 } from "node:fs/promises";
2895
+ import { readdir as readdir6 } from "node:fs/promises";
2810
2896
  async function readJsonSafe(path) {
2811
2897
  try {
2812
2898
  if (!existsSync13(path))
@@ -2882,14 +2968,14 @@ async function readAllMcpConfigs() {
2882
2968
  const pluginsBase = join15(home, ".claude", "plugins", "marketplaces");
2883
2969
  if (existsSync13(pluginsBase)) {
2884
2970
  try {
2885
- const marketplaces = await readdir4(pluginsBase, { withFileTypes: true });
2971
+ const marketplaces = await readdir6(pluginsBase, { withFileTypes: true });
2886
2972
  for (const mp of marketplaces) {
2887
2973
  if (!mp.isDirectory())
2888
2974
  continue;
2889
2975
  const extDir = join15(pluginsBase, mp.name, "external_plugins");
2890
2976
  if (!existsSync13(extDir))
2891
2977
  continue;
2892
- const plugins = await readdir4(extDir, { withFileTypes: true });
2978
+ const plugins = await readdir6(extDir, { withFileTypes: true });
2893
2979
  for (const plugin of plugins) {
2894
2980
  if (!plugin.isDirectory())
2895
2981
  continue;
@@ -2903,16 +2989,16 @@ async function readAllMcpConfigs() {
2903
2989
  const cacheBase = join15(home, ".claude", "plugins", "cache");
2904
2990
  if (existsSync13(cacheBase)) {
2905
2991
  try {
2906
- const registries = await readdir4(cacheBase, { withFileTypes: true });
2992
+ const registries = await readdir6(cacheBase, { withFileTypes: true });
2907
2993
  for (const reg of registries) {
2908
2994
  if (!reg.isDirectory())
2909
2995
  continue;
2910
2996
  const regDir = join15(cacheBase, reg.name);
2911
- const plugins = await readdir4(regDir, { withFileTypes: true });
2997
+ const plugins = await readdir6(regDir, { withFileTypes: true });
2912
2998
  for (const plugin of plugins) {
2913
2999
  if (!plugin.isDirectory())
2914
3000
  continue;
2915
- const versionDirs = await readdir4(join15(regDir, plugin.name), { withFileTypes: true });
3001
+ const versionDirs = await readdir6(join15(regDir, plugin.name), { withFileTypes: true });
2916
3002
  for (const ver of versionDirs) {
2917
3003
  if (!ver.isDirectory())
2918
3004
  continue;
@@ -2945,7 +3031,7 @@ var init_read_configs = __esm({
2945
3031
  });
2946
3032
 
2947
3033
  // dist/mcp/scan-processes.js
2948
- import { execFileSync as execFileSync4 } from "node:child_process";
3034
+ import { execFileSync as execFileSync5 } from "node:child_process";
2949
3035
  import { readlinkSync } from "node:fs";
2950
3036
  function parsePsOutput(output) {
2951
3037
  const lines = output.trim().split("\n").slice(1);
@@ -3026,7 +3112,7 @@ function findProcessesForConfig(config2, processes) {
3026
3112
  }
3027
3113
  function getProcessTable() {
3028
3114
  try {
3029
- const output = execFileSync4("ps", ["-eo", "pid,tty,etime,rss,args"], {
3115
+ const output = execFileSync5("ps", ["-eo", "pid,tty,etime,rss,args"], {
3030
3116
  encoding: "utf-8",
3031
3117
  timeout: 5e3
3032
3118
  });
@@ -3067,11 +3153,11 @@ __export(mcp_watch_exports, {
3067
3153
  mcpWatchCommand: () => mcpWatchCommand
3068
3154
  });
3069
3155
  import chalk21 from "chalk";
3070
- import { execFileSync as execFileSync5 } from "node:child_process";
3156
+ import { execFileSync as execFileSync6 } from "node:child_process";
3071
3157
  import { createHash as createHash2 } from "node:crypto";
3072
3158
  function detectTty() {
3073
3159
  try {
3074
- const result = execFileSync5("ps", ["-o", "tty=", "-p", String(process.pid)], {
3160
+ const result = execFileSync6("ps", ["-o", "tty=", "-p", String(process.pid)], {
3075
3161
  encoding: "utf-8",
3076
3162
  timeout: 3e3
3077
3163
  }).trim();
@@ -3668,11 +3754,11 @@ Folder "${name}" created ${teamLabel} (${data.id})`));
3668
3754
  // dist/commands/add-device.js
3669
3755
  init_auth();
3670
3756
  import { resolve } from "node:path";
3671
- import { hostname, platform } from "node:os";
3757
+ import { hostname as hostname2, platform as platform2 } from "node:os";
3672
3758
  import chalk7 from "chalk";
3673
3759
  import { input as input4, select as select2 } from "@inquirer/prompts";
3674
- function detectOs() {
3675
- const p = platform();
3760
+ function detectOs2() {
3761
+ const p = platform2();
3676
3762
  if (p === "win32")
3677
3763
  return "windows";
3678
3764
  if (p === "darwin")
@@ -3682,8 +3768,8 @@ function detectOs() {
3682
3768
  return "other";
3683
3769
  }
3684
3770
  function suggestDeviceName() {
3685
- const os = detectOs();
3686
- const host = hostname().split(".")[0];
3771
+ const os = detectOs2();
3772
+ const host = hostname2().split(".")[0];
3687
3773
  const osLabel = os.charAt(0).toUpperCase() + os.slice(1);
3688
3774
  return `${host}-${osLabel}`;
3689
3775
  }
@@ -3718,7 +3804,7 @@ async function addDeviceCommand() {
3718
3804
  { name: "Linux", value: "linux" },
3719
3805
  { name: "Other", value: "other" }
3720
3806
  ],
3721
- default: detectOs()
3807
+ default: detectOs2()
3722
3808
  });
3723
3809
  const description = await input4({ message: "Description (optional):" });
3724
3810
  const { error } = await supabase.from("device_paths").insert({
@@ -3911,10 +3997,13 @@ init_check_update();
3911
3997
  import { resolve as resolve6 } from "node:path";
3912
3998
  import chalk16 from "chalk";
3913
3999
  async function linkCommand(projectId) {
4000
+ const shouldContinue = await promptUpdateIfAvailable(["link", projectId]);
4001
+ if (!shouldContinue)
4002
+ return;
3914
4003
  const { supabase, userId } = await getAuthenticatedClient();
3915
4004
  const cwd = resolve6(process.cwd());
3916
4005
  const deviceName = detectDeviceName();
3917
- const osType = detectOs2();
4006
+ const osType = detectOs();
3918
4007
  const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
3919
4008
  if (folderErr || !folder) {
3920
4009
  console.error(chalk16.red("Project not found, or you do not have access."));
@@ -4528,7 +4617,7 @@ init_check_update();
4528
4617
  init_config();
4529
4618
  init_auth();
4530
4619
  import chalk23 from "chalk";
4531
- import { execFileSync as execFileSync6, spawn } from "node:child_process";
4620
+ import { execFileSync as execFileSync7, spawn as spawn2 } from "node:child_process";
4532
4621
  async function fetchLatestVersion() {
4533
4622
  try {
4534
4623
  const controller = new AbortController();
@@ -4556,9 +4645,9 @@ function isNewer2(a, b) {
4556
4645
  }
4557
4646
  return false;
4558
4647
  }
4559
- function runNpmInstall() {
4648
+ function runNpmInstall2() {
4560
4649
  try {
4561
- execFileSync6("npm", ["install", "-g", "md4ai"], {
4650
+ execFileSync7("npm", ["install", "-g", "md4ai"], {
4562
4651
  stdio: "inherit",
4563
4652
  timeout: 6e4
4564
4653
  });
@@ -4568,7 +4657,7 @@ function runNpmInstall() {
4568
4657
  }
4569
4658
  }
4570
4659
  function spawnPostUpdate() {
4571
- const child = spawn("md4ai", ["update", "--post-update"], {
4660
+ const child = spawn2("md4ai", ["update", "--post-update"], {
4572
4661
  stdio: "inherit",
4573
4662
  shell: false
4574
4663
  });
@@ -4674,7 +4763,7 @@ async function updateCommand(options) {
4674
4763
  return;
4675
4764
  }
4676
4765
  console.log(chalk23.blue("\n Installing...\n"));
4677
- const ok = runNpmInstall();
4766
+ const ok = runNpmInstall2();
4678
4767
  if (!ok) {
4679
4768
  console.log(chalk23.red("\n npm install failed. Try running manually:"));
4680
4769
  console.log(chalk23.cyan(" npm install -g md4ai\n"));
@@ -4734,17 +4823,17 @@ async function startCommand() {
4734
4823
  default: true
4735
4824
  });
4736
4825
  if (wantUpdate) {
4737
- const { execFileSync: execFileSync7 } = await import("node:child_process");
4826
+ const { execFileSync: execFileSync8 } = await import("node:child_process");
4738
4827
  console.log(chalk24.blue("\n Installing...\n"));
4739
4828
  try {
4740
- execFileSync7("npm", ["install", "-g", "md4ai"], {
4829
+ execFileSync8("npm", ["install", "-g", "md4ai"], {
4741
4830
  stdio: "inherit",
4742
4831
  timeout: 6e4
4743
4832
  });
4744
4833
  console.log(chalk24.green(" Updated successfully."));
4745
4834
  console.log(chalk24.dim(" Restarting with the new version...\n"));
4746
- const { spawn: spawn2 } = await import("node:child_process");
4747
- const child = spawn2("md4ai", ["start"], { stdio: "inherit", shell: false });
4835
+ const { spawn: spawn3 } = await import("node:child_process");
4836
+ const child = spawn3("md4ai", ["start"], { stdio: "inherit", shell: false });
4748
4837
  child.on("exit", (code) => process.exit(code ?? 0));
4749
4838
  return;
4750
4839
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md4ai",
3
- "version": "0.13.3",
3
+ "version": "0.14.1",
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",