md4ai 0.12.0 → 0.13.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 +236 -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.0" : "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,60 @@ ${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
+ }, { onConflict: "folder_id,device_id" });
2489
+ }
2490
+ await pushToolings(supabase, folder_id, result.toolings);
2491
+ const manifestForHealth = result.envManifest ?? storedManifest;
2492
+ if (manifestForHealth) {
2493
+ await pushHealthResults(supabase, folder_id, manifestForHealth, deviceId);
2494
+ }
2495
+ await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder_id).eq("device_name", device_name);
2496
+ await saveState({
2497
+ lastFolderId: folder_id,
2498
+ lastDeviceName: device_name,
2499
+ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
2500
+ });
2501
+ console.log(chalk12.green("Synced to Supabase."));
2502
+ console.log(chalk12.cyan(`
2456
2503
  https://www.md4ai.com/project/${folder_id}
2457
2504
  `));
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).`));
2505
+ const graphPaths = result.graph.nodes.map((n) => n.filePath);
2506
+ const configFiles = await readClaudeConfigFiles(projectRoot, graphPaths);
2507
+ if (configFiles.length > 0) {
2508
+ for (const file of configFiles) {
2509
+ await supabase.from("folder_files").upsert({
2510
+ folder_id,
2511
+ file_path: file.filePath,
2512
+ content: file.content,
2513
+ size_bytes: file.sizeBytes,
2514
+ last_modified: file.lastModified,
2515
+ device_id: deviceId
2516
+ }, { onConflict: "folder_id,file_path,device_id" });
2471
2517
  }
2518
+ console.log(chalk12.green(` Uploaded ${configFiles.length} config file(s).`));
2472
2519
  }
2473
2520
  } else {
2474
2521
  console.log(chalk12.yellow("\nThis folder is not linked to a project on your dashboard."));
@@ -2480,7 +2527,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2480
2527
  if (!shouldLink) {
2481
2528
  console.log(chalk12.dim("Skipped \u2014 local preview still generated."));
2482
2529
  } else {
2483
- const { supabase: sb, userId } = await getAuthenticatedClient();
2530
+ const { supabase: sb, userId: userId2 } = await getAuthenticatedClient();
2484
2531
  const { data: folders } = await sb.from("claude_folders").select("id, name").order("name");
2485
2532
  const choices = [
2486
2533
  { name: "+ Create a new project", value: "__new__" },
@@ -2496,7 +2543,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2496
2543
  message: "Project name:",
2497
2544
  default: basename2(projectRoot)
2498
2545
  });
2499
- const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId, name: projectName }).select("id").single();
2546
+ const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId2, name: projectName }).select("id").single();
2500
2547
  if (createErr || !newFolder) {
2501
2548
  console.error(chalk12.red(`Failed to create project: ${createErr?.message}`));
2502
2549
  return;
@@ -2509,33 +2556,59 @@ ${proposedFiles.length} file(s) proposed for deletion:
2509
2556
  const deviceName = detectDeviceName();
2510
2557
  const osType = detectOs2();
2511
2558
  await sb.from("devices").upsert({
2512
- user_id: userId,
2559
+ user_id: userId2,
2513
2560
  device_name: deviceName,
2514
2561
  os_type: osType
2515
2562
  }, { onConflict: "user_id,device_name" });
2563
+ const { data: inlineDeviceRow } = await sb.from("devices").select("id").eq("user_id", userId2).eq("device_name", deviceName).single();
2564
+ const inlineDeviceId = inlineDeviceRow?.id;
2516
2565
  await sb.from("device_paths").upsert({
2517
- user_id: userId,
2566
+ user_id: userId2,
2518
2567
  folder_id: folderId,
2519
2568
  device_name: deviceName,
2520
2569
  os_type: osType,
2521
2570
  path: projectRoot,
2522
2571
  last_synced: (/* @__PURE__ */ new Date()).toISOString()
2523
2572
  }, { 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);
2573
+ try {
2574
+ const { error: inlineFolderErr } = await sb.from("claude_folders").update({
2575
+ graph_json: result.graph,
2576
+ orphans_json: result.orphans,
2577
+ broken_refs_json: result.brokenRefs,
2578
+ skills_table_json: result.skills,
2579
+ stale_files_json: result.staleFiles,
2580
+ env_manifest_json: result.envManifest,
2581
+ doppler_json: result.doppler,
2582
+ marketplace_plugins_json: result.marketplacePlugins,
2583
+ last_scanned: result.scannedAt,
2584
+ data_hash: result.dataHash
2585
+ }).eq("id", folderId);
2586
+ if (inlineFolderErr) {
2587
+ console.log(chalk12.dim(`Debug: claude_folders update skipped (${inlineFolderErr.message})`));
2588
+ }
2589
+ } catch (err) {
2590
+ console.log(chalk12.dim(`Debug: claude_folders update skipped (${err instanceof Error ? err.message : String(err)})`));
2591
+ }
2592
+ if (inlineDeviceId) {
2593
+ await sb.from("device_scans").upsert({
2594
+ folder_id: folderId,
2595
+ device_id: inlineDeviceId,
2596
+ user_id: userId2,
2597
+ graph_json: result.graph,
2598
+ orphans_json: result.orphans,
2599
+ skills_table_json: result.skills,
2600
+ stale_files_json: result.staleFiles,
2601
+ broken_refs_json: result.brokenRefs,
2602
+ env_manifest_json: result.envManifest,
2603
+ doppler_json: result.doppler,
2604
+ marketplace_plugins_json: result.marketplacePlugins,
2605
+ data_hash: result.dataHash,
2606
+ scanned_at: result.scannedAt
2607
+ }, { onConflict: "folder_id,device_id" });
2608
+ }
2536
2609
  await pushToolings(sb, folderId, result.toolings);
2537
2610
  if (result.envManifest) {
2538
- await pushHealthResults(sb, folderId, result.envManifest);
2611
+ await pushHealthResults(sb, folderId, result.envManifest, inlineDeviceId);
2539
2612
  }
2540
2613
  const graphPaths2 = result.graph.nodes.map((n) => n.filePath);
2541
2614
  const configFiles = await readClaudeConfigFiles(projectRoot, graphPaths2);
@@ -2545,8 +2618,9 @@ ${proposedFiles.length} file(s) proposed for deletion:
2545
2618
  file_path: file.filePath,
2546
2619
  content: file.content,
2547
2620
  size_bytes: file.sizeBytes,
2548
- last_modified: file.lastModified
2549
- }, { onConflict: "folder_id,file_path" });
2621
+ last_modified: file.lastModified,
2622
+ device_id: inlineDeviceId
2623
+ }, { onConflict: "folder_id,file_path,device_id" });
2550
2624
  }
2551
2625
  await saveState({
2552
2626
  lastFolderId: folderId,
@@ -2591,7 +2665,7 @@ function isValidProjectPath(p) {
2591
2665
  return resolved.startsWith("/") && !resolved.includes("..") && existsSync11(resolved);
2592
2666
  }
2593
2667
  async function syncCommand(options) {
2594
- const { supabase } = await getAuthenticatedClient();
2668
+ const { supabase, userId } = await getAuthenticatedClient();
2595
2669
  if (options.all) {
2596
2670
  const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path");
2597
2671
  if (error || !devices?.length) {
@@ -2610,16 +2684,40 @@ async function syncCommand(options) {
2610
2684
  }
2611
2685
  try {
2612
2686
  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);
2687
+ const { data: allDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
2688
+ const allDeviceId = allDeviceRow?.id;
2689
+ try {
2690
+ const { error: allFolderErr } = await supabase.from("claude_folders").update({
2691
+ graph_json: result.graph,
2692
+ orphans_json: result.orphans,
2693
+ broken_refs_json: result.brokenRefs,
2694
+ skills_table_json: result.skills,
2695
+ stale_files_json: result.staleFiles,
2696
+ env_manifest_json: result.envManifest,
2697
+ last_scanned: result.scannedAt,
2698
+ data_hash: result.dataHash
2699
+ }).eq("id", device.folder_id);
2700
+ if (allFolderErr) {
2701
+ console.log(chalk15.dim(` Debug: claude_folders update skipped (${allFolderErr.message})`));
2702
+ }
2703
+ } catch (folderErr) {
2704
+ console.log(chalk15.dim(` Debug: claude_folders update skipped (${folderErr instanceof Error ? folderErr.message : String(folderErr)})`));
2705
+ }
2706
+ if (allDeviceId) {
2707
+ await supabase.from("device_scans").upsert({
2708
+ folder_id: device.folder_id,
2709
+ device_id: allDeviceId,
2710
+ user_id: userId,
2711
+ graph_json: result.graph,
2712
+ orphans_json: result.orphans,
2713
+ skills_table_json: result.skills,
2714
+ stale_files_json: result.staleFiles,
2715
+ broken_refs_json: result.brokenRefs,
2716
+ env_manifest_json: result.envManifest,
2717
+ data_hash: result.dataHash,
2718
+ scanned_at: result.scannedAt
2719
+ }, { onConflict: "folder_id,device_id" });
2720
+ }
2623
2721
  await pushToolings(supabase, device.folder_id, result.toolings);
2624
2722
  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
2723
  console.log(chalk15.green(` Done: ${device.device_name}`));
@@ -2650,15 +2748,38 @@ async function syncCommand(options) {
2650
2748
  console.log(chalk15.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2651
2749
  }
2652
2750
  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);
2751
+ const { data: singleDeviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", device.device_name).single();
2752
+ const singleDeviceId = singleDeviceRow?.id;
2753
+ try {
2754
+ const { error: singleFolderErr } = await supabase.from("claude_folders").update({
2755
+ graph_json: result.graph,
2756
+ orphans_json: result.orphans,
2757
+ broken_refs_json: result.brokenRefs,
2758
+ skills_table_json: result.skills,
2759
+ stale_files_json: result.staleFiles,
2760
+ last_scanned: result.scannedAt,
2761
+ data_hash: result.dataHash
2762
+ }).eq("id", device.folder_id);
2763
+ if (singleFolderErr) {
2764
+ console.log(chalk15.dim(`Debug: claude_folders update skipped (${singleFolderErr.message})`));
2765
+ }
2766
+ } catch (err) {
2767
+ console.log(chalk15.dim(`Debug: claude_folders update skipped (${err instanceof Error ? err.message : String(err)})`));
2768
+ }
2769
+ if (singleDeviceId) {
2770
+ await supabase.from("device_scans").upsert({
2771
+ folder_id: device.folder_id,
2772
+ device_id: singleDeviceId,
2773
+ user_id: userId,
2774
+ graph_json: result.graph,
2775
+ orphans_json: result.orphans,
2776
+ skills_table_json: result.skills,
2777
+ stale_files_json: result.staleFiles,
2778
+ broken_refs_json: result.brokenRefs,
2779
+ data_hash: result.dataHash,
2780
+ scanned_at: result.scannedAt
2781
+ }, { onConflict: "folder_id,device_id" });
2782
+ }
2662
2783
  await pushToolings(supabase, device.folder_id, result.toolings);
2663
2784
  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
2785
  await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
@@ -3805,6 +3926,8 @@ Linking "${folder.name}" to this device...
3805
3926
  device_name: deviceName,
3806
3927
  os_type: osType
3807
3928
  }, { onConflict: "user_id,device_name" });
3929
+ const { data: deviceRow } = await supabase.from("devices").select("id").eq("user_id", userId).eq("device_name", deviceName).single();
3930
+ const deviceId = deviceRow.id;
3808
3931
  const { data: existing } = await supabase.from("device_paths").select("id").eq("folder_id", folder.id).eq("device_name", deviceName).maybeSingle();
3809
3932
  if (existing) {
3810
3933
  await supabase.from("device_paths").update({ path: cwd, os_type: osType, last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", existing.id);
@@ -3832,21 +3955,40 @@ Linking "${folder.name}" to this device...
3832
3955
  console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
3833
3956
  console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s) \u2014 ${result.doppler.project}` : "see above"}`);
3834
3957
  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({
3958
+ try {
3959
+ const { error: scanErr } = await supabase.from("claude_folders").update({
3960
+ graph_json: result.graph,
3961
+ orphans_json: result.orphans,
3962
+ broken_refs_json: result.brokenRefs,
3963
+ skills_table_json: result.skills,
3964
+ stale_files_json: result.staleFiles,
3965
+ env_manifest_json: result.envManifest,
3966
+ doppler_json: result.doppler,
3967
+ marketplace_plugins_json: result.marketplacePlugins,
3968
+ last_scanned: result.scannedAt,
3969
+ data_hash: result.dataHash
3970
+ }).eq("id", folder.id);
3971
+ if (scanErr) {
3972
+ console.log(chalk16.dim(`Debug: claude_folders update skipped (${scanErr.message})`));
3973
+ }
3974
+ } catch (err) {
3975
+ console.log(chalk16.dim(`Debug: claude_folders update skipped (${err instanceof Error ? err.message : String(err)})`));
3976
+ }
3977
+ await supabase.from("device_scans").upsert({
3978
+ folder_id: folder.id,
3979
+ device_id: deviceId,
3980
+ user_id: userId,
3836
3981
  graph_json: result.graph,
3837
3982
  orphans_json: result.orphans,
3838
- broken_refs_json: result.brokenRefs,
3839
3983
  skills_table_json: result.skills,
3840
3984
  stale_files_json: result.staleFiles,
3985
+ broken_refs_json: result.brokenRefs,
3841
3986
  env_manifest_json: result.envManifest,
3842
3987
  doppler_json: result.doppler,
3843
3988
  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
- }
3989
+ data_hash: result.dataHash,
3990
+ scanned_at: result.scannedAt
3991
+ }, { onConflict: "folder_id,device_id" });
3850
3992
  await pushToolings(supabase, folder.id, result.toolings);
3851
3993
  const graphPaths = result.graph.nodes.map((n) => n.filePath);
3852
3994
  const configFiles = await readClaudeConfigFiles(cwd, graphPaths);
@@ -3857,8 +3999,9 @@ Linking "${folder.name}" to this device...
3857
3999
  file_path: file.filePath,
3858
4000
  content: file.content,
3859
4001
  size_bytes: file.sizeBytes,
3860
- last_modified: file.lastModified
3861
- }, { onConflict: "folder_id,file_path" });
4002
+ last_modified: file.lastModified,
4003
+ device_id: deviceId
4004
+ }, { onConflict: "folder_id,file_path,device_id" });
3862
4005
  }
3863
4006
  console.log(chalk16.green(` Uploaded ${configFiles.length} config file(s).`));
3864
4007
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md4ai",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "CLI for MD4AI — scan Claude projects and sync to your dashboard",
5
5
  "type": "module",
6
6
  "bin": {