md4ai 0.17.1 → 0.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -122,7 +122,7 @@ var CURRENT_VERSION;
122
122
  var init_check_update = __esm({
123
123
  "dist/check-update.js"() {
124
124
  "use strict";
125
- CURRENT_VERSION = true ? "0.17.1" : "0.0.0-dev";
125
+ CURRENT_VERSION = true ? "0.17.3" : "0.0.0-dev";
126
126
  }
127
127
  });
128
128
 
@@ -365,14 +365,26 @@ import chalk5 from "chalk";
365
365
  import { confirm, input as input2, password as password2 } from "@inquirer/prompts";
366
366
  async function getAuthenticatedClient() {
367
367
  const creds = await loadCredentials();
368
- const needsLogin = !creds?.accessToken || Date.now() > creds.expiresAt;
369
- if (needsLogin) {
370
- const reason = !creds?.accessToken ? "Not logged in." : "Session expired.";
371
- console.log(chalk5.yellow(`${reason}`));
368
+ if (!creds?.accessToken) {
369
+ console.log(chalk5.yellow("Not logged in."));
372
370
  const shouldLogin = await confirm({ message: "Would you like to log in now?" });
373
- if (!shouldLogin) {
371
+ if (!shouldLogin)
374
372
  process.exit(0);
373
+ return promptLogin();
374
+ }
375
+ if (creds.refreshToken) {
376
+ const refreshed = await refreshSession();
377
+ if (refreshed) {
378
+ updateDeviceCliVersion(refreshed.supabase, refreshed.userId).catch(() => {
379
+ });
380
+ return refreshed;
375
381
  }
382
+ }
383
+ if (Date.now() > creds.expiresAt) {
384
+ console.log(chalk5.yellow("Session expired."));
385
+ const shouldLogin = await confirm({ message: "Would you like to log in now?" });
386
+ if (!shouldLogin)
387
+ process.exit(0);
376
388
  return promptLogin();
377
389
  }
378
390
  const anonKey = getAnonKey();
@@ -2069,6 +2081,11 @@ var init_doppler_scanner = __esm({
2069
2081
  });
2070
2082
 
2071
2083
  // dist/scanner/index.js
2084
+ var scanner_exports = {};
2085
+ __export(scanner_exports, {
2086
+ readClaudeConfigFiles: () => readClaudeConfigFiles,
2087
+ scanProject: () => scanProject
2088
+ });
2072
2089
  import { readdir as readdir5 } from "node:fs/promises";
2073
2090
  import { join as join12, relative as relative3 } from "node:path";
2074
2091
  import { existsSync as existsSync7 } from "node:fs";
@@ -2619,7 +2636,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2619
2636
  }
2620
2637
  }
2621
2638
  if (deviceId) {
2622
- await supabase.from("device_scans").upsert({
2639
+ const scanPayload = {
2623
2640
  folder_id,
2624
2641
  device_id: deviceId,
2625
2642
  user_id: userId,
@@ -2635,7 +2652,14 @@ ${proposedFiles.length} file(s) proposed for deletion:
2635
2652
  data_hash: result.dataHash,
2636
2653
  scanned_at: result.scannedAt,
2637
2654
  cli_version: CURRENT_VERSION
2638
- }, { onConflict: "folder_id,device_id" });
2655
+ };
2656
+ const { data: updated, error: updateErr } = await supabase.from("device_scans").update(scanPayload).eq("folder_id", folder_id).eq("device_id", deviceId).select("id");
2657
+ if (updateErr || !updated?.length) {
2658
+ const { error: insertErr } = await supabase.from("device_scans").insert(scanPayload);
2659
+ if (insertErr) {
2660
+ console.error(chalk12.red(` Failed to save scan data: ${insertErr.message}`));
2661
+ }
2662
+ }
2639
2663
  }
2640
2664
  await pushToolings(supabase, folder_id, result.toolings, deviceId);
2641
2665
  const manifestForHealth = result.envManifest ?? storedManifest;
@@ -2803,8 +2827,14 @@ async function syncCommand(options) {
2803
2827
  const currentDeviceName = detectDeviceName();
2804
2828
  const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path").eq("device_name", currentDeviceName);
2805
2829
  if (error || !devices?.length) {
2806
- console.error(chalk15.red("No devices found."));
2807
- process.exit(1);
2830
+ console.log(chalk15.yellow(`
2831
+ No projects linked on ${currentDeviceName} yet.
2832
+ `));
2833
+ console.log(chalk15.dim(" To link a project, cd into its folder and run:"));
2834
+ console.log(chalk15.cyan(" md4ai scan"));
2835
+ console.log(chalk15.dim(" Or link an existing project from the dashboard:"));
2836
+ console.log(chalk15.cyan(" md4ai link <project-id>\n"));
2837
+ return;
2808
2838
  }
2809
2839
  for (const device of devices) {
2810
2840
  if (!isValidProjectPath(device.path)) {
@@ -3587,12 +3617,13 @@ async function checkPendingRescans(supabase, deviceId, deviceName) {
3587
3617
  if (!paths?.length)
3588
3618
  return;
3589
3619
  const folderIds = paths.map((p) => p.folder_id);
3590
- const { data: folders } = await supabase.from("claude_folders").select("id, last_scanned, rescan_requested_at").in("id", folderIds).not("rescan_requested_at", "is", null);
3620
+ const { data: folders } = await supabase.from("claude_folders").select("id, rescan_requested_at").in("id", folderIds).not("rescan_requested_at", "is", null);
3591
3621
  if (!folders?.length)
3592
3622
  return;
3593
3623
  for (const folder of folders) {
3594
3624
  const requested = new Date(folder.rescan_requested_at).getTime();
3595
- const scanned = folder.last_scanned ? new Date(folder.last_scanned).getTime() : 0;
3625
+ const { data: scanRow } = await supabase.from("device_scans").select("scanned_at").eq("folder_id", folder.id).eq("device_id", deviceId).maybeSingle();
3626
+ const scanned = scanRow?.scanned_at ? new Date(scanRow.scanned_at).getTime() : 0;
3596
3627
  if (requested <= scanned)
3597
3628
  continue;
3598
3629
  const dp = paths.find((p) => p.folder_id === folder.id);
@@ -3600,24 +3631,46 @@ async function checkPendingRescans(supabase, deviceId, deviceName) {
3600
3631
  continue;
3601
3632
  try {
3602
3633
  const result = await scanProject(dp.path);
3603
- await supabase.from("device_scans").upsert({
3634
+ const userId = (await supabase.auth.getUser()).data.user.id;
3635
+ const scanPayload = {
3604
3636
  folder_id: folder.id,
3605
3637
  device_id: deviceId,
3606
- user_id: (await supabase.auth.getUser()).data.user.id,
3638
+ user_id: userId,
3607
3639
  graph_json: result.graph,
3608
3640
  orphans_json: result.orphans,
3609
3641
  skills_table_json: result.skills,
3610
3642
  stale_files_json: result.staleFiles,
3611
3643
  broken_refs_json: result.brokenRefs,
3612
3644
  env_manifest_json: result.envManifest,
3645
+ doppler_json: result.doppler,
3646
+ marketplace_plugins_json: result.marketplacePlugins,
3647
+ plugin_versions_json: result.pluginVersions,
3613
3648
  data_hash: result.dataHash,
3614
3649
  scanned_at: result.scannedAt,
3615
3650
  cli_version: CURRENT_VERSION
3616
- }, { onConflict: "folder_id,device_id" });
3651
+ };
3652
+ const { data: updated } = await supabase.from("device_scans").update(scanPayload).eq("folder_id", folder.id).eq("device_id", deviceId).select("id");
3653
+ if (!updated?.length) {
3654
+ await supabase.from("device_scans").insert(scanPayload);
3655
+ }
3617
3656
  await supabase.from("claude_folders").update({
3618
3657
  rescan_requested_at: null
3619
3658
  }).eq("id", folder.id);
3620
3659
  await pushToolings(supabase, folder.id, result.toolings, deviceId);
3660
+ const graphPaths = result.graph.nodes.map((n) => n.filePath);
3661
+ const { readClaudeConfigFiles: readClaudeConfigFiles2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
3662
+ const configFiles = await readClaudeConfigFiles2(dp.path, graphPaths);
3663
+ for (const cf of configFiles) {
3664
+ await supabase.from("folder_files").upsert({
3665
+ folder_id: folder.id,
3666
+ file_path: cf.filePath,
3667
+ content: cf.content,
3668
+ size_bytes: cf.sizeBytes,
3669
+ last_modified: cf.lastModified,
3670
+ synced_at: (/* @__PURE__ */ new Date()).toISOString(),
3671
+ device_id: deviceId
3672
+ }, { onConflict: "folder_id,file_path,device_id" });
3673
+ }
3621
3674
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
3622
3675
  } catch {
3623
3676
  await supabase.from("claude_folders").update({ rescan_requested_at: null }).eq("id", folder.id);
@@ -4334,7 +4387,15 @@ var VERSION_SOURCES = {
4334
4387
  "turborepo": { type: "npm", package: "turbo" },
4335
4388
  "npm": { type: "npm", package: "npm" },
4336
4389
  "node": { type: "github", repo: "nodejs/node" },
4337
- "supabase-cli": { type: "npm", package: "supabase" }
4390
+ "supabase-cli": { type: "npm", package: "supabase" },
4391
+ "vercel-cli": { type: "npm", package: "vercel" },
4392
+ "@sentry/node": { type: "npm", package: "@sentry/node" },
4393
+ "react-markdown": { type: "npm", package: "react-markdown" },
4394
+ "rehype-sanitize": { type: "npm", package: "rehype-sanitize" },
4395
+ "@react-email/render": { type: "npm", package: "@react-email/render" },
4396
+ "@anthropic-ai/sdk": { type: "npm", package: "@anthropic-ai/sdk" },
4397
+ "@xyflow/react": { type: "npm", package: "@xyflow/react" },
4398
+ "fflate": { type: "npm", package: "fflate" }
4338
4399
  };
4339
4400
 
4340
4401
  // dist/commands/admin-fetch-versions.js
@@ -4708,14 +4769,26 @@ ${lines.join("\n")}`;
4708
4769
  }
4709
4770
  const { select: selectPrompt } = await import("@inquirer/prompts");
4710
4771
  const cwd = process.cwd();
4711
- const allLabel = linkedPaths?.length ? `All ${linkedPaths.length} linked project${linkedPaths.length === 1 ? "" : "s"} on this device${linkedDisplay}` : "All linked projects on this device";
4772
+ const hasLinked = linkedPaths && linkedPaths.length > 0;
4773
+ const choices = [
4774
+ { name: `Scan current folder (${cwd})`, value: "cwd" }
4775
+ ];
4776
+ if (hasLinked) {
4777
+ const allLabel = `All ${linkedPaths.length} linked project${linkedPaths.length === 1 ? "" : "s"} on this device${linkedDisplay}`;
4778
+ choices.push({ name: allLabel, value: "all" });
4779
+ }
4780
+ choices.push({ name: "Skip", value: "skip" });
4781
+ if (!hasLinked) {
4782
+ console.log(chalk23.dim(` Device: ${currentDeviceName}`));
4783
+ console.log(chalk23.dim(" No projects linked on this device yet.\n"));
4784
+ console.log(chalk23.dim(" To link a project, cd into its folder and run:"));
4785
+ console.log(chalk23.cyan(" md4ai scan"));
4786
+ console.log(chalk23.dim(" Or link an existing project from the dashboard:"));
4787
+ console.log(chalk23.cyan(" md4ai link <project-id>\n"));
4788
+ }
4712
4789
  const scanChoice = await selectPrompt({
4713
- message: "What would you like to scan?",
4714
- choices: [
4715
- { name: `Current folder (${cwd})`, value: "cwd" },
4716
- { name: allLabel, value: "all" },
4717
- { name: "Skip scanning", value: "skip" }
4718
- ]
4790
+ message: "What would you like to do?",
4791
+ choices
4719
4792
  });
4720
4793
  if (scanChoice === "cwd") {
4721
4794
  console.log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md4ai",
3
- "version": "0.17.1",
3
+ "version": "0.17.3",
4
4
  "description": "CLI for MD4AI — scan Claude projects and sync to your dashboard",
5
5
  "type": "module",
6
6
  "bin": {