md4ai 0.13.2 → 0.14.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 +160 -69
  2. package/package.json +1 -1
@@ -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.2" : "0.0.0-dev";
125
+ CURRENT_VERSION = true ? "0.14.0" : "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"]
@@ -2294,44 +2399,6 @@ var init_push_health_results = __esm({
2294
2399
  }
2295
2400
  });
2296
2401
 
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
2402
  // dist/commands/map.js
2336
2403
  var map_exports = {};
2337
2404
  __export(map_exports, {
@@ -2343,7 +2410,12 @@ import { existsSync as existsSync8 } from "node:fs";
2343
2410
  import chalk12 from "chalk";
2344
2411
  import { select as select4, input as input5, confirm as confirm2 } from "@inquirer/prompts";
2345
2412
  async function mapCommand(path, options) {
2346
- await checkForUpdate();
2413
+ const args = path ? ["scan", path] : ["scan"];
2414
+ if (options.offline)
2415
+ args.push("--offline");
2416
+ const shouldContinue = await promptUpdateIfAvailable(args);
2417
+ if (!shouldContinue)
2418
+ return;
2347
2419
  const projectRoot = resolve4(path ?? process.cwd());
2348
2420
  if (!existsSync8(projectRoot)) {
2349
2421
  console.error(chalk12.red(`Path not found: ${projectRoot}`));
@@ -2555,7 +2627,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2555
2627
  folderId = chosen;
2556
2628
  }
2557
2629
  const deviceName = detectDeviceName();
2558
- const osType = detectOs2();
2630
+ const osType = detectOs();
2559
2631
  await sb.from("devices").upsert({
2560
2632
  user_id: userId2,
2561
2633
  device_name: deviceName,
@@ -2945,7 +3017,7 @@ var init_read_configs = __esm({
2945
3017
  });
2946
3018
 
2947
3019
  // dist/mcp/scan-processes.js
2948
- import { execFileSync as execFileSync4 } from "node:child_process";
3020
+ import { execFileSync as execFileSync5 } from "node:child_process";
2949
3021
  import { readlinkSync } from "node:fs";
2950
3022
  function parsePsOutput(output) {
2951
3023
  const lines = output.trim().split("\n").slice(1);
@@ -3026,7 +3098,7 @@ function findProcessesForConfig(config2, processes) {
3026
3098
  }
3027
3099
  function getProcessTable() {
3028
3100
  try {
3029
- const output = execFileSync4("ps", ["-eo", "pid,tty,etime,rss,args"], {
3101
+ const output = execFileSync5("ps", ["-eo", "pid,tty,etime,rss,args"], {
3030
3102
  encoding: "utf-8",
3031
3103
  timeout: 5e3
3032
3104
  });
@@ -3067,11 +3139,11 @@ __export(mcp_watch_exports, {
3067
3139
  mcpWatchCommand: () => mcpWatchCommand
3068
3140
  });
3069
3141
  import chalk21 from "chalk";
3070
- import { execFileSync as execFileSync5 } from "node:child_process";
3142
+ import { execFileSync as execFileSync6 } from "node:child_process";
3071
3143
  import { createHash as createHash2 } from "node:crypto";
3072
3144
  function detectTty() {
3073
3145
  try {
3074
- const result = execFileSync5("ps", ["-o", "tty=", "-p", String(process.pid)], {
3146
+ const result = execFileSync6("ps", ["-o", "tty=", "-p", String(process.pid)], {
3075
3147
  encoding: "utf-8",
3076
3148
  timeout: 3e3
3077
3149
  }).trim();
@@ -3668,11 +3740,11 @@ Folder "${name}" created ${teamLabel} (${data.id})`));
3668
3740
  // dist/commands/add-device.js
3669
3741
  init_auth();
3670
3742
  import { resolve } from "node:path";
3671
- import { hostname, platform } from "node:os";
3743
+ import { hostname as hostname2, platform as platform2 } from "node:os";
3672
3744
  import chalk7 from "chalk";
3673
3745
  import { input as input4, select as select2 } from "@inquirer/prompts";
3674
- function detectOs() {
3675
- const p = platform();
3746
+ function detectOs2() {
3747
+ const p = platform2();
3676
3748
  if (p === "win32")
3677
3749
  return "windows";
3678
3750
  if (p === "darwin")
@@ -3682,8 +3754,8 @@ function detectOs() {
3682
3754
  return "other";
3683
3755
  }
3684
3756
  function suggestDeviceName() {
3685
- const os = detectOs();
3686
- const host = hostname().split(".")[0];
3757
+ const os = detectOs2();
3758
+ const host = hostname2().split(".")[0];
3687
3759
  const osLabel = os.charAt(0).toUpperCase() + os.slice(1);
3688
3760
  return `${host}-${osLabel}`;
3689
3761
  }
@@ -3718,7 +3790,7 @@ async function addDeviceCommand() {
3718
3790
  { name: "Linux", value: "linux" },
3719
3791
  { name: "Other", value: "other" }
3720
3792
  ],
3721
- default: detectOs()
3793
+ default: detectOs2()
3722
3794
  });
3723
3795
  const description = await input4({ message: "Description (optional):" });
3724
3796
  const { error } = await supabase.from("device_paths").insert({
@@ -3911,10 +3983,13 @@ init_check_update();
3911
3983
  import { resolve as resolve6 } from "node:path";
3912
3984
  import chalk16 from "chalk";
3913
3985
  async function linkCommand(projectId) {
3986
+ const shouldContinue = await promptUpdateIfAvailable(["link", projectId]);
3987
+ if (!shouldContinue)
3988
+ return;
3914
3989
  const { supabase, userId } = await getAuthenticatedClient();
3915
3990
  const cwd = resolve6(process.cwd());
3916
3991
  const deviceName = detectDeviceName();
3917
- const osType = detectOs2();
3992
+ const osType = detectOs();
3918
3993
  const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
3919
3994
  if (folderErr || !folder) {
3920
3995
  console.error(chalk16.red("Project not found, or you do not have access."));
@@ -4526,8 +4601,9 @@ Manifest created: ${relative4(projectRoot, manifestPath)}`));
4526
4601
  // dist/commands/update.js
4527
4602
  init_check_update();
4528
4603
  init_config();
4604
+ init_auth();
4529
4605
  import chalk23 from "chalk";
4530
- import { execFileSync as execFileSync6, spawn } from "node:child_process";
4606
+ import { execFileSync as execFileSync7, spawn as spawn2 } from "node:child_process";
4531
4607
  async function fetchLatestVersion() {
4532
4608
  try {
4533
4609
  const controller = new AbortController();
@@ -4555,9 +4631,9 @@ function isNewer2(a, b) {
4555
4631
  }
4556
4632
  return false;
4557
4633
  }
4558
- function runNpmInstall() {
4634
+ function runNpmInstall2() {
4559
4635
  try {
4560
- execFileSync6("npm", ["install", "-g", "md4ai"], {
4636
+ execFileSync7("npm", ["install", "-g", "md4ai"], {
4561
4637
  stdio: "inherit",
4562
4638
  timeout: 6e4
4563
4639
  });
@@ -4567,7 +4643,7 @@ function runNpmInstall() {
4567
4643
  }
4568
4644
  }
4569
4645
  function spawnPostUpdate() {
4570
- const child = spawn("md4ai", ["update", "--post-update"], {
4646
+ const child = spawn2("md4ai", ["update", "--post-update"], {
4571
4647
  stdio: "inherit",
4572
4648
  shell: false
4573
4649
  });
@@ -4596,13 +4672,28 @@ async function postUpdateFlow() {
4596
4672
  return;
4597
4673
  }
4598
4674
  }
4675
+ const { supabase } = await getAuthenticatedClient();
4676
+ const { data: linkedPaths } = await supabase.from("device_paths").select("path, folder_id").order("path");
4677
+ let linkedDisplay = "";
4678
+ if (linkedPaths?.length) {
4679
+ const folderIds = [...new Set(linkedPaths.map((p) => p.folder_id))];
4680
+ const { data: folders } = await supabase.from("claude_folders").select("id, name").in("id", folderIds);
4681
+ const nameMap = new Map((folders ?? []).map((f) => [f.id, f.name]));
4682
+ const lines = linkedPaths.map((p) => {
4683
+ const name = nameMap.get(p.folder_id) ?? "Unknown";
4684
+ return ` ${name} (${p.path})`;
4685
+ });
4686
+ linkedDisplay = `
4687
+ ${lines.join("\n")}`;
4688
+ }
4599
4689
  const { select: selectPrompt } = await import("@inquirer/prompts");
4600
4690
  const cwd = process.cwd();
4691
+ const allLabel = linkedPaths?.length ? `All ${linkedPaths.length} linked project${linkedPaths.length === 1 ? "" : "s"} on this device${linkedDisplay}` : "All linked projects on this device";
4601
4692
  const scanChoice = await selectPrompt({
4602
4693
  message: "What would you like to scan?",
4603
4694
  choices: [
4604
4695
  { name: `Current folder (${cwd})`, value: "cwd" },
4605
- { name: "All linked projects on this device", value: "all" },
4696
+ { name: allLabel, value: "all" },
4606
4697
  { name: "Skip scanning", value: "skip" }
4607
4698
  ]
4608
4699
  });
@@ -4658,7 +4749,7 @@ async function updateCommand(options) {
4658
4749
  return;
4659
4750
  }
4660
4751
  console.log(chalk23.blue("\n Installing...\n"));
4661
- const ok = runNpmInstall();
4752
+ const ok = runNpmInstall2();
4662
4753
  if (!ok) {
4663
4754
  console.log(chalk23.red("\n npm install failed. Try running manually:"));
4664
4755
  console.log(chalk23.cyan(" npm install -g md4ai\n"));
@@ -4718,17 +4809,17 @@ async function startCommand() {
4718
4809
  default: true
4719
4810
  });
4720
4811
  if (wantUpdate) {
4721
- const { execFileSync: execFileSync7 } = await import("node:child_process");
4812
+ const { execFileSync: execFileSync8 } = await import("node:child_process");
4722
4813
  console.log(chalk24.blue("\n Installing...\n"));
4723
4814
  try {
4724
- execFileSync7("npm", ["install", "-g", "md4ai"], {
4815
+ execFileSync8("npm", ["install", "-g", "md4ai"], {
4725
4816
  stdio: "inherit",
4726
4817
  timeout: 6e4
4727
4818
  });
4728
4819
  console.log(chalk24.green(" Updated successfully."));
4729
4820
  console.log(chalk24.dim(" Restarting with the new version...\n"));
4730
- const { spawn: spawn2 } = await import("node:child_process");
4731
- const child = spawn2("md4ai", ["start"], { stdio: "inherit", shell: false });
4821
+ const { spawn: spawn3 } = await import("node:child_process");
4822
+ const child = spawn3("md4ai", ["start"], { stdio: "inherit", shell: false });
4732
4823
  child.on("exit", (code) => process.exit(code ?? 0));
4733
4824
  return;
4734
4825
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md4ai",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "description": "CLI for MD4AI — scan Claude projects and sync to your dashboard",
5
5
  "type": "module",
6
6
  "bin": {