md4ai 0.12.0 → 0.13.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 +243 -93
  2. package/package.json +1 -1
@@ -73,7 +73,7 @@ var CURRENT_VERSION;
73
73
  var init_check_update = __esm({
74
74
  "dist/check-update.js"() {
75
75
  "use strict";
76
- CURRENT_VERSION = true ? "0.12.0" : "0.0.0-dev";
76
+ CURRENT_VERSION = true ? "0.13.1" : "0.0.0-dev";
77
77
  }
78
78
  });
79
79
 
@@ -1656,21 +1656,40 @@ var init_marketplace_scanner = __esm({
1656
1656
 
1657
1657
  // dist/doppler/auth.js
1658
1658
  async function resolveDopplerToken() {
1659
+ const { join: join17 } = await import("node:path");
1660
+ const { homedir: homedir10 } = await import("node:os");
1661
+ const credPath = join17(homedir10(), ".md4ai", "credentials.json");
1659
1662
  const creds = await loadCredentials();
1660
1663
  if (creds?.dopplerToken) {
1661
- const { join: join17 } = await import("node:path");
1662
- const { homedir: homedir10 } = await import("node:os");
1663
- return {
1664
- token: creds.dopplerToken,
1665
- sourcePath: join17(homedir10(), ".md4ai", "credentials.json")
1666
- };
1664
+ return { token: creds.dopplerToken, sourcePath: credPath };
1665
+ }
1666
+ if (!creds?.accessToken || !creds?.userId)
1667
+ return null;
1668
+ try {
1669
+ let supabase = createSupabaseClient(getAnonKey(), creds.accessToken);
1670
+ let userId = creds.userId;
1671
+ if (Date.now() > creds.expiresAt) {
1672
+ const refreshed = await refreshSession();
1673
+ if (!refreshed)
1674
+ return null;
1675
+ supabase = refreshed.supabase;
1676
+ userId = refreshed.userId;
1677
+ }
1678
+ const { data } = await supabase.from("user_secrets").select("doppler_token").eq("user_id", userId).maybeSingle();
1679
+ if (!data?.doppler_token)
1680
+ return null;
1681
+ await mergeCredentials({ dopplerToken: data.doppler_token });
1682
+ return { token: data.doppler_token, sourcePath: "Supabase (cached locally)" };
1683
+ } catch {
1684
+ return null;
1667
1685
  }
1668
- return null;
1669
1686
  }
1670
1687
  var init_auth3 = __esm({
1671
1688
  "dist/doppler/auth.js"() {
1672
1689
  "use strict";
1673
1690
  init_config();
1691
+ init_auth();
1692
+ init_dist();
1674
1693
  }
1675
1694
  });
1676
1695
 
@@ -2175,7 +2194,7 @@ var init_push_toolings = __esm({
2175
2194
 
2176
2195
  // dist/commands/push-health-results.js
2177
2196
  import chalk11 from "chalk";
2178
- async function pushHealthResults(supabase, folderId, manifest) {
2197
+ async function pushHealthResults(supabase, folderId, manifest, deviceId) {
2179
2198
  const { data: checks } = await supabase.from("env_health_checks").select("id, check_type, check_config").eq("folder_id", folderId).eq("enabled", true);
2180
2199
  if (!checks?.length)
2181
2200
  return;
@@ -2193,7 +2212,8 @@ async function pushHealthResults(supabase, folderId, manifest) {
2193
2212
  check_id: chk.id,
2194
2213
  status: localStatus === "present" ? "pass" : "fail",
2195
2214
  message: localStatus === "present" ? "Set" : "Missing",
2196
- ran_at: ranAt
2215
+ ran_at: ranAt,
2216
+ device_id: deviceId ?? null
2197
2217
  });
2198
2218
  }
2199
2219
  } else if (chk.check_type === "file_exists") {
@@ -2201,7 +2221,8 @@ async function pushHealthResults(supabase, folderId, manifest) {
2201
2221
  check_id: chk.id,
2202
2222
  status: "pass",
2203
2223
  message: "Found",
2204
- ran_at: ranAt
2224
+ ran_at: ranAt,
2225
+ device_id: deviceId ?? null
2205
2226
  });
2206
2227
  } else if (chk.check_type === "gh_secret") {
2207
2228
  const variable = config2.secret;
@@ -2211,7 +2232,8 @@ async function pushHealthResults(supabase, folderId, manifest) {
2211
2232
  check_id: chk.id,
2212
2233
  status: ghStatus === "present" ? "pass" : ghStatus === "missing" ? "fail" : "warn",
2213
2234
  message: ghStatus === "present" ? "Exists" : ghStatus === "missing" ? "Missing" : "Unknown",
2214
- ran_at: ranAt
2235
+ ran_at: ranAt,
2236
+ device_id: deviceId ?? null
2215
2237
  });
2216
2238
  } else if (chk.check_type === "vercel_env") {
2217
2239
  const variable = config2.variable;
@@ -2226,14 +2248,16 @@ async function pushHealthResults(supabase, folderId, manifest) {
2226
2248
  check_id: chk.id,
2227
2249
  status: hasTarget ? "pass" : "warn",
2228
2250
  message: hasTarget ? `Present (${target})` : `Present but not in ${target}`,
2229
- ran_at: ranAt
2251
+ ran_at: ranAt,
2252
+ device_id: deviceId ?? null
2230
2253
  });
2231
2254
  } else {
2232
2255
  results.push({
2233
2256
  check_id: chk.id,
2234
2257
  status: "fail",
2235
2258
  message: `Not found in ${projectName}`,
2236
- ran_at: ranAt
2259
+ ran_at: ranAt,
2260
+ device_id: deviceId ?? null
2237
2261
  });
2238
2262
  }
2239
2263
  }
@@ -2369,10 +2393,12 @@ async function mapCommand(path, options) {
2369
2393
  Local preview: ${htmlPath}`));
2370
2394
  if (!options.offline) {
2371
2395
  try {
2372
- const { supabase } = await getAuthenticatedClient();
2396
+ const { supabase, userId } = await getAuthenticatedClient();
2373
2397
  const { data: devicePaths } = await supabase.from("device_paths").select("folder_id, device_name").eq("path", projectRoot);
2374
2398
  if (devicePaths?.length) {
2375
2399
  const { folder_id, device_name } = devicePaths[0];
2400
+ const { data: deviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device_name).single();
2401
+ const deviceId = deviceRow?.id;
2376
2402
  const { data: proposedFiles } = await supabase.from("folder_files").select("id, file_path, proposed_at").eq("folder_id", folder_id).eq("proposed_for_deletion", true);
2377
2403
  if (proposedFiles?.length && process.stdin.isTTY) {
2378
2404
  const { checkbox } = await import("@inquirer/prompts");
@@ -2436,39 +2462,61 @@ ${proposedFiles.length} file(s) proposed for deletion:
2436
2462
  if (result.doppler) {
2437
2463
  updatePayload.doppler_json = result.doppler;
2438
2464
  }
2439
- const { error } = await supabase.from("claude_folders").update(updatePayload).eq("id", folder_id);
2440
- if (error) {
2441
- console.error(chalk12.yellow(`Sync warning: ${error.message}`));
2442
- } else {
2443
- await pushToolings(supabase, folder_id, result.toolings);
2444
- const manifestForHealth = result.envManifest ?? storedManifest;
2445
- if (manifestForHealth) {
2446
- await pushHealthResults(supabase, folder_id, manifestForHealth);
2465
+ try {
2466
+ const { error } = await supabase.from("claude_folders").update(updatePayload).eq("id", folder_id);
2467
+ if (error) {
2468
+ console.log(chalk12.dim(`Debug: claude_folders update skipped (${error.message})`));
2447
2469
  }
2448
- await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
2449
- await saveState({
2450
- lastFolderId: folder_id,
2451
- lastDeviceName: device_name,
2452
- lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
2453
- });
2454
- console.log(chalk12.green("Synced to Supabase."));
2455
- console.log(chalk12.cyan(`
2470
+ } catch (err) {
2471
+ console.log(chalk12.dim(`Debug: claude_folders update skipped (${err instanceof Error ? err.message : String(err)})`));
2472
+ }
2473
+ if (deviceId) {
2474
+ await supabase.from("device_scans").upsert({
2475
+ folder_id,
2476
+ device_id: deviceId,
2477
+ user_id: userId,
2478
+ graph_json: result.graph,
2479
+ orphans_json: result.orphans,
2480
+ skills_table_json: result.skills,
2481
+ stale_files_json: result.staleFiles,
2482
+ broken_refs_json: result.brokenRefs,
2483
+ env_manifest_json: result.envManifest,
2484
+ doppler_json: result.doppler,
2485
+ marketplace_plugins_json: result.marketplacePlugins,
2486
+ data_hash: result.dataHash,
2487
+ scanned_at: result.scannedAt,
2488
+ cli_version: CURRENT_VERSION
2489
+ }, { onConflict: "folder_id,device_id" });
2490
+ }
2491
+ await pushToolings(supabase, folder_id, result.toolings);
2492
+ const manifestForHealth = result.envManifest ?? storedManifest;
2493
+ if (manifestForHealth) {
2494
+ await pushHealthResults(supabase, folder_id, manifestForHealth, deviceId);
2495
+ }
2496
+ await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
2497
+ await saveState({
2498
+ lastFolderId: folder_id,
2499
+ lastDeviceName: device_name,
2500
+ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
2501
+ });
2502
+ console.log(chalk12.green("Synced to Supabase."));
2503
+ console.log(chalk12.cyan(`
2456
2504
  https://www.md4ai.com/project/${folder_id}
2457
2505
  `));
2458
- const graphPaths = result.graph.nodes.map((n) => n.filePath);
2459
- const configFiles = await readClaudeConfigFiles(projectRoot, graphPaths);
2460
- if (configFiles.length > 0) {
2461
- for (const file of configFiles) {
2462
- await supabase.from("folder_files").upsert({
2463
- folder_id,
2464
- file_path: file.filePath,
2465
- content: file.content,
2466
- size_bytes: file.sizeBytes,
2467
- last_modified: file.lastModified
2468
- }, { onConflict: "folder_id,file_path" });
2469
- }
2470
- console.log(chalk12.green(` Uploaded ${configFiles.length} config file(s).`));
2506
+ const graphPaths = result.graph.nodes.map((n) => n.filePath);
2507
+ const configFiles = await readClaudeConfigFiles(projectRoot, graphPaths);
2508
+ if (configFiles.length > 0) {
2509
+ for (const file of configFiles) {
2510
+ await supabase.from("folder_files").upsert({
2511
+ folder_id,
2512
+ file_path: file.filePath,
2513
+ content: file.content,
2514
+ size_bytes: file.sizeBytes,
2515
+ last_modified: file.lastModified,
2516
+ device_id: deviceId
2517
+ }, { onConflict: "folder_id,file_path,device_id" });
2471
2518
  }
2519
+ console.log(chalk12.green(` Uploaded ${configFiles.length} config file(s).`));
2472
2520
  }
2473
2521
  } else {
2474
2522
  console.log(chalk12.yellow("\nThis folder is not linked to a project on your dashboard."));
@@ -2480,7 +2528,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2480
2528
  if (!shouldLink) {
2481
2529
  console.log(chalk12.dim("Skipped \u2014 local preview still generated."));
2482
2530
  } else {
2483
- const { supabase: sb, userId } = await getAuthenticatedClient();
2531
+ const { supabase: sb, userId: userId2 } = await getAuthenticatedClient();
2484
2532
  const { data: folders } = await sb.from("claude_folders").select("id, name").order("name");
2485
2533
  const choices = [
2486
2534
  { name: "+ Create a new project", value: "__new__" },
@@ -2496,7 +2544,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2496
2544
  message: "Project name:",
2497
2545
  default: basename2(projectRoot)
2498
2546
  });
2499
- const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId, name: projectName }).select("id").single();
2547
+ const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId2, name: projectName }).select("id").single();
2500
2548
  if (createErr || !newFolder) {
2501
2549
  console.error(chalk12.red(`Failed to create project: ${createErr?.message}`));
2502
2550
  return;
@@ -2509,33 +2557,60 @@ ${proposedFiles.length} file(s) proposed for deletion:
2509
2557
  const deviceName = detectDeviceName();
2510
2558
  const osType = detectOs2();
2511
2559
  await sb.from("devices").upsert({
2512
- user_id: userId,
2560
+ user_id: userId2,
2513
2561
  device_name: deviceName,
2514
2562
  os_type: osType
2515
2563
  }, { onConflict: "user_id,device_name" });
2564
+ const { data: inlineDeviceRow } = await sb.from("devices").select("id").eq("user_id", userId2).eq("device_name", deviceName).single();
2565
+ const inlineDeviceId = inlineDeviceRow?.id;
2516
2566
  await sb.from("device_paths").upsert({
2517
- user_id: userId,
2567
+ user_id: userId2,
2518
2568
  folder_id: folderId,
2519
2569
  device_name: deviceName,
2520
2570
  os_type: osType,
2521
2571
  path: projectRoot,
2522
2572
  last_synced: (/* @__PURE__ */ new Date()).toISOString()
2523
2573
  }, { onConflict: "folder_id,device_name" });
2524
- await sb.from("claude_folders").update({
2525
- graph_json: result.graph,
2526
- orphans_json: result.orphans,
2527
- broken_refs_json: result.brokenRefs,
2528
- skills_table_json: result.skills,
2529
- stale_files_json: result.staleFiles,
2530
- env_manifest_json: result.envManifest,
2531
- doppler_json: result.doppler,
2532
- marketplace_plugins_json: result.marketplacePlugins,
2533
- last_scanned: result.scannedAt,
2534
- data_hash: result.dataHash
2535
- }).eq("id", folderId);
2574
+ try {
2575
+ const { error: inlineFolderErr } = await sb.from("claude_folders").update({
2576
+ graph_json: result.graph,
2577
+ orphans_json: result.orphans,
2578
+ broken_refs_json: result.brokenRefs,
2579
+ skills_table_json: result.skills,
2580
+ stale_files_json: result.staleFiles,
2581
+ env_manifest_json: result.envManifest,
2582
+ doppler_json: result.doppler,
2583
+ marketplace_plugins_json: result.marketplacePlugins,
2584
+ last_scanned: result.scannedAt,
2585
+ data_hash: result.dataHash
2586
+ }).eq("id", folderId);
2587
+ if (inlineFolderErr) {
2588
+ console.log(chalk12.dim(`Debug: claude_folders update skipped (${inlineFolderErr.message})`));
2589
+ }
2590
+ } catch (err) {
2591
+ console.log(chalk12.dim(`Debug: claude_folders update skipped (${err instanceof Error ? err.message : String(err)})`));
2592
+ }
2593
+ if (inlineDeviceId) {
2594
+ await sb.from("device_scans").upsert({
2595
+ folder_id: folderId,
2596
+ device_id: inlineDeviceId,
2597
+ user_id: userId2,
2598
+ graph_json: result.graph,
2599
+ orphans_json: result.orphans,
2600
+ skills_table_json: result.skills,
2601
+ stale_files_json: result.staleFiles,
2602
+ broken_refs_json: result.brokenRefs,
2603
+ env_manifest_json: result.envManifest,
2604
+ doppler_json: result.doppler,
2605
+ marketplace_plugins_json: result.marketplacePlugins,
2606
+ data_hash: result.dataHash,
2607
+ scanned_at: result.scannedAt,
2608
+ cli_version: CURRENT_VERSION
2609
+ }, { onConflict: "folder_id,device_id" });
2610
+ }
2536
2611
  await pushToolings(sb, folderId, result.toolings);
2537
2612
  if (result.envManifest) {
2538
- await pushHealthResults(sb, folderId, result.envManifest);
2613
+ await pushHealthResults(sb, folderId, result.envManifest, inlineDeviceId);
2539
2614
  }
2540
2615
  const graphPaths2 = result.graph.nodes.map((n) => n.filePath);
2541
2616
  const configFiles = await readClaudeConfigFiles(projectRoot, graphPaths2);
@@ -2545,8 +2620,9 @@ ${proposedFiles.length} file(s) proposed for deletion:
2545
2620
  file_path: file.filePath,
2546
2621
  content: file.content,
2547
2622
  size_bytes: file.sizeBytes,
2548
- last_modified: file.lastModified
2549
- }, { onConflict: "folder_id,file_path" });
2623
+ last_modified: file.lastModified,
2624
+ device_id: inlineDeviceId
2625
+ }, { onConflict: "folder_id,file_path,device_id" });
2550
2626
  }
2551
2627
  await saveState({
2552
2628
  lastFolderId: folderId,
@@ -2591,7 +2667,7 @@ function isValidProjectPath(p) {
2591
2667
  return resolved.startsWith("/") && !resolved.includes("..") && existsSync11(resolved);
2592
2668
  }
2593
2669
  async function syncCommand(options) {
2594
- const { supabase } = await getAuthenticatedClient();
2670
+ const { supabase, userId } = await getAuthenticatedClient();
2595
2671
  if (options.all) {
2596
2672
  const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path");
2597
2673
  if (error || !devices?.length) {
@@ -2610,16 +2686,41 @@ async function syncCommand(options) {
2610
2686
  }
2611
2687
  try {
2612
2688
  const result = await scanProject(device.path);
2613
- await supabase.from("claude_folders").update({
2614
- graph_json: result.graph,
2615
- orphans_json: result.orphans,
2616
- broken_refs_json: result.brokenRefs,
2617
- skills_table_json: result.skills,
2618
- stale_files_json: result.staleFiles,
2619
- env_manifest_json: result.envManifest,
2620
- last_scanned: result.scannedAt,
2621
- data_hash: result.dataHash
2622
- }).eq("id", device.folder_id);
2689
+ const { data: allDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
2690
+ const allDeviceId = allDeviceRow?.id;
2691
+ try {
2692
+ const { error: allFolderErr } = await supabase.from("claude_folders").update({
2693
+ graph_json: result.graph,
2694
+ orphans_json: result.orphans,
2695
+ broken_refs_json: result.brokenRefs,
2696
+ skills_table_json: result.skills,
2697
+ stale_files_json: result.staleFiles,
2698
+ env_manifest_json: result.envManifest,
2699
+ last_scanned: result.scannedAt,
2700
+ data_hash: result.dataHash
2701
+ }).eq("id", device.folder_id);
2702
+ if (allFolderErr) {
2703
+ console.log(chalk15.dim(` Debug: claude_folders update skipped (${allFolderErr.message})`));
2704
+ }
2705
+ } catch (folderErr) {
2706
+ console.log(chalk15.dim(` Debug: claude_folders update skipped (${folderErr instanceof Error ? folderErr.message : String(folderErr)})`));
2707
+ }
2708
+ if (allDeviceId) {
2709
+ await supabase.from("device_scans").upsert({
2710
+ folder_id: device.folder_id,
2711
+ device_id: allDeviceId,
2712
+ user_id: userId,
2713
+ graph_json: result.graph,
2714
+ orphans_json: result.orphans,
2715
+ skills_table_json: result.skills,
2716
+ stale_files_json: result.staleFiles,
2717
+ broken_refs_json: result.brokenRefs,
2718
+ env_manifest_json: result.envManifest,
2719
+ data_hash: result.dataHash,
2720
+ scanned_at: result.scannedAt,
2721
+ cli_version: CURRENT_VERSION
2722
+ }, { onConflict: "folder_id,device_id" });
2723
+ }
2623
2724
  await pushToolings(supabase, device.folder_id, result.toolings);
2624
2725
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
2625
2726
  console.log(chalk15.green(` Done: ${device.device_name}`));
@@ -2650,15 +2751,39 @@ async function syncCommand(options) {
2650
2751
  console.log(chalk15.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2651
2752
  }
2652
2753
  const result = await scanProject(device.path);
2653
- await supabase.from("claude_folders").update({
2654
- graph_json: result.graph,
2655
- orphans_json: result.orphans,
2656
- broken_refs_json: result.brokenRefs,
2657
- skills_table_json: result.skills,
2658
- stale_files_json: result.staleFiles,
2659
- last_scanned: result.scannedAt,
2660
- data_hash: result.dataHash
2661
- }).eq("id", device.folder_id);
2754
+ const { data: singleDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
2755
+ const singleDeviceId = singleDeviceRow?.id;
2756
+ try {
2757
+ const { error: singleFolderErr } = await supabase.from("claude_folders").update({
2758
+ graph_json: result.graph,
2759
+ orphans_json: result.orphans,
2760
+ broken_refs_json: result.brokenRefs,
2761
+ skills_table_json: result.skills,
2762
+ stale_files_json: result.staleFiles,
2763
+ last_scanned: result.scannedAt,
2764
+ data_hash: result.dataHash
2765
+ }).eq("id", device.folder_id);
2766
+ if (singleFolderErr) {
2767
+ console.log(chalk15.dim(`Debug: claude_folders update skipped (${singleFolderErr.message})`));
2768
+ }
2769
+ } catch (err) {
2770
+ console.log(chalk15.dim(`Debug: claude_folders update skipped (${err instanceof Error ? err.message : String(err)})`));
2771
+ }
2772
+ if (singleDeviceId) {
2773
+ await supabase.from("device_scans").upsert({
2774
+ folder_id: device.folder_id,
2775
+ device_id: singleDeviceId,
2776
+ user_id: userId,
2777
+ graph_json: result.graph,
2778
+ orphans_json: result.orphans,
2779
+ skills_table_json: result.skills,
2780
+ stale_files_json: result.staleFiles,
2781
+ broken_refs_json: result.brokenRefs,
2782
+ data_hash: result.dataHash,
2783
+ scanned_at: result.scannedAt,
2784
+ cli_version: CURRENT_VERSION
2785
+ }, { onConflict: "folder_id,device_id" });
2786
+ }
2662
2787
  await pushToolings(supabase, device.folder_id, result.toolings);
2663
2788
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
2664
2789
  await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
@@ -2672,6 +2797,7 @@ var init_sync = __esm({
2672
2797
  init_config();
2673
2798
  init_scanner();
2674
2799
  init_push_toolings();
2800
+ init_check_update();
2675
2801
  }
2676
2802
  });
2677
2803
 
@@ -3780,6 +3906,7 @@ init_config();
3780
3906
  init_scanner();
3781
3907
  init_push_toolings();
3782
3908
  init_device_utils();
3909
+ init_check_update();
3783
3910
  import { resolve as resolve6 } from "node:path";
3784
3911
  import chalk16 from "chalk";
3785
3912
  async function linkCommand(projectId) {
@@ -3805,6 +3932,8 @@ Linking "${folder.name}" to this device...
3805
3932
  device_name: deviceName,
3806
3933
  os_type: osType
3807
3934
  }, { onConflict: "user_id,device_name" });
3935
+ const { data: deviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", deviceName).single();
3936
+ const deviceId = deviceRow.id;
3808
3937
  const { data: existing } = await supabase.from("device_paths").select("id").eq("folder_id", folder.id).eq("device_name", deviceName).maybeSingle();
3809
3938
  if (existing) {
3810
3939
  await supabase.from("device_paths").update({ path: cwd, os_type: osType, last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", existing.id);
@@ -3832,21 +3961,41 @@ Linking "${folder.name}" to this device...
3832
3961
  console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
3833
3962
  console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s) \u2014 ${result.doppler.project}` : "see above"}`);
3834
3963
  console.log(` Plugins: ${result.marketplacePlugins.length} (${result.marketplacePlugins.reduce((n, p) => n + p.skills.length, 0)} skills)`);
3835
- const { error: scanErr } = await supabase.from("claude_folders").update({
3964
+ try {
3965
+ const { error: scanErr } = await supabase.from("claude_folders").update({
3966
+ graph_json: result.graph,
3967
+ orphans_json: result.orphans,
3968
+ broken_refs_json: result.brokenRefs,
3969
+ skills_table_json: result.skills,
3970
+ stale_files_json: result.staleFiles,
3971
+ env_manifest_json: result.envManifest,
3972
+ doppler_json: result.doppler,
3973
+ marketplace_plugins_json: result.marketplacePlugins,
3974
+ last_scanned: result.scannedAt,
3975
+ data_hash: result.dataHash
3976
+ }).eq("id", folder.id);
3977
+ if (scanErr) {
3978
+ console.log(chalk16.dim(`Debug: claude_folders update skipped (${scanErr.message})`));
3979
+ }
3980
+ } catch (err) {
3981
+ console.log(chalk16.dim(`Debug: claude_folders update skipped (${err instanceof Error ? err.message : String(err)})`));
3982
+ }
3983
+ await supabase.from("device_scans").upsert({
3984
+ folder_id: folder.id,
3985
+ device_id: deviceId,
3986
+ user_id: userId,
3836
3987
  graph_json: result.graph,
3837
3988
  orphans_json: result.orphans,
3838
- broken_refs_json: result.brokenRefs,
3839
3989
  skills_table_json: result.skills,
3840
3990
  stale_files_json: result.staleFiles,
3991
+ broken_refs_json: result.brokenRefs,
3841
3992
  env_manifest_json: result.envManifest,
3842
3993
  doppler_json: result.doppler,
3843
3994
  marketplace_plugins_json: result.marketplacePlugins,
3844
- last_scanned: result.scannedAt,
3845
- data_hash: result.dataHash
3846
- }).eq("id", folder.id);
3847
- if (scanErr) {
3848
- console.error(chalk16.yellow(`Scan upload warning: ${scanErr.message}`));
3849
- }
3995
+ data_hash: result.dataHash,
3996
+ scanned_at: result.scannedAt,
3997
+ cli_version: CURRENT_VERSION
3998
+ }, { onConflict: "folder_id,device_id" });
3850
3999
  await pushToolings(supabase, folder.id, result.toolings);
3851
4000
  const graphPaths = result.graph.nodes.map((n) => n.filePath);
3852
4001
  const configFiles = await readClaudeConfigFiles(cwd, graphPaths);
@@ -3857,8 +4006,9 @@ Linking "${folder.name}" to this device...
3857
4006
  file_path: file.filePath,
3858
4007
  content: file.content,
3859
4008
  size_bytes: file.sizeBytes,
3860
- last_modified: file.lastModified
3861
- }, { onConflict: "folder_id,file_path" });
4009
+ last_modified: file.lastModified,
4010
+ device_id: deviceId
4011
+ }, { onConflict: "folder_id,file_path,device_id" });
3862
4012
  }
3863
4013
  console.log(chalk16.green(` Uploaded ${configFiles.length} config file(s).`));
3864
4014
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md4ai",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
4
4
  "description": "CLI for MD4AI — scan Claude projects and sync to your dashboard",
5
5
  "type": "module",
6
6
  "bin": {