md4ai 0.11.0 → 0.12.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 +125 -12
  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.11.0" : "0.0.0-dev";
76
+ CURRENT_VERSION = true ? "0.12.0" : "0.0.0-dev";
77
77
  }
78
78
  });
79
79
 
@@ -1713,6 +1713,10 @@ async function validateDopplerToken(token) {
1713
1713
  throw err;
1714
1714
  }
1715
1715
  }
1716
+ async function fetchDopplerProjects(token) {
1717
+ const data = await dopplerFetch("https://api.doppler.com/v3/projects", token);
1718
+ return data.projects.map((p) => ({ slug: p.slug, name: p.name }));
1719
+ }
1716
1720
  var DopplerApiError, MAX_RETRIES;
1717
1721
  var init_api = __esm({
1718
1722
  "dist/doppler/api.js"() {
@@ -1733,9 +1737,10 @@ var init_api = __esm({
1733
1737
 
1734
1738
  // dist/scanner/doppler-scanner.js
1735
1739
  import { readFile as readFile9 } from "node:fs/promises";
1736
- import { join as join11 } from "node:path";
1740
+ import { join as join11, basename } from "node:path";
1737
1741
  import { existsSync as existsSync6 } from "node:fs";
1738
1742
  import chalk10 from "chalk";
1743
+ import { select as select3 } from "@inquirer/prompts";
1739
1744
  async function parseDopplerYaml(projectRoot) {
1740
1745
  const yamlPath = join11(projectRoot, "doppler.yaml");
1741
1746
  if (!existsSync6(yamlPath))
@@ -1751,6 +1756,7 @@ async function parseDopplerYaml(projectRoot) {
1751
1756
  async function scanDoppler(projectRoot, folderId) {
1752
1757
  const tokenResult = await resolveDopplerToken();
1753
1758
  if (!tokenResult) {
1759
+ console.log(chalk10.dim(' Doppler: no token configured. Run "md4ai doppler connect" to set up.'));
1754
1760
  return null;
1755
1761
  }
1756
1762
  let projectSlug = await parseDopplerYaml(projectRoot);
@@ -1762,7 +1768,40 @@ async function scanDoppler(projectRoot, folderId) {
1762
1768
  matchedVia = "manual";
1763
1769
  }
1764
1770
  if (!projectSlug) {
1765
- return null;
1771
+ if (!folderId || !process.stdin.isTTY) {
1772
+ console.log(chalk10.dim(' Doppler: token configured but no project linked. Run "md4ai doppler set-project <slug>" to link one.'));
1773
+ return null;
1774
+ }
1775
+ const projects = await fetchDopplerProjects(tokenResult.token);
1776
+ if (projects.length === 0) {
1777
+ console.log(chalk10.yellow(" Doppler: no projects accessible with this token."));
1778
+ return null;
1779
+ }
1780
+ const projectName = basename(projectRoot);
1781
+ if (projects.length === 1) {
1782
+ projectSlug = projects[0].slug;
1783
+ console.log(chalk10.green(` Doppler: using "${projectSlug}" (only accessible project).`));
1784
+ } else {
1785
+ const autoMatch = projects.find((p) => p.slug.toLowerCase() === projectName.toLowerCase());
1786
+ if (autoMatch) {
1787
+ projectSlug = autoMatch.slug;
1788
+ console.log(chalk10.green(` Doppler: auto-matched project "${projectSlug}" from project name.`));
1789
+ } else {
1790
+ projectSlug = await select3({
1791
+ message: `Which Doppler project holds secrets for "${projectName}"?`,
1792
+ choices: projects.map((p) => ({
1793
+ name: `${p.slug}${p.name !== p.slug ? ` (${p.name})` : ""}`,
1794
+ value: p.slug
1795
+ }))
1796
+ });
1797
+ }
1798
+ }
1799
+ const state = await loadState();
1800
+ const dopplerProjects = state.dopplerProjects ?? {};
1801
+ dopplerProjects[folderId] = projectSlug;
1802
+ await saveState({ dopplerProjects });
1803
+ matchedVia = "manual";
1804
+ console.log(chalk10.dim(` Doppler: saved "${projectSlug}" for this project. To change: md4ai doppler set-project <slug>`));
1766
1805
  }
1767
1806
  console.log(chalk10.dim(` Doppler: checking project "${projectSlug}" (via ${matchedVia})...`));
1768
1807
  try {
@@ -2274,11 +2313,11 @@ var map_exports = {};
2274
2313
  __export(map_exports, {
2275
2314
  mapCommand: () => mapCommand
2276
2315
  });
2277
- import { resolve as resolve4, basename } from "node:path";
2316
+ import { resolve as resolve4, basename as basename2 } from "node:path";
2278
2317
  import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
2279
2318
  import { existsSync as existsSync8 } from "node:fs";
2280
2319
  import chalk12 from "chalk";
2281
- import { select as select3, input as input5, confirm as confirm2 } from "@inquirer/prompts";
2320
+ import { select as select4, input as input5, confirm as confirm2 } from "@inquirer/prompts";
2282
2321
  async function mapCommand(path, options) {
2283
2322
  await checkForUpdate();
2284
2323
  const projectRoot = resolve4(path ?? process.cwd());
@@ -2288,7 +2327,16 @@ async function mapCommand(path, options) {
2288
2327
  }
2289
2328
  console.log(chalk12.blue(`Scanning: ${projectRoot}
2290
2329
  `));
2291
- const result = await scanProject(projectRoot);
2330
+ let earlyFolderId;
2331
+ if (!options.offline) {
2332
+ try {
2333
+ const { supabase: earlyDb } = await getAuthenticatedClient();
2334
+ const { data: dp } = await earlyDb.from("device_paths").select("folder_id").eq("path", projectRoot).maybeSingle();
2335
+ earlyFolderId = dp?.folder_id ?? void 0;
2336
+ } catch {
2337
+ }
2338
+ }
2339
+ const result = await scanProject(projectRoot, earlyFolderId);
2292
2340
  console.log(` Files found: ${result.graph.nodes.length}`);
2293
2341
  console.log(` References: ${result.graph.edges.length}`);
2294
2342
  console.log(` Broken refs: ${result.brokenRefs.length}`);
@@ -2297,7 +2345,7 @@ async function mapCommand(path, options) {
2297
2345
  console.log(` Skills: ${result.skills.length}`);
2298
2346
  console.log(` Toolings: ${result.toolings.length}`);
2299
2347
  console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
2300
- console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s)` : "not configured"}`);
2348
+ console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s) \u2014 ${result.doppler.project}` : "see above"}`);
2301
2349
  console.log(` Plugins: ${result.marketplacePlugins.length} (${result.marketplacePlugins.reduce((n, p) => n + p.skills.length, 0)} skills)`);
2302
2350
  console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
2303
2351
  if (result.brokenRefs.length > 0) {
@@ -2438,7 +2486,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2438
2486
  { name: "+ Create a new project", value: "__new__" },
2439
2487
  ...(folders ?? []).map((f) => ({ name: f.name, value: f.id }))
2440
2488
  ];
2441
- const chosen = await select3({
2489
+ const chosen = await select4({
2442
2490
  message: "Link to which project?",
2443
2491
  choices
2444
2492
  });
@@ -2446,7 +2494,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2446
2494
  if (chosen === "__new__") {
2447
2495
  const projectName = await input5({
2448
2496
  message: "Project name:",
2449
- default: basename(projectRoot)
2497
+ default: basename2(projectRoot)
2450
2498
  });
2451
2499
  const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId, name: projectName }).select("id").single();
2452
2500
  if (createErr || !newFolder) {
@@ -3782,7 +3830,7 @@ Linking "${folder.name}" to this device...
3782
3830
  console.log(` Skills: ${result.skills.length}`);
3783
3831
  console.log(` Toolings: ${result.toolings.length}`);
3784
3832
  console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
3785
- console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s)` : "not configured"}`);
3833
+ console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s) \u2014 ${result.doppler.project}` : "see above"}`);
3786
3834
  console.log(` Plugins: ${result.marketplacePlugins.length} (${result.marketplacePlugins.reduce((n, p) => n + p.skills.length, 0)} skills)`);
3787
3835
  const { error: scanErr } = await supabase.from("claude_folders").update({
3788
3836
  graph_json: result.graph,
@@ -4581,6 +4629,8 @@ async function configSetCommand(key, value) {
4581
4629
  // dist/commands/doppler.js
4582
4630
  init_config();
4583
4631
  init_api();
4632
+ init_auth();
4633
+ init_dist();
4584
4634
  import chalk26 from "chalk";
4585
4635
  import { input as input7 } from "@inquirer/prompts";
4586
4636
  async function dopplerConnectCommand() {
@@ -4599,7 +4649,51 @@ async function dopplerConnectCommand() {
4599
4649
  await mergeCredentials({ dopplerToken: token });
4600
4650
  console.log(chalk26.green(`Token validated \u2014 ${projectCount} project${projectCount !== 1 ? "s" : ""} accessible.`));
4601
4651
  console.log(chalk26.green("Saved to ~/.md4ai/credentials.json"));
4602
- console.log(chalk26.dim("Doppler will be checked automatically on your next scan."));
4652
+ const creds = await loadCredentials();
4653
+ if (!creds?.accessToken || !creds?.userId) {
4654
+ console.log("");
4655
+ console.log(chalk26.yellow("\u26A0 Not logged in to MD4AI \u2014 token saved locally only."));
4656
+ console.log(chalk26.yellow(" The web dashboard cannot show live Doppler status until the token is synced."));
4657
+ console.log(chalk26.yellow(' To fix: run "md4ai login", then "md4ai doppler connect" again.'));
4658
+ } else {
4659
+ let supabase = createSupabaseClient(getAnonKey(), creds.accessToken);
4660
+ let userId = creds.userId;
4661
+ if (Date.now() > creds.expiresAt) {
4662
+ const refreshed = await refreshSession();
4663
+ if (refreshed) {
4664
+ supabase = refreshed.supabase;
4665
+ userId = refreshed.userId;
4666
+ } else {
4667
+ console.log("");
4668
+ console.log(chalk26.yellow("\u26A0 Session expired \u2014 token saved locally only."));
4669
+ console.log(chalk26.yellow(' To sync to the web dashboard: run "md4ai login", then "md4ai doppler connect" again.'));
4670
+ }
4671
+ }
4672
+ if (supabase) {
4673
+ try {
4674
+ const { error } = await supabase.from("user_secrets").upsert({ user_id: userId, doppler_token: token, updated_at: (/* @__PURE__ */ new Date()).toISOString() }, { onConflict: "user_id" });
4675
+ if (error) {
4676
+ console.log(chalk26.yellow(`\u26A0 Could not sync token to web dashboard: ${error.message}`));
4677
+ } else {
4678
+ const { data: verify } = await supabase.from("user_secrets").select("id").eq("user_id", userId).maybeSingle();
4679
+ if (verify) {
4680
+ console.log(chalk26.green("Token synced to web dashboard."));
4681
+ } else {
4682
+ console.log(chalk26.yellow("\u26A0 Token sync could not be verified \u2014 check the web dashboard."));
4683
+ }
4684
+ }
4685
+ } catch (err) {
4686
+ console.log(chalk26.yellow(`\u26A0 Could not sync token to web dashboard: ${err instanceof Error ? err.message : "unknown error"}`));
4687
+ }
4688
+ }
4689
+ }
4690
+ console.log("");
4691
+ console.log(chalk26.cyan("Next step: link a Doppler project to your Claude project."));
4692
+ console.log(chalk26.dim(" The scanner looks for a doppler.yaml in the project root, e.g.:"));
4693
+ console.log(chalk26.dim(" project: my-doppler-project"));
4694
+ console.log(chalk26.dim(" Or use a manual override:"));
4695
+ console.log(chalk26.dim(" md4ai doppler set-project <slug>"));
4696
+ console.log(chalk26.dim(' Then run "md4ai scan" to fetch secret names from Doppler.'));
4603
4697
  }
4604
4698
  async function dopplerDisconnectCommand() {
4605
4699
  const creds = await loadCredentials();
@@ -4614,7 +4708,26 @@ async function dopplerDisconnectCommand() {
4614
4708
  const credPath = join17(homedir10(), ".md4ai", "credentials.json");
4615
4709
  await writeFile6(credPath, JSON.stringify(rest, null, 2), "utf-8");
4616
4710
  await chmod2(credPath, 384);
4617
- console.log(chalk26.green("Doppler token removed."));
4711
+ try {
4712
+ const currentCreds = await loadCredentials();
4713
+ if (currentCreds?.accessToken && currentCreds?.userId) {
4714
+ let supabase = createSupabaseClient(getAnonKey(), currentCreds.accessToken);
4715
+ let userId = currentCreds.userId;
4716
+ if (Date.now() > currentCreds.expiresAt) {
4717
+ const refreshed = await refreshSession();
4718
+ if (refreshed) {
4719
+ supabase = refreshed.supabase;
4720
+ userId = refreshed.userId;
4721
+ }
4722
+ }
4723
+ await supabase.from("user_secrets").delete().eq("user_id", userId);
4724
+ console.log(chalk26.green("Doppler token removed from local and web dashboard."));
4725
+ } else {
4726
+ console.log(chalk26.green("Doppler token removed locally."));
4727
+ }
4728
+ } catch {
4729
+ console.log(chalk26.green("Doppler token removed locally."));
4730
+ }
4618
4731
  }
4619
4732
  async function dopplerSetProjectCommand(slug) {
4620
4733
  const state = await loadState();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md4ai",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "CLI for MD4AI — scan Claude projects and sync to your dashboard",
5
5
  "type": "module",
6
6
  "bin": {