md4ai 0.10.4 → 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 +688 -272
  2. package/package.json +2 -2
@@ -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.10.4" : "0.0.0-dev";
76
+ CURRENT_VERSION = true ? "0.12.0" : "0.0.0-dev";
77
77
  }
78
78
  });
79
79
 
@@ -1479,7 +1479,61 @@ function computeDrift(variables, apps, localPresence, workflowRefs) {
1479
1479
  workflowBrokenRefs: workflowBrokenRefs.sort()
1480
1480
  };
1481
1481
  }
1482
- var execFileAsync, MANIFEST_PATH;
1482
+ function computeDopplerDrift(doppler2, manifest) {
1483
+ const dopplerNotDeployed = [];
1484
+ const notInDoppler = [];
1485
+ const allDopplerSecrets = /* @__PURE__ */ new Set();
1486
+ for (const config2 of doppler2.configs) {
1487
+ for (const name of config2.secretNames)
1488
+ allDopplerSecrets.add(name);
1489
+ }
1490
+ for (const config2 of doppler2.configs) {
1491
+ const targets = CONFIG_TARGET_MAP[config2.name.toLowerCase()];
1492
+ if (!targets)
1493
+ continue;
1494
+ for (const secretName of config2.secretNames) {
1495
+ const manifestVar = manifest.variables.find((v) => v.name === secretName);
1496
+ if (targets.local && manifestVar) {
1497
+ for (const [app, status] of Object.entries(manifestVar.localStatus)) {
1498
+ if (status === "missing") {
1499
+ dopplerNotDeployed.push({
1500
+ variable: secretName,
1501
+ config: config2.name,
1502
+ missingFrom: "local",
1503
+ app
1504
+ });
1505
+ }
1506
+ }
1507
+ }
1508
+ if (targets.vercel && manifestVar?.vercelStatus) {
1509
+ for (const [proj, status] of Object.entries(manifestVar.vercelStatus)) {
1510
+ if (status === "missing") {
1511
+ dopplerNotDeployed.push({
1512
+ variable: secretName,
1513
+ config: config2.name,
1514
+ missingFrom: "vercel",
1515
+ app: proj
1516
+ });
1517
+ }
1518
+ }
1519
+ }
1520
+ if (targets.github && manifestVar?.githubStatus === "missing") {
1521
+ dopplerNotDeployed.push({
1522
+ variable: secretName,
1523
+ config: config2.name,
1524
+ missingFrom: "github"
1525
+ });
1526
+ }
1527
+ }
1528
+ }
1529
+ for (const v of manifest.variables) {
1530
+ if (!allDopplerSecrets.has(v.name)) {
1531
+ notInDoppler.push({ variable: v.name, foundIn: "manifest" });
1532
+ }
1533
+ }
1534
+ return { dopplerNotDeployed, notInDoppler };
1535
+ }
1536
+ var execFileAsync, MANIFEST_PATH, CONFIG_TARGET_MAP;
1483
1537
  var init_env_manifest_scanner = __esm({
1484
1538
  "dist/scanner/env-manifest-scanner.js"() {
1485
1539
  "use strict";
@@ -1488,6 +1542,15 @@ var init_env_manifest_scanner = __esm({
1488
1542
  init_fetch_env_vars();
1489
1543
  execFileAsync = promisify(execFile);
1490
1544
  MANIFEST_PATH = "docs/reference/env-manifest.md";
1545
+ CONFIG_TARGET_MAP = {
1546
+ dev: { local: true, vercel: false, github: false },
1547
+ development: { local: true, vercel: false, github: false },
1548
+ stg: { local: false, vercel: true, github: false },
1549
+ staging: { local: false, vercel: true, github: false },
1550
+ prd: { local: false, vercel: true, github: true },
1551
+ prod: { local: false, vercel: true, github: true },
1552
+ production: { local: false, vercel: true, github: true }
1553
+ };
1491
1554
  }
1492
1555
  });
1493
1556
 
@@ -1591,19 +1654,226 @@ var init_marketplace_scanner = __esm({
1591
1654
  }
1592
1655
  });
1593
1656
 
1657
+ // dist/doppler/auth.js
1658
+ async function resolveDopplerToken() {
1659
+ const creds = await loadCredentials();
1660
+ 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
+ };
1667
+ }
1668
+ return null;
1669
+ }
1670
+ var init_auth3 = __esm({
1671
+ "dist/doppler/auth.js"() {
1672
+ "use strict";
1673
+ init_config();
1674
+ }
1675
+ });
1676
+
1677
+ // dist/doppler/api.js
1678
+ async function dopplerFetch(url, token, retryCount = 0) {
1679
+ const res = await fetch(url, {
1680
+ headers: {
1681
+ Authorization: `Bearer ${token}`,
1682
+ Accept: "application/json"
1683
+ }
1684
+ });
1685
+ if (res.status === 429 && retryCount < MAX_RETRIES) {
1686
+ const retryAfter = parseInt(res.headers.get("retry-after") ?? "2", 10);
1687
+ await new Promise((r) => setTimeout(r, retryAfter * 1e3));
1688
+ return dopplerFetch(url, token, retryCount + 1);
1689
+ }
1690
+ if (!res.ok) {
1691
+ const isAuth = res.status === 401 || res.status === 403;
1692
+ const body = await res.text().catch(() => "");
1693
+ throw new DopplerApiError(`Doppler API ${res.status}: ${body || res.statusText}`, res.status, isAuth);
1694
+ }
1695
+ return res.json();
1696
+ }
1697
+ async function fetchDopplerConfigs(projectSlug, token) {
1698
+ const data = await dopplerFetch(`https://api.doppler.com/v3/configs?project=${encodeURIComponent(projectSlug)}`, token);
1699
+ return data.configs;
1700
+ }
1701
+ async function fetchDopplerSecretNames(projectSlug, configName, token) {
1702
+ const data = await dopplerFetch(`https://api.doppler.com/v3/configs/config/secrets?project=${encodeURIComponent(projectSlug)}&config=${encodeURIComponent(configName)}`, token);
1703
+ return Object.keys(data.secrets);
1704
+ }
1705
+ async function validateDopplerToken(token) {
1706
+ try {
1707
+ const data = await dopplerFetch("https://api.doppler.com/v3/projects", token);
1708
+ return { valid: true, projectCount: data.projects.length };
1709
+ } catch (err) {
1710
+ if (err instanceof DopplerApiError && err.isAuthError) {
1711
+ return { valid: false, projectCount: 0 };
1712
+ }
1713
+ throw err;
1714
+ }
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
+ }
1720
+ var DopplerApiError, MAX_RETRIES;
1721
+ var init_api = __esm({
1722
+ "dist/doppler/api.js"() {
1723
+ "use strict";
1724
+ DopplerApiError = class extends Error {
1725
+ status;
1726
+ isAuthError;
1727
+ constructor(message, status, isAuthError) {
1728
+ super(message);
1729
+ this.status = status;
1730
+ this.isAuthError = isAuthError;
1731
+ this.name = "DopplerApiError";
1732
+ }
1733
+ };
1734
+ MAX_RETRIES = 2;
1735
+ }
1736
+ });
1737
+
1738
+ // dist/scanner/doppler-scanner.js
1739
+ import { readFile as readFile9 } from "node:fs/promises";
1740
+ import { join as join11, basename } from "node:path";
1741
+ import { existsSync as existsSync6 } from "node:fs";
1742
+ import chalk10 from "chalk";
1743
+ import { select as select3 } from "@inquirer/prompts";
1744
+ async function parseDopplerYaml(projectRoot) {
1745
+ const yamlPath = join11(projectRoot, "doppler.yaml");
1746
+ if (!existsSync6(yamlPath))
1747
+ return null;
1748
+ try {
1749
+ const content = await readFile9(yamlPath, "utf-8");
1750
+ const match = content.match(/^project:\s*["']?([a-z0-9_-]+)["']?\s*$/m);
1751
+ return match?.[1] ?? null;
1752
+ } catch {
1753
+ return null;
1754
+ }
1755
+ }
1756
+ async function scanDoppler(projectRoot, folderId) {
1757
+ const tokenResult = await resolveDopplerToken();
1758
+ if (!tokenResult) {
1759
+ console.log(chalk10.dim(' Doppler: no token configured. Run "md4ai doppler connect" to set up.'));
1760
+ return null;
1761
+ }
1762
+ let projectSlug = await parseDopplerYaml(projectRoot);
1763
+ let matchedVia = "doppler-yaml";
1764
+ if (!projectSlug && folderId) {
1765
+ const state = await loadState();
1766
+ projectSlug = state.dopplerProjects?.[folderId] ?? null;
1767
+ if (projectSlug)
1768
+ matchedVia = "manual";
1769
+ }
1770
+ if (!projectSlug) {
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>`));
1805
+ }
1806
+ console.log(chalk10.dim(` Doppler: checking project "${projectSlug}" (via ${matchedVia})...`));
1807
+ try {
1808
+ const apiConfigs = await fetchDopplerConfigs(projectSlug, tokenResult.token);
1809
+ const CONCURRENCY = 4;
1810
+ const configs = [];
1811
+ for (let i = 0; i < apiConfigs.length; i += CONCURRENCY) {
1812
+ const batch = apiConfigs.slice(i, i + CONCURRENCY);
1813
+ const results = await Promise.all(batch.map(async (cfg) => {
1814
+ try {
1815
+ const secretNames = await fetchDopplerSecretNames(projectSlug, cfg.name, tokenResult.token);
1816
+ return {
1817
+ name: cfg.name,
1818
+ environment: cfg.environment,
1819
+ secretNames
1820
+ };
1821
+ } catch (err) {
1822
+ if (err instanceof DopplerApiError && err.status === 429) {
1823
+ console.log(chalk10.yellow(` Doppler: rate limited on config "${cfg.name}", skipping remaining.`));
1824
+ return null;
1825
+ }
1826
+ console.log(chalk10.dim(` Doppler: failed to fetch config "${cfg.name}": ${err instanceof Error ? err.message : String(err)}`));
1827
+ return null;
1828
+ }
1829
+ }));
1830
+ for (const r of results) {
1831
+ if (r)
1832
+ configs.push(r);
1833
+ }
1834
+ }
1835
+ console.log(chalk10.dim(` Doppler: ${configs.length} config(s), ${configs.reduce((n, c) => n + c.secretNames.length, 0)} total secrets`));
1836
+ return {
1837
+ project: projectSlug,
1838
+ configs,
1839
+ matchedVia,
1840
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
1841
+ };
1842
+ } catch (err) {
1843
+ if (err instanceof DopplerApiError) {
1844
+ if (err.isAuthError) {
1845
+ console.log(chalk10.yellow(' Doppler: token invalid or expired. Run "md4ai doppler connect" to reconfigure.'));
1846
+ } else {
1847
+ console.log(chalk10.yellow(` Doppler: API error \u2014 ${err.message}`));
1848
+ }
1849
+ } else {
1850
+ console.log(chalk10.yellow(` Doppler: ${err instanceof Error ? err.message : String(err)}`));
1851
+ }
1852
+ return null;
1853
+ }
1854
+ }
1855
+ var init_doppler_scanner = __esm({
1856
+ "dist/scanner/doppler-scanner.js"() {
1857
+ "use strict";
1858
+ init_auth3();
1859
+ init_api();
1860
+ init_config();
1861
+ }
1862
+ });
1863
+
1594
1864
  // dist/scanner/index.js
1595
1865
  import { readdir as readdir3 } from "node:fs/promises";
1596
- import { join as join11, relative as relative3 } from "node:path";
1597
- import { existsSync as existsSync6 } from "node:fs";
1866
+ import { join as join12, relative as relative3 } from "node:path";
1867
+ import { existsSync as existsSync7 } from "node:fs";
1598
1868
  import { homedir as homedir7 } from "node:os";
1599
1869
  import { createHash } from "node:crypto";
1600
- async function scanProject(projectRoot) {
1870
+ async function scanProject(projectRoot, folderId) {
1601
1871
  const allFiles = await discoverFiles(projectRoot);
1602
1872
  const rootFiles = identifyRoots(allFiles, projectRoot);
1603
1873
  const allRefs = [];
1604
1874
  const allBrokenRefs = [];
1605
1875
  for (const file of allFiles) {
1606
- const fullPath = file.startsWith("/") ? file : join11(projectRoot, file);
1876
+ const fullPath = file.startsWith("/") ? file : join12(projectRoot, file);
1607
1877
  try {
1608
1878
  const { refs, brokenRefs: brokenRefs2 } = await parseFileReferences(fullPath, projectRoot);
1609
1879
  allRefs.push(...refs);
@@ -1618,6 +1888,12 @@ async function scanProject(projectRoot) {
1618
1888
  const toolings = await detectToolings(projectRoot);
1619
1889
  const envManifest = await scanEnvManifest(projectRoot);
1620
1890
  const marketplacePlugins = await scanMarketplacePlugins();
1891
+ const doppler2 = await scanDoppler(projectRoot, folderId);
1892
+ if (doppler2 && envManifest) {
1893
+ const dopplerDrift = computeDopplerDrift(doppler2, envManifest);
1894
+ envManifest.drift.dopplerNotDeployed = dopplerDrift.dopplerNotDeployed;
1895
+ envManifest.drift.notInDoppler = dopplerDrift.notInDoppler;
1896
+ }
1621
1897
  const depthMap = /* @__PURE__ */ new Map();
1622
1898
  const queue = [...rootFiles];
1623
1899
  for (const r of queue)
@@ -1638,7 +1914,7 @@ async function scanProject(projectRoot) {
1638
1914
  depth: depthMap.get(br.from) ?? 999
1639
1915
  }));
1640
1916
  brokenRefs.sort((a, b) => a.depth - b.depth || a.from.localeCompare(b.from));
1641
- const scanData = JSON.stringify({ graph, orphans, brokenRefs, skills, staleFiles, toolings, envManifest, marketplacePlugins });
1917
+ const scanData = JSON.stringify({ graph, orphans, brokenRefs, skills, staleFiles, toolings, envManifest, marketplacePlugins, doppler: doppler2 });
1642
1918
  const dataHash = createHash("sha256").update(scanData).digest("hex");
1643
1919
  return {
1644
1920
  graph,
@@ -1648,6 +1924,7 @@ async function scanProject(projectRoot) {
1648
1924
  staleFiles,
1649
1925
  toolings,
1650
1926
  envManifest,
1927
+ doppler: doppler2,
1651
1928
  marketplacePlugins,
1652
1929
  scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
1653
1930
  dataHash
@@ -1655,18 +1932,18 @@ async function scanProject(projectRoot) {
1655
1932
  }
1656
1933
  async function discoverFiles(projectRoot) {
1657
1934
  const files = [];
1658
- const claudeDir = join11(projectRoot, ".claude");
1659
- if (existsSync6(claudeDir)) {
1935
+ const claudeDir = join12(projectRoot, ".claude");
1936
+ if (existsSync7(claudeDir)) {
1660
1937
  await walkDir(claudeDir, projectRoot, files);
1661
1938
  }
1662
- if (existsSync6(join11(projectRoot, "CLAUDE.md"))) {
1939
+ if (existsSync7(join12(projectRoot, "CLAUDE.md"))) {
1663
1940
  files.push("CLAUDE.md");
1664
1941
  }
1665
- if (existsSync6(join11(projectRoot, "skills.md"))) {
1942
+ if (existsSync7(join12(projectRoot, "skills.md"))) {
1666
1943
  files.push("skills.md");
1667
1944
  }
1668
- const plansDir = join11(projectRoot, "docs", "plans");
1669
- if (existsSync6(plansDir)) {
1945
+ const plansDir = join12(projectRoot, "docs", "plans");
1946
+ if (existsSync7(plansDir)) {
1670
1947
  await walkDir(plansDir, projectRoot, files);
1671
1948
  }
1672
1949
  return [...new Set(files)];
@@ -1674,7 +1951,7 @@ async function discoverFiles(projectRoot) {
1674
1951
  async function walkDir(dir, projectRoot, files) {
1675
1952
  const entries = await readdir3(dir, { withFileTypes: true });
1676
1953
  for (const entry of entries) {
1677
- const fullPath = join11(dir, entry.name);
1954
+ const fullPath = join12(dir, entry.name);
1678
1955
  if (entry.isDirectory()) {
1679
1956
  if (["node_modules", ".git", ".turbo", "cache", "session-env"].includes(entry.name))
1680
1957
  continue;
@@ -1694,15 +1971,15 @@ function identifyRoots(allFiles, projectRoot) {
1694
1971
  }
1695
1972
  for (const globalFile of GLOBAL_ROOT_FILES) {
1696
1973
  const expanded = globalFile.replace("~", homedir7());
1697
- if (existsSync6(expanded)) {
1974
+ if (existsSync7(expanded)) {
1698
1975
  roots.push(globalFile);
1699
1976
  }
1700
1977
  }
1701
1978
  return roots;
1702
1979
  }
1703
1980
  async function readClaudeConfigFiles(projectRoot, graphFilePaths) {
1704
- const { readFile: readFile13, stat: stat2 } = await import("node:fs/promises");
1705
- const { existsSync: existsSync14 } = await import("node:fs");
1981
+ const { readFile: readFile14, stat: stat2 } = await import("node:fs/promises");
1982
+ const { existsSync: existsSync15 } = await import("node:fs");
1706
1983
  const filePaths = graphFilePaths ?? await discoverFiles(projectRoot);
1707
1984
  const files = [];
1708
1985
  const seen = /* @__PURE__ */ new Set();
@@ -1712,11 +1989,11 @@ async function readClaudeConfigFiles(projectRoot, graphFilePaths) {
1712
1989
  if (relPath.startsWith("~") || relPath.startsWith("/"))
1713
1990
  continue;
1714
1991
  seen.add(relPath);
1715
- const fullPath = join11(projectRoot, relPath);
1716
- if (!existsSync14(fullPath))
1992
+ const fullPath = join12(projectRoot, relPath);
1993
+ if (!existsSync15(fullPath))
1717
1994
  continue;
1718
1995
  try {
1719
- const content = await readFile13(fullPath, "utf-8");
1996
+ const content = await readFile14(fullPath, "utf-8");
1720
1997
  const fileStat = await stat2(fullPath);
1721
1998
  const lastMod = getGitLastModified(fullPath, projectRoot);
1722
1999
  files.push({ filePath: relPath, content, sizeBytes: fileStat.size, lastModified: lastMod });
@@ -1729,7 +2006,7 @@ function detectStaleFiles(allFiles, projectRoot) {
1729
2006
  const stale = [];
1730
2007
  const now = Date.now();
1731
2008
  for (const file of allFiles) {
1732
- const fullPath = join11(projectRoot, file);
2009
+ const fullPath = join12(projectRoot, file);
1733
2010
  const lastMod = getGitLastModified(fullPath, projectRoot);
1734
2011
  if (lastMod) {
1735
2012
  const days = Math.floor((now - new Date(lastMod).getTime()) / (1e3 * 60 * 60 * 24));
@@ -1752,6 +2029,7 @@ var init_scanner = __esm({
1752
2029
  init_tooling_detector();
1753
2030
  init_env_manifest_scanner();
1754
2031
  init_marketplace_scanner();
2032
+ init_doppler_scanner();
1755
2033
  }
1756
2034
  });
1757
2035
 
@@ -1896,7 +2174,7 @@ var init_push_toolings = __esm({
1896
2174
  });
1897
2175
 
1898
2176
  // dist/commands/push-health-results.js
1899
- import chalk10 from "chalk";
2177
+ import chalk11 from "chalk";
1900
2178
  async function pushHealthResults(supabase, folderId, manifest) {
1901
2179
  const { data: checks } = await supabase.from("env_health_checks").select("id, check_type, check_config").eq("folder_id", folderId).eq("enabled", true);
1902
2180
  if (!checks?.length)
@@ -1964,9 +2242,9 @@ async function pushHealthResults(supabase, folderId, manifest) {
1964
2242
  if (results.length > 0) {
1965
2243
  const { error } = await supabase.from("env_health_results").insert(results);
1966
2244
  if (error) {
1967
- console.error(chalk10.yellow(`Health results warning: ${error.message}`));
2245
+ console.error(chalk11.yellow(`Health results warning: ${error.message}`));
1968
2246
  } else {
1969
- console.log(chalk10.green(` Updated ${results.length} health check result(s).`));
2247
+ console.log(chalk11.green(` Updated ${results.length} health check result(s).`));
1970
2248
  const checkIds = results.map((r) => r.check_id);
1971
2249
  const { data: allResults } = await supabase.from("env_health_results").select("id, check_id, ran_at").in("check_id", checkIds).order("ran_at", { ascending: false });
1972
2250
  if (allResults) {
@@ -2035,21 +2313,30 @@ var map_exports = {};
2035
2313
  __export(map_exports, {
2036
2314
  mapCommand: () => mapCommand
2037
2315
  });
2038
- import { resolve as resolve4, basename } from "node:path";
2316
+ import { resolve as resolve4, basename as basename2 } from "node:path";
2039
2317
  import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
2040
- import { existsSync as existsSync7 } from "node:fs";
2041
- import chalk11 from "chalk";
2042
- import { select as select3, input as input5, confirm as confirm2 } from "@inquirer/prompts";
2318
+ import { existsSync as existsSync8 } from "node:fs";
2319
+ import chalk12 from "chalk";
2320
+ import { select as select4, input as input5, confirm as confirm2 } from "@inquirer/prompts";
2043
2321
  async function mapCommand(path, options) {
2044
2322
  await checkForUpdate();
2045
2323
  const projectRoot = resolve4(path ?? process.cwd());
2046
- if (!existsSync7(projectRoot)) {
2047
- console.error(chalk11.red(`Path not found: ${projectRoot}`));
2324
+ if (!existsSync8(projectRoot)) {
2325
+ console.error(chalk12.red(`Path not found: ${projectRoot}`));
2048
2326
  process.exit(1);
2049
2327
  }
2050
- console.log(chalk11.blue(`Scanning: ${projectRoot}
2328
+ console.log(chalk12.blue(`Scanning: ${projectRoot}
2051
2329
  `));
2052
- 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);
2053
2340
  console.log(` Files found: ${result.graph.nodes.length}`);
2054
2341
  console.log(` References: ${result.graph.edges.length}`);
2055
2342
  console.log(` Broken refs: ${result.brokenRefs.length}`);
@@ -2058,26 +2345,27 @@ async function mapCommand(path, options) {
2058
2345
  console.log(` Skills: ${result.skills.length}`);
2059
2346
  console.log(` Toolings: ${result.toolings.length}`);
2060
2347
  console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
2348
+ console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s) \u2014 ${result.doppler.project}` : "see above"}`);
2061
2349
  console.log(` Plugins: ${result.marketplacePlugins.length} (${result.marketplacePlugins.reduce((n, p) => n + p.skills.length, 0)} skills)`);
2062
2350
  console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
2063
2351
  if (result.brokenRefs.length > 0) {
2064
- console.log(chalk11.red(`
2352
+ console.log(chalk12.red(`
2065
2353
  Warning: ${result.brokenRefs.length} broken reference(s) found:`));
2066
2354
  for (const br of result.brokenRefs.slice(0, 5)) {
2067
- console.log(chalk11.red(` ${br.from} -> ${br.to}`));
2355
+ console.log(chalk12.red(` ${br.from} -> ${br.to}`));
2068
2356
  }
2069
2357
  if (result.brokenRefs.length > 5) {
2070
- console.log(chalk11.red(` ... and ${result.brokenRefs.length - 5} more`));
2358
+ console.log(chalk12.red(` ... and ${result.brokenRefs.length - 5} more`));
2071
2359
  }
2072
2360
  }
2073
2361
  const outputDir = resolve4(projectRoot, "output");
2074
- if (!existsSync7(outputDir)) {
2362
+ if (!existsSync8(outputDir)) {
2075
2363
  await mkdir2(outputDir, { recursive: true });
2076
2364
  }
2077
2365
  const htmlPath = resolve4(outputDir, "index.html");
2078
2366
  const html = generateOfflineHtml(result, projectRoot);
2079
2367
  await writeFile2(htmlPath, html, "utf-8");
2080
- console.log(chalk11.green(`
2368
+ console.log(chalk12.green(`
2081
2369
  Local preview: ${htmlPath}`));
2082
2370
  if (!options.offline) {
2083
2371
  try {
@@ -2088,7 +2376,7 @@ Local preview: ${htmlPath}`));
2088
2376
  const { data: proposedFiles } = await supabase.from("folder_files").select("id, file_path, proposed_at").eq("folder_id", folder_id).eq("proposed_for_deletion", true);
2089
2377
  if (proposedFiles?.length && process.stdin.isTTY) {
2090
2378
  const { checkbox } = await import("@inquirer/prompts");
2091
- console.log(chalk11.yellow(`
2379
+ console.log(chalk12.yellow(`
2092
2380
  ${proposedFiles.length} file(s) proposed for deletion:
2093
2381
  `));
2094
2382
  const toDelete = await checkbox({
@@ -2102,16 +2390,16 @@ ${proposedFiles.length} file(s) proposed for deletion:
2102
2390
  for (const file of toDelete) {
2103
2391
  const fullPath = resolve4(projectRoot, file.file_path);
2104
2392
  if (!fullPath.startsWith(resolvedRoot + "/")) {
2105
- console.error(chalk11.red(` Blocked path traversal: ${file.file_path}`));
2393
+ console.error(chalk12.red(` Blocked path traversal: ${file.file_path}`));
2106
2394
  continue;
2107
2395
  }
2108
2396
  try {
2109
2397
  const { unlink } = await import("node:fs/promises");
2110
2398
  await unlink(fullPath);
2111
2399
  await supabase.from("folder_files").delete().eq("id", file.id);
2112
- console.log(chalk11.green(` Deleted: ${file.file_path}`));
2400
+ console.log(chalk12.green(` Deleted: ${file.file_path}`));
2113
2401
  } catch (err) {
2114
- console.error(chalk11.red(` Failed to delete ${file.file_path}: ${err instanceof Error ? err.message : String(err)}`));
2402
+ console.error(chalk12.red(` Failed to delete ${file.file_path}: ${err instanceof Error ? err.message : String(err)}`));
2115
2403
  }
2116
2404
  }
2117
2405
  const keptIds = proposedFiles.filter((f) => !toDelete.some((d) => d.id === f.id)).map((f) => f.id);
@@ -2119,11 +2407,11 @@ ${proposedFiles.length} file(s) proposed for deletion:
2119
2407
  for (const id of keptIds) {
2120
2408
  await supabase.from("folder_files").update({ proposed_for_deletion: false, proposed_at: null, proposed_by: null }).eq("id", id);
2121
2409
  }
2122
- console.log(chalk11.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
2410
+ console.log(chalk12.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
2123
2411
  }
2124
2412
  console.log("");
2125
2413
  } else if (proposedFiles?.length) {
2126
- console.log(chalk11.dim(` ${proposedFiles.length} file(s) proposed for deletion \u2014 skipped (non-interactive mode).`));
2414
+ console.log(chalk12.dim(` ${proposedFiles.length} file(s) proposed for deletion \u2014 skipped (non-interactive mode).`));
2127
2415
  }
2128
2416
  let storedManifest = null;
2129
2417
  if (!result.envManifest) {
@@ -2145,9 +2433,12 @@ ${proposedFiles.length} file(s) proposed for deletion:
2145
2433
  if (result.envManifest) {
2146
2434
  updatePayload.env_manifest_json = result.envManifest;
2147
2435
  }
2436
+ if (result.doppler) {
2437
+ updatePayload.doppler_json = result.doppler;
2438
+ }
2148
2439
  const { error } = await supabase.from("claude_folders").update(updatePayload).eq("id", folder_id);
2149
2440
  if (error) {
2150
- console.error(chalk11.yellow(`Sync warning: ${error.message}`));
2441
+ console.error(chalk12.yellow(`Sync warning: ${error.message}`));
2151
2442
  } else {
2152
2443
  await pushToolings(supabase, folder_id, result.toolings);
2153
2444
  const manifestForHealth = result.envManifest ?? storedManifest;
@@ -2160,8 +2451,8 @@ ${proposedFiles.length} file(s) proposed for deletion:
2160
2451
  lastDeviceName: device_name,
2161
2452
  lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
2162
2453
  });
2163
- console.log(chalk11.green("Synced to Supabase."));
2164
- console.log(chalk11.cyan(`
2454
+ console.log(chalk12.green("Synced to Supabase."));
2455
+ console.log(chalk12.cyan(`
2165
2456
  https://www.md4ai.com/project/${folder_id}
2166
2457
  `));
2167
2458
  const graphPaths = result.graph.nodes.map((n) => n.filePath);
@@ -2176,18 +2467,18 @@ ${proposedFiles.length} file(s) proposed for deletion:
2176
2467
  last_modified: file.lastModified
2177
2468
  }, { onConflict: "folder_id,file_path" });
2178
2469
  }
2179
- console.log(chalk11.green(` Uploaded ${configFiles.length} config file(s).`));
2470
+ console.log(chalk12.green(` Uploaded ${configFiles.length} config file(s).`));
2180
2471
  }
2181
2472
  }
2182
2473
  } else {
2183
- console.log(chalk11.yellow("\nThis folder is not linked to a project on your dashboard."));
2474
+ console.log(chalk12.yellow("\nThis folder is not linked to a project on your dashboard."));
2184
2475
  if (!process.stdin.isTTY) {
2185
- console.log(chalk11.dim("Non-interactive mode \u2014 skipping link prompt."));
2476
+ console.log(chalk12.dim("Non-interactive mode \u2014 skipping link prompt."));
2186
2477
  return;
2187
2478
  }
2188
2479
  const shouldLink = await confirm2({ message: "Would you like to link it now?" });
2189
2480
  if (!shouldLink) {
2190
- console.log(chalk11.dim("Skipped \u2014 local preview still generated."));
2481
+ console.log(chalk12.dim("Skipped \u2014 local preview still generated."));
2191
2482
  } else {
2192
2483
  const { supabase: sb, userId } = await getAuthenticatedClient();
2193
2484
  const { data: folders } = await sb.from("claude_folders").select("id, name").order("name");
@@ -2195,7 +2486,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2195
2486
  { name: "+ Create a new project", value: "__new__" },
2196
2487
  ...(folders ?? []).map((f) => ({ name: f.name, value: f.id }))
2197
2488
  ];
2198
- const chosen = await select3({
2489
+ const chosen = await select4({
2199
2490
  message: "Link to which project?",
2200
2491
  choices
2201
2492
  });
@@ -2203,15 +2494,15 @@ ${proposedFiles.length} file(s) proposed for deletion:
2203
2494
  if (chosen === "__new__") {
2204
2495
  const projectName = await input5({
2205
2496
  message: "Project name:",
2206
- default: basename(projectRoot)
2497
+ default: basename2(projectRoot)
2207
2498
  });
2208
2499
  const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId, name: projectName }).select("id").single();
2209
2500
  if (createErr || !newFolder) {
2210
- console.error(chalk11.red(`Failed to create project: ${createErr?.message}`));
2501
+ console.error(chalk12.red(`Failed to create project: ${createErr?.message}`));
2211
2502
  return;
2212
2503
  }
2213
2504
  folderId = newFolder.id;
2214
- console.log(chalk11.green(`Created project "${projectName}".`));
2505
+ console.log(chalk12.green(`Created project "${projectName}".`));
2215
2506
  } else {
2216
2507
  folderId = chosen;
2217
2508
  }
@@ -2237,6 +2528,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2237
2528
  skills_table_json: result.skills,
2238
2529
  stale_files_json: result.staleFiles,
2239
2530
  env_manifest_json: result.envManifest,
2531
+ doppler_json: result.doppler,
2240
2532
  marketplace_plugins_json: result.marketplacePlugins,
2241
2533
  last_scanned: result.scannedAt,
2242
2534
  data_hash: result.dataHash
@@ -2261,14 +2553,14 @@ ${proposedFiles.length} file(s) proposed for deletion:
2261
2553
  lastDeviceName: deviceName,
2262
2554
  lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
2263
2555
  });
2264
- console.log(chalk11.green("\nLinked and synced."));
2265
- console.log(chalk11.cyan(`
2556
+ console.log(chalk12.green("\nLinked and synced."));
2557
+ console.log(chalk12.cyan(`
2266
2558
  https://www.md4ai.com/project/${folderId}
2267
2559
  `));
2268
2560
  }
2269
2561
  }
2270
2562
  } catch {
2271
- console.log(chalk11.yellow("Not logged in \u2014 local preview only."));
2563
+ console.log(chalk12.yellow("Not logged in \u2014 local preview only."));
2272
2564
  }
2273
2565
  }
2274
2566
  }
@@ -2292,29 +2584,29 @@ __export(sync_exports, {
2292
2584
  syncCommand: () => syncCommand
2293
2585
  });
2294
2586
  import { resolve as resolve5 } from "node:path";
2295
- import { existsSync as existsSync10 } from "node:fs";
2296
- import chalk14 from "chalk";
2587
+ import { existsSync as existsSync11 } from "node:fs";
2588
+ import chalk15 from "chalk";
2297
2589
  function isValidProjectPath(p) {
2298
2590
  const resolved = resolve5(p);
2299
- return resolved.startsWith("/") && !resolved.includes("..") && existsSync10(resolved);
2591
+ return resolved.startsWith("/") && !resolved.includes("..") && existsSync11(resolved);
2300
2592
  }
2301
2593
  async function syncCommand(options) {
2302
2594
  const { supabase } = await getAuthenticatedClient();
2303
2595
  if (options.all) {
2304
2596
  const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path");
2305
2597
  if (error || !devices?.length) {
2306
- console.error(chalk14.red("No devices found."));
2598
+ console.error(chalk15.red("No devices found."));
2307
2599
  process.exit(1);
2308
2600
  }
2309
2601
  for (const device of devices) {
2310
2602
  if (!isValidProjectPath(device.path)) {
2311
- console.error(chalk14.red(` Skipping invalid path: ${device.path}`));
2603
+ console.error(chalk15.red(` Skipping invalid path: ${device.path}`));
2312
2604
  continue;
2313
2605
  }
2314
- console.log(chalk14.blue(`Syncing: ${device.path}`));
2606
+ console.log(chalk15.blue(`Syncing: ${device.path}`));
2315
2607
  const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
2316
2608
  if (proposedAll?.length) {
2317
- console.log(chalk14.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2609
+ console.log(chalk15.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2318
2610
  }
2319
2611
  try {
2320
2612
  const result = await scanProject(device.path);
@@ -2330,32 +2622,32 @@ async function syncCommand(options) {
2330
2622
  }).eq("id", device.folder_id);
2331
2623
  await pushToolings(supabase, device.folder_id, result.toolings);
2332
2624
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
2333
- console.log(chalk14.green(` Done: ${device.device_name}`));
2625
+ console.log(chalk15.green(` Done: ${device.device_name}`));
2334
2626
  } catch (err) {
2335
- console.error(chalk14.red(` Failed: ${device.path}: ${err}`));
2627
+ console.error(chalk15.red(` Failed: ${device.path}: ${err}`));
2336
2628
  }
2337
2629
  }
2338
2630
  await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
2339
- console.log(chalk14.green("\nAll devices synced."));
2631
+ console.log(chalk15.green("\nAll devices synced."));
2340
2632
  } else {
2341
2633
  const state = await loadState();
2342
2634
  if (!state.lastFolderId) {
2343
- console.error(chalk14.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
2635
+ console.error(chalk15.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
2344
2636
  process.exit(1);
2345
2637
  }
2346
2638
  const { data: device } = await supabase.from("device_paths").select("folder_id, device_name, path").eq("folder_id", state.lastFolderId).eq("device_name", state.lastDeviceName).single();
2347
2639
  if (!device) {
2348
- console.error(chalk14.red("Could not find last synced device/folder."));
2640
+ console.error(chalk15.red("Could not find last synced device/folder."));
2349
2641
  process.exit(1);
2350
2642
  }
2351
2643
  if (!isValidProjectPath(device.path)) {
2352
- console.error(chalk14.red(`Invalid project path: ${device.path}`));
2644
+ console.error(chalk15.red(`Invalid project path: ${device.path}`));
2353
2645
  process.exit(1);
2354
2646
  }
2355
- console.log(chalk14.blue(`Syncing: ${device.path}`));
2647
+ console.log(chalk15.blue(`Syncing: ${device.path}`));
2356
2648
  const { data: proposedSingle } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
2357
2649
  if (proposedSingle?.length) {
2358
- console.log(chalk14.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2650
+ console.log(chalk15.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2359
2651
  }
2360
2652
  const result = await scanProject(device.path);
2361
2653
  await supabase.from("claude_folders").update({
@@ -2370,7 +2662,7 @@ async function syncCommand(options) {
2370
2662
  await pushToolings(supabase, device.folder_id, result.toolings);
2371
2663
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", device.folder_id).eq("device_name", device.device_name);
2372
2664
  await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
2373
- console.log(chalk14.green("Synced."));
2665
+ console.log(chalk15.green("Synced."));
2374
2666
  }
2375
2667
  }
2376
2668
  var init_sync = __esm({
@@ -2384,16 +2676,16 @@ var init_sync = __esm({
2384
2676
  });
2385
2677
 
2386
2678
  // dist/mcp/read-configs.js
2387
- import { readFile as readFile11 } from "node:fs/promises";
2388
- import { join as join14 } from "node:path";
2679
+ import { readFile as readFile12 } from "node:fs/promises";
2680
+ import { join as join15 } from "node:path";
2389
2681
  import { homedir as homedir9 } from "node:os";
2390
- import { existsSync as existsSync12 } from "node:fs";
2682
+ import { existsSync as existsSync13 } from "node:fs";
2391
2683
  import { readdir as readdir4 } from "node:fs/promises";
2392
2684
  async function readJsonSafe(path) {
2393
2685
  try {
2394
- if (!existsSync12(path))
2686
+ if (!existsSync13(path))
2395
2687
  return null;
2396
- const raw = await readFile11(path, "utf-8");
2688
+ const raw = await readFile12(path, "utf-8");
2397
2689
  return JSON.parse(raw);
2398
2690
  } catch {
2399
2691
  return null;
@@ -2455,50 +2747,50 @@ function parseFlatConfig(data, source) {
2455
2747
  async function readAllMcpConfigs() {
2456
2748
  const home = homedir9();
2457
2749
  const entries = [];
2458
- const userConfig = await readJsonSafe(join14(home, ".claude.json"));
2750
+ const userConfig = await readJsonSafe(join15(home, ".claude.json"));
2459
2751
  entries.push(...parseServers(userConfig, "global"));
2460
- const globalMcp = await readJsonSafe(join14(home, ".claude", "mcp.json"));
2752
+ const globalMcp = await readJsonSafe(join15(home, ".claude", "mcp.json"));
2461
2753
  entries.push(...parseServers(globalMcp, "global"));
2462
- const cwdMcp = await readJsonSafe(join14(process.cwd(), ".mcp.json"));
2754
+ const cwdMcp = await readJsonSafe(join15(process.cwd(), ".mcp.json"));
2463
2755
  entries.push(...parseServers(cwdMcp, "project"));
2464
- const pluginsBase = join14(home, ".claude", "plugins", "marketplaces");
2465
- if (existsSync12(pluginsBase)) {
2756
+ const pluginsBase = join15(home, ".claude", "plugins", "marketplaces");
2757
+ if (existsSync13(pluginsBase)) {
2466
2758
  try {
2467
2759
  const marketplaces = await readdir4(pluginsBase, { withFileTypes: true });
2468
2760
  for (const mp of marketplaces) {
2469
2761
  if (!mp.isDirectory())
2470
2762
  continue;
2471
- const extDir = join14(pluginsBase, mp.name, "external_plugins");
2472
- if (!existsSync12(extDir))
2763
+ const extDir = join15(pluginsBase, mp.name, "external_plugins");
2764
+ if (!existsSync13(extDir))
2473
2765
  continue;
2474
2766
  const plugins = await readdir4(extDir, { withFileTypes: true });
2475
2767
  for (const plugin of plugins) {
2476
2768
  if (!plugin.isDirectory())
2477
2769
  continue;
2478
- const pluginMcp = await readJsonSafe(join14(extDir, plugin.name, ".mcp.json"));
2770
+ const pluginMcp = await readJsonSafe(join15(extDir, plugin.name, ".mcp.json"));
2479
2771
  entries.push(...parseServers(pluginMcp, "plugin"));
2480
2772
  }
2481
2773
  }
2482
2774
  } catch {
2483
2775
  }
2484
2776
  }
2485
- const cacheBase = join14(home, ".claude", "plugins", "cache");
2486
- if (existsSync12(cacheBase)) {
2777
+ const cacheBase = join15(home, ".claude", "plugins", "cache");
2778
+ if (existsSync13(cacheBase)) {
2487
2779
  try {
2488
2780
  const registries = await readdir4(cacheBase, { withFileTypes: true });
2489
2781
  for (const reg of registries) {
2490
2782
  if (!reg.isDirectory())
2491
2783
  continue;
2492
- const regDir = join14(cacheBase, reg.name);
2784
+ const regDir = join15(cacheBase, reg.name);
2493
2785
  const plugins = await readdir4(regDir, { withFileTypes: true });
2494
2786
  for (const plugin of plugins) {
2495
2787
  if (!plugin.isDirectory())
2496
2788
  continue;
2497
- const versionDirs = await readdir4(join14(regDir, plugin.name), { withFileTypes: true });
2789
+ const versionDirs = await readdir4(join15(regDir, plugin.name), { withFileTypes: true });
2498
2790
  for (const ver of versionDirs) {
2499
2791
  if (!ver.isDirectory())
2500
2792
  continue;
2501
- const mcpPath = join14(regDir, plugin.name, ver.name, ".mcp.json");
2793
+ const mcpPath = join15(regDir, plugin.name, ver.name, ".mcp.json");
2502
2794
  const flatData = await readJsonSafe(mcpPath);
2503
2795
  if (flatData) {
2504
2796
  entries.push(...parseFlatConfig(flatData, "plugin"));
@@ -2648,7 +2940,7 @@ var mcp_watch_exports = {};
2648
2940
  __export(mcp_watch_exports, {
2649
2941
  mcpWatchCommand: () => mcpWatchCommand
2650
2942
  });
2651
- import chalk20 from "chalk";
2943
+ import chalk21 from "chalk";
2652
2944
  import { execFileSync as execFileSync5 } from "node:child_process";
2653
2945
  import { createHash as createHash2 } from "node:crypto";
2654
2946
  function detectTty() {
@@ -2802,20 +3094,20 @@ function buildRows(configs, httpResults, cdpStatus) {
2802
3094
  }
2803
3095
  function printTable(rows, deviceName, watcherPid, cdpResult) {
2804
3096
  process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
2805
- console.log(chalk20.bold.cyan(`
2806
- MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) + chalk20.dim(` (PID ${watcherPid})`));
2807
- console.log(chalk20.dim(` ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \xB7 refreshes every 30s \xB7 Ctrl+C to stop
3097
+ console.log(chalk21.bold.cyan(`
3098
+ MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) + chalk21.dim(` (PID ${watcherPid})`));
3099
+ console.log(chalk21.dim(` ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \xB7 refreshes every 30s \xB7 Ctrl+C to stop
2808
3100
  `));
2809
3101
  if (cdpResult) {
2810
3102
  if (cdpResult.status === "reachable") {
2811
3103
  const browserInfo = cdpResult.browser ? ` (${cdpResult.browser})` : "";
2812
- console.log(chalk20.green(" \u2714 Chrome browser connected") + chalk20.dim(browserInfo));
2813
- console.log(chalk20.dim(" Claude Code can control Chrome via DevTools Protocol on localhost:9222"));
3104
+ console.log(chalk21.green(" \u2714 Chrome browser connected") + chalk21.dim(browserInfo));
3105
+ console.log(chalk21.dim(" Claude Code can control Chrome via DevTools Protocol on localhost:9222"));
2814
3106
  } else {
2815
- console.log(chalk20.red(" \u2717 Chrome browser not connected"));
2816
- console.log(chalk20.dim(" Claude Code cannot reach Chrome. To fix:"));
2817
- console.log(chalk20.dim(" 1. Open Chrome with: google-chrome --remote-debugging-port=9222"));
2818
- console.log(chalk20.dim(" 2. Or add --remote-debugging-port=9222 to your Chrome shortcut"));
3107
+ console.log(chalk21.red(" \u2717 Chrome browser not connected"));
3108
+ console.log(chalk21.dim(" Claude Code cannot reach Chrome. To fix:"));
3109
+ console.log(chalk21.dim(" 1. Open Chrome with: google-chrome --remote-debugging-port=9222"));
3110
+ console.log(chalk21.dim(" 2. Or add --remote-debugging-port=9222 to your Chrome shortcut"));
2819
3111
  }
2820
3112
  console.log("");
2821
3113
  }
@@ -2836,41 +3128,41 @@ function printTable(rows, deviceName, watcherPid, cdpResult) {
2836
3128
  sessionNum++;
2837
3129
  const totalMem = servers.reduce((sum, s) => sum + (s.memory_mb ?? 0), 0);
2838
3130
  const label = byTty.size === 1 ? "Claude Code session" : `Claude Code session ${sessionNum}`;
2839
- console.log(chalk20.green(` ${label}`) + chalk20.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB \xB7 terminal ${tty}`));
3131
+ console.log(chalk21.green(` ${label}`) + chalk21.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB \xB7 terminal ${tty}`));
2840
3132
  for (const s of servers) {
2841
3133
  const uptime = formatUptime(s.uptime_seconds ?? 0);
2842
- const source = chalk20.dim(`[${s.config_source}]`);
2843
- console.log(` ${chalk20.green("\u25CF")} ${s.server_name.padEnd(20)} ${source} ${chalk20.dim((s.package_name ?? "").padEnd(25))} ${String(s.memory_mb ?? 0).padStart(4)} MB ${uptime}`);
3134
+ const source = chalk21.dim(`[${s.config_source}]`);
3135
+ console.log(` ${chalk21.green("\u25CF")} ${s.server_name.padEnd(20)} ${source} ${chalk21.dim((s.package_name ?? "").padEnd(25))} ${String(s.memory_mb ?? 0).padStart(4)} MB ${uptime}`);
2844
3136
  }
2845
3137
  console.log("");
2846
3138
  }
2847
3139
  if (byTty.size > 1) {
2848
- console.log(chalk20.dim(" Each open Claude Code window runs its own set of MCP servers.\n"));
3140
+ console.log(chalk21.dim(" Each open Claude Code window runs its own set of MCP servers.\n"));
2849
3141
  }
2850
3142
  }
2851
3143
  if (runningHttp.length > 0) {
2852
- console.log(chalk20.blue(` Remote Services (${runningHttp.length})`) + chalk20.dim(" \u2014 HTTP endpoints reachable"));
3144
+ console.log(chalk21.blue(` Remote Services (${runningHttp.length})`) + chalk21.dim(" \u2014 HTTP endpoints reachable"));
2853
3145
  for (const s of runningHttp) {
2854
- console.log(` ${chalk20.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk20.dim((s.http_url ?? "").padEnd(30))}`);
3146
+ console.log(` ${chalk21.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk21.dim((s.http_url ?? "").padEnd(30))}`);
2855
3147
  }
2856
3148
  console.log("");
2857
3149
  }
2858
3150
  if (stopped.length > 0 || errored.length > 0) {
2859
3151
  const notRunning = [...stopped, ...errored];
2860
- console.log(chalk20.yellow(` Not Running (${notRunning.length})`) + chalk20.dim(" \u2014 configured but no process detected"));
3152
+ console.log(chalk21.yellow(` Not Running (${notRunning.length})`) + chalk21.dim(" \u2014 configured but no process detected"));
2861
3153
  for (const s of notRunning) {
2862
- const icon = s.status === "error" ? chalk20.red("\u2717") : chalk20.yellow("\u25CB");
2863
- const source = chalk20.dim(`[${s.config_source}]`);
2864
- const detail = s.error_detail ? chalk20.dim(` \u2014 ${s.error_detail}`) : "";
3154
+ const icon = s.status === "error" ? chalk21.red("\u2717") : chalk21.yellow("\u25CB");
3155
+ const source = chalk21.dim(`[${s.config_source}]`);
3156
+ const detail = s.error_detail ? chalk21.dim(` \u2014 ${s.error_detail}`) : "";
2865
3157
  console.log(` ${icon} ${s.server_name.padEnd(20)} ${source}${detail}`);
2866
3158
  }
2867
3159
  console.log("");
2868
3160
  }
2869
3161
  if (rows.length === 0) {
2870
- console.log(chalk20.yellow(" No MCP servers configured."));
2871
- console.log(chalk20.dim(" Configure servers in ~/.claude/mcp.json or .mcp.json\n"));
3162
+ console.log(chalk21.yellow(" No MCP servers configured."));
3163
+ console.log(chalk21.dim(" Configure servers in ~/.claude/mcp.json or .mcp.json\n"));
2872
3164
  }
2873
- console.log(chalk20.bgYellow.black.bold(" \u26A0 DO NOT CLOSE THIS WINDOW \u2014 it feeds live data to the dashboard \u26A0 "));
3165
+ console.log(chalk21.bgYellow.black.bold(" \u26A0 DO NOT CLOSE THIS WINDOW \u2014 it feeds live data to the dashboard \u26A0 "));
2874
3166
  console.log("");
2875
3167
  }
2876
3168
  function formatUptime(seconds) {
@@ -2899,7 +3191,7 @@ async function mcpWatchCommand() {
2899
3191
  const { data: existingWatchers } = await supabase.from("mcp_watchers").select("pid, tty, cli_version, started_at").eq("device_id", deviceId);
2900
3192
  if (existingWatchers && existingWatchers.length > 0) {
2901
3193
  console.log("");
2902
- console.log(chalk20.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
3194
+ console.log(chalk21.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
2903
3195
  for (const w of existingWatchers) {
2904
3196
  try {
2905
3197
  if (typeof w.pid === "number" && w.pid > 1 && w.pid !== process.pid) {
@@ -2910,15 +3202,15 @@ async function mcpWatchCommand() {
2910
3202
  }
2911
3203
  await supabase.from("mcp_watchers").delete().eq("device_id", deviceId);
2912
3204
  await new Promise((r) => setTimeout(r, 1e3));
2913
- console.log(chalk20.dim(" Previous watcher stopped.\n"));
3205
+ console.log(chalk21.dim(" Previous watcher stopped.\n"));
2914
3206
  }
2915
3207
  process.stdout.write(`\x1B]0;MCP mon\x07`);
2916
- console.log(chalk20.blue(`Starting MCP monitor for ${deviceName}...`));
3208
+ console.log(chalk21.blue(`Starting MCP monitor for ${deviceName}...`));
2917
3209
  console.log("");
2918
- console.log(chalk20.dim(" View this in the online dashboard at:"));
2919
- console.log(chalk20.cyan(` https://www.md4ai.com/device/${deviceId}`));
3210
+ console.log(chalk21.dim(" View this in the online dashboard at:"));
3211
+ console.log(chalk21.cyan(` https://www.md4ai.com/device/${deviceId}`));
2920
3212
  console.log("");
2921
- console.log(chalk20.yellow(" Please note, closing this window will stop ALL watch reports."));
3213
+ console.log(chalk21.yellow(" Please note, closing this window will stop ALL watch reports."));
2922
3214
  console.log("");
2923
3215
  await supabase.from("mcp_watchers").upsert({
2924
3216
  device_id: deviceId,
@@ -2948,7 +3240,7 @@ async function mcpWatchCommand() {
2948
3240
  return;
2949
3241
  }
2950
3242
  if (deleteError) {
2951
- console.error(chalk20.red(` [debug] Failed to delete old status: ${deleteError.message}`));
3243
+ console.error(chalk21.red(` [debug] Failed to delete old status: ${deleteError.message}`));
2952
3244
  }
2953
3245
  if (rows.length > 0) {
2954
3246
  const { error: insertError } = await supabase.from("mcp_server_status").insert(rows.map((row) => ({
@@ -2961,7 +3253,7 @@ async function mcpWatchCommand() {
2961
3253
  return;
2962
3254
  }
2963
3255
  if (insertError) {
2964
- console.error(chalk20.red(` [debug] Failed to write MCP status: ${insertError.message}`));
3256
+ console.error(chalk21.red(` [debug] Failed to write MCP status: ${insertError.message}`));
2965
3257
  }
2966
3258
  }
2967
3259
  const { error: heartbeatError } = await supabase.from("mcp_watchers").update({ last_heartbeat: now }).eq("device_id", deviceId).eq("pid", myPid);
@@ -2979,11 +3271,11 @@ async function mcpWatchCommand() {
2979
3271
  return;
2980
3272
  }
2981
3273
  jwtRefreshAttempted = true;
2982
- console.log(chalk20.yellow("\n Session token expired \u2014 attempting to refresh..."));
3274
+ console.log(chalk21.yellow("\n Session token expired \u2014 attempting to refresh..."));
2983
3275
  const refreshed = await refreshSession();
2984
3276
  if (refreshed) {
2985
3277
  supabase = refreshed.supabase;
2986
- console.log(chalk20.green(" Session refreshed successfully. Resuming monitoring.\n"));
3278
+ console.log(chalk21.green(" Session refreshed successfully. Resuming monitoring.\n"));
2987
3279
  jwtRefreshAttempted = false;
2988
3280
  await supabase.from("mcp_watchers").upsert({
2989
3281
  device_id: deviceId,
@@ -3001,14 +3293,14 @@ async function mcpWatchCommand() {
3001
3293
  function printSessionExpired() {
3002
3294
  process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
3003
3295
  console.log("");
3004
- console.log(chalk20.bgRed.white.bold(" \u2717 SESSION EXPIRED \u2014 this watcher is no longer sending data to the dashboard \u2717 "));
3296
+ console.log(chalk21.bgRed.white.bold(" \u2717 SESSION EXPIRED \u2014 this watcher is no longer sending data to the dashboard \u2717 "));
3005
3297
  console.log("");
3006
- console.log(chalk20.yellow(" Your authentication token has expired and could not be refreshed."));
3007
- console.log(chalk20.yellow(' The dashboard will show "No watchers" because this process can no longer update it.'));
3298
+ console.log(chalk21.yellow(" Your authentication token has expired and could not be refreshed."));
3299
+ console.log(chalk21.yellow(' The dashboard will show "No watchers" because this process can no longer update it.'));
3008
3300
  console.log("");
3009
- console.log(chalk20.white(" To fix this:"));
3010
- console.log(chalk20.cyan(" 1. Run: md4ai login"));
3011
- console.log(chalk20.cyan(" 2. Then restart the watcher: md4ai mcp-watch"));
3301
+ console.log(chalk21.white(" To fix this:"));
3302
+ console.log(chalk21.cyan(" 1. Run: md4ai login"));
3303
+ console.log(chalk21.cyan(" 2. Then restart the watcher: md4ai mcp-watch"));
3012
3304
  console.log("");
3013
3305
  }
3014
3306
  let interval;
@@ -3051,7 +3343,7 @@ async function mcpWatchCommand() {
3051
3343
  clearInterval(interval);
3052
3344
  clearInterval(envInterval);
3053
3345
  await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).eq("pid", myPid);
3054
- console.log(chalk20.dim("\nMCP monitor stopped."));
3346
+ console.log(chalk21.dim("\nMCP monitor stopped."));
3055
3347
  process.exit(0);
3056
3348
  };
3057
3349
  process.on("SIGINT", () => {
@@ -3359,71 +3651,71 @@ init_map();
3359
3651
 
3360
3652
  // dist/commands/simulate.js
3361
3653
  init_dist();
3362
- import { join as join12 } from "node:path";
3363
- import { existsSync as existsSync8 } from "node:fs";
3654
+ import { join as join13 } from "node:path";
3655
+ import { existsSync as existsSync9 } from "node:fs";
3364
3656
  import { homedir as homedir8 } from "node:os";
3365
- import chalk12 from "chalk";
3657
+ import chalk13 from "chalk";
3366
3658
  async function simulateCommand(prompt) {
3367
3659
  const projectRoot = process.cwd();
3368
- console.log(chalk12.blue(`Simulating prompt: "${prompt}"
3660
+ console.log(chalk13.blue(`Simulating prompt: "${prompt}"
3369
3661
  `));
3370
- console.log(chalk12.dim("Files Claude would load:\n"));
3371
- const globalClaude = join12(homedir8(), ".claude", "CLAUDE.md");
3372
- if (existsSync8(globalClaude)) {
3373
- console.log(chalk12.green(" \u2713 ~/.claude/CLAUDE.md (global)"));
3662
+ console.log(chalk13.dim("Files Claude would load:\n"));
3663
+ const globalClaude = join13(homedir8(), ".claude", "CLAUDE.md");
3664
+ if (existsSync9(globalClaude)) {
3665
+ console.log(chalk13.green(" \u2713 ~/.claude/CLAUDE.md (global)"));
3374
3666
  }
3375
3667
  for (const rootFile of ROOT_FILES) {
3376
- const fullPath = join12(projectRoot, rootFile);
3377
- if (existsSync8(fullPath)) {
3378
- console.log(chalk12.green(` \u2713 ${rootFile} (project root)`));
3668
+ const fullPath = join13(projectRoot, rootFile);
3669
+ if (existsSync9(fullPath)) {
3670
+ console.log(chalk13.green(` \u2713 ${rootFile} (project root)`));
3379
3671
  }
3380
3672
  }
3381
3673
  const settingsFiles = [
3382
- join12(homedir8(), ".claude", "settings.json"),
3383
- join12(homedir8(), ".claude", "settings.local.json"),
3384
- join12(projectRoot, ".claude", "settings.json"),
3385
- join12(projectRoot, ".claude", "settings.local.json")
3674
+ join13(homedir8(), ".claude", "settings.json"),
3675
+ join13(homedir8(), ".claude", "settings.local.json"),
3676
+ join13(projectRoot, ".claude", "settings.json"),
3677
+ join13(projectRoot, ".claude", "settings.local.json")
3386
3678
  ];
3387
3679
  for (const sf of settingsFiles) {
3388
- if (existsSync8(sf)) {
3680
+ if (existsSync9(sf)) {
3389
3681
  const display = sf.startsWith(homedir8()) ? sf.replace(homedir8(), "~") : sf.replace(projectRoot + "/", "");
3390
- console.log(chalk12.green(` \u2713 ${display} (settings)`));
3682
+ console.log(chalk13.green(` \u2713 ${display} (settings)`));
3391
3683
  }
3392
3684
  }
3393
3685
  const words = prompt.split(/\s+/);
3394
3686
  for (const word of words) {
3395
3687
  const cleaned = word.replace(/['"]/g, "");
3396
- const candidatePath = join12(projectRoot, cleaned);
3397
- if (existsSync8(candidatePath) && cleaned.includes("/")) {
3398
- console.log(chalk12.cyan(` \u2192 ${cleaned} (referenced in prompt)`));
3688
+ const candidatePath = join13(projectRoot, cleaned);
3689
+ if (existsSync9(candidatePath) && cleaned.includes("/")) {
3690
+ console.log(chalk13.cyan(` \u2192 ${cleaned} (referenced in prompt)`));
3399
3691
  }
3400
3692
  }
3401
- console.log(chalk12.dim("\nNote: This is an approximation. Actual file loading depends on Claude Code internals."));
3693
+ console.log(chalk13.dim("\nNote: This is an approximation. Actual file loading depends on Claude Code internals."));
3402
3694
  }
3403
3695
 
3404
3696
  // dist/commands/print.js
3405
- import { join as join13 } from "node:path";
3406
- import { readFile as readFile9, writeFile as writeFile3 } from "node:fs/promises";
3407
- import { existsSync as existsSync9 } from "node:fs";
3408
- import chalk13 from "chalk";
3697
+ import { join as join14 } from "node:path";
3698
+ import { readFile as readFile10, writeFile as writeFile3 } from "node:fs/promises";
3699
+ import { existsSync as existsSync10 } from "node:fs";
3700
+ import chalk14 from "chalk";
3409
3701
  async function printCommand(title) {
3410
3702
  const projectRoot = process.cwd();
3411
- const scanDataPath = join13(projectRoot, "output", "index.html");
3412
- if (!existsSync9(scanDataPath)) {
3413
- console.error(chalk13.red("No scan data found. Run: md4ai scan"));
3703
+ const scanDataPath = join14(projectRoot, "output", "index.html");
3704
+ if (!existsSync10(scanDataPath)) {
3705
+ console.error(chalk14.red("No scan data found. Run: md4ai scan"));
3414
3706
  process.exit(1);
3415
3707
  }
3416
- const html = await readFile9(scanDataPath, "utf-8");
3708
+ const html = await readFile10(scanDataPath, "utf-8");
3417
3709
  const match = html.match(/<script type="application\/json" id="scan-data">([\s\S]*?)<\/script>/);
3418
3710
  if (!match) {
3419
- console.error(chalk13.red("Could not extract scan data from output/index.html"));
3711
+ console.error(chalk14.red("Could not extract scan data from output/index.html"));
3420
3712
  process.exit(1);
3421
3713
  }
3422
3714
  const result = JSON.parse(match[1]);
3423
3715
  const printHtml = generatePrintHtml(result, title);
3424
- const outputPath = join13(projectRoot, "output", `print-${Date.now()}.html`);
3716
+ const outputPath = join14(projectRoot, "output", `print-${Date.now()}.html`);
3425
3717
  await writeFile3(outputPath, printHtml, "utf-8");
3426
- console.log(chalk13.green(`Print-ready wall sheet: ${outputPath}`));
3718
+ console.log(chalk14.green(`Print-ready wall sheet: ${outputPath}`));
3427
3719
  }
3428
3720
  function escapeHtml2(text) {
3429
3721
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -3489,7 +3781,7 @@ init_scanner();
3489
3781
  init_push_toolings();
3490
3782
  init_device_utils();
3491
3783
  import { resolve as resolve6 } from "node:path";
3492
- import chalk15 from "chalk";
3784
+ import chalk16 from "chalk";
3493
3785
  async function linkCommand(projectId) {
3494
3786
  const { supabase, userId } = await getAuthenticatedClient();
3495
3787
  const cwd = resolve6(process.cwd());
@@ -3497,11 +3789,11 @@ async function linkCommand(projectId) {
3497
3789
  const osType = detectOs2();
3498
3790
  const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
3499
3791
  if (folderErr || !folder) {
3500
- console.error(chalk15.red("Project not found, or you do not have access."));
3501
- console.error(chalk15.yellow("Check the project ID in the MD4AI web dashboard."));
3792
+ console.error(chalk16.red("Project not found, or you do not have access."));
3793
+ console.error(chalk16.yellow("Check the project ID in the MD4AI web dashboard."));
3502
3794
  process.exit(1);
3503
3795
  }
3504
- console.log(chalk15.blue(`
3796
+ console.log(chalk16.blue(`
3505
3797
  Linking "${folder.name}" to this device...
3506
3798
  `));
3507
3799
  console.log(` Project: ${folder.name}`);
@@ -3525,19 +3817,20 @@ Linking "${folder.name}" to this device...
3525
3817
  path: cwd
3526
3818
  });
3527
3819
  if (pathErr) {
3528
- console.error(chalk15.red(`Failed to link: ${pathErr.message}`));
3820
+ console.error(chalk16.red(`Failed to link: ${pathErr.message}`));
3529
3821
  process.exit(1);
3530
3822
  }
3531
3823
  }
3532
- console.log(chalk15.green("\nLinked successfully."));
3533
- console.log(chalk15.blue("\nRunning initial scan...\n"));
3534
- const result = await scanProject(cwd);
3824
+ console.log(chalk16.green("\nLinked successfully."));
3825
+ console.log(chalk16.blue("\nRunning initial scan...\n"));
3826
+ const result = await scanProject(cwd, folder.id);
3535
3827
  console.log(` Files: ${result.graph.nodes.length}`);
3536
3828
  console.log(` References:${result.graph.edges.length}`);
3537
3829
  console.log(` Orphans: ${result.orphans.length}`);
3538
3830
  console.log(` Skills: ${result.skills.length}`);
3539
3831
  console.log(` Toolings: ${result.toolings.length}`);
3540
3832
  console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
3833
+ console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s) \u2014 ${result.doppler.project}` : "see above"}`);
3541
3834
  console.log(` Plugins: ${result.marketplacePlugins.length} (${result.marketplacePlugins.reduce((n, p) => n + p.skills.length, 0)} skills)`);
3542
3835
  const { error: scanErr } = await supabase.from("claude_folders").update({
3543
3836
  graph_json: result.graph,
@@ -3546,12 +3839,13 @@ Linking "${folder.name}" to this device...
3546
3839
  skills_table_json: result.skills,
3547
3840
  stale_files_json: result.staleFiles,
3548
3841
  env_manifest_json: result.envManifest,
3842
+ doppler_json: result.doppler,
3549
3843
  marketplace_plugins_json: result.marketplacePlugins,
3550
3844
  last_scanned: result.scannedAt,
3551
3845
  data_hash: result.dataHash
3552
3846
  }).eq("id", folder.id);
3553
3847
  if (scanErr) {
3554
- console.error(chalk15.yellow(`Scan upload warning: ${scanErr.message}`));
3848
+ console.error(chalk16.yellow(`Scan upload warning: ${scanErr.message}`));
3555
3849
  }
3556
3850
  await pushToolings(supabase, folder.id, result.toolings);
3557
3851
  const graphPaths = result.graph.nodes.map((n) => n.filePath);
@@ -3566,7 +3860,7 @@ Linking "${folder.name}" to this device...
3566
3860
  last_modified: file.lastModified
3567
3861
  }, { onConflict: "folder_id,file_path" });
3568
3862
  }
3569
- console.log(chalk15.green(` Uploaded ${configFiles.length} config file(s).`));
3863
+ console.log(chalk16.green(` Uploaded ${configFiles.length} config file(s).`));
3570
3864
  }
3571
3865
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
3572
3866
  await saveState({
@@ -3575,40 +3869,40 @@ Linking "${folder.name}" to this device...
3575
3869
  lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
3576
3870
  });
3577
3871
  const projectUrl = `https://www.md4ai.com/project/${folder.id}`;
3578
- console.log(chalk15.green("\nDone! Project linked and scanned."));
3579
- console.log(chalk15.cyan(`
3872
+ console.log(chalk16.green("\nDone! Project linked and scanned."));
3873
+ console.log(chalk16.cyan(`
3580
3874
  ${projectUrl}
3581
3875
  `));
3582
- console.log(chalk15.grey('Run "md4ai scan" to rescan at any time.'));
3876
+ console.log(chalk16.grey('Run "md4ai scan" to rescan at any time.'));
3583
3877
  }
3584
3878
 
3585
3879
  // dist/commands/import-bundle.js
3586
- import { readFile as readFile10, writeFile as writeFile4, mkdir as mkdir3, stat as fsStat } from "node:fs/promises";
3880
+ import { readFile as readFile11, writeFile as writeFile4, mkdir as mkdir3, stat as fsStat } from "node:fs/promises";
3587
3881
  import { dirname as dirname3, resolve as resolve7 } from "node:path";
3588
- import { existsSync as existsSync11 } from "node:fs";
3589
- import chalk16 from "chalk";
3882
+ import { existsSync as existsSync12 } from "node:fs";
3883
+ import chalk17 from "chalk";
3590
3884
  import { confirm as confirm3, input as input6 } from "@inquirer/prompts";
3591
3885
  var MAX_BUNDLE_SIZE_BYTES = 50 * 1024 * 1024;
3592
3886
  async function importBundleCommand(zipPath) {
3593
- if (!existsSync11(zipPath)) {
3594
- console.error(chalk16.red(`File not found: ${zipPath}`));
3887
+ if (!existsSync12(zipPath)) {
3888
+ console.error(chalk17.red(`File not found: ${zipPath}`));
3595
3889
  process.exit(1);
3596
3890
  }
3597
3891
  const fileStat = await fsStat(zipPath);
3598
3892
  if (fileStat.size > MAX_BUNDLE_SIZE_BYTES) {
3599
- console.error(chalk16.red(`Bundle too large (${Math.round(fileStat.size / 1024 / 1024)} MB). Maximum is 50 MB.`));
3893
+ console.error(chalk17.red(`Bundle too large (${Math.round(fileStat.size / 1024 / 1024)} MB). Maximum is 50 MB.`));
3600
3894
  process.exit(1);
3601
3895
  }
3602
3896
  const JSZip = (await import("jszip")).default;
3603
- const zipData = await readFile10(zipPath);
3897
+ const zipData = await readFile11(zipPath);
3604
3898
  const zip = await JSZip.loadAsync(zipData);
3605
3899
  const manifestFile = zip.file("manifest.json");
3606
3900
  if (!manifestFile) {
3607
- console.error(chalk16.red("Invalid bundle: missing manifest.json"));
3901
+ console.error(chalk17.red("Invalid bundle: missing manifest.json"));
3608
3902
  process.exit(1);
3609
3903
  }
3610
3904
  const manifest = JSON.parse(await manifestFile.async("string"));
3611
- console.log(chalk16.blue(`
3905
+ console.log(chalk17.blue(`
3612
3906
  Bundle: ${manifest.folderName}`));
3613
3907
  console.log(` Exported by: ${manifest.ownerEmail}`);
3614
3908
  console.log(` Exported at: ${manifest.exportedAt}`);
@@ -3622,10 +3916,10 @@ Bundle: ${manifest.folderName}`));
3622
3916
  }
3623
3917
  }
3624
3918
  if (files.length === 0) {
3625
- console.log(chalk16.yellow("No Claude config files found in bundle."));
3919
+ console.log(chalk17.yellow("No Claude config files found in bundle."));
3626
3920
  return;
3627
3921
  }
3628
- console.log(chalk16.blue(`
3922
+ console.log(chalk17.blue(`
3629
3923
  Files to extract:`));
3630
3924
  for (const f of files) {
3631
3925
  console.log(` ${f.filePath}`);
@@ -3638,39 +3932,39 @@ Files to extract:`));
3638
3932
  message: `Extract ${files.length} file(s) to ${targetDir}?`
3639
3933
  });
3640
3934
  if (!proceed) {
3641
- console.log(chalk16.yellow("Cancelled."));
3935
+ console.log(chalk17.yellow("Cancelled."));
3642
3936
  return;
3643
3937
  }
3644
3938
  const resolvedTarget = resolve7(targetDir);
3645
3939
  for (const file of files) {
3646
3940
  const fullPath = resolve7(targetDir, file.filePath);
3647
3941
  if (!fullPath.startsWith(resolvedTarget + "/") && fullPath !== resolvedTarget) {
3648
- console.error(chalk16.red(` Blocked path traversal: ${file.filePath}`));
3942
+ console.error(chalk17.red(` Blocked path traversal: ${file.filePath}`));
3649
3943
  continue;
3650
3944
  }
3651
3945
  const dir = dirname3(fullPath);
3652
- if (!existsSync11(dir)) {
3946
+ if (!existsSync12(dir)) {
3653
3947
  await mkdir3(dir, { recursive: true });
3654
3948
  }
3655
3949
  await writeFile4(fullPath, file.content, "utf-8");
3656
- console.log(chalk16.green(` \u2713 ${file.filePath}`));
3950
+ console.log(chalk17.green(` \u2713 ${file.filePath}`));
3657
3951
  }
3658
- console.log(chalk16.green(`
3952
+ console.log(chalk17.green(`
3659
3953
  Done! ${files.length} file(s) extracted to ${targetDir}`));
3660
3954
  }
3661
3955
 
3662
3956
  // dist/commands/admin-update-tool.js
3663
3957
  init_auth();
3664
- import chalk17 from "chalk";
3958
+ import chalk18 from "chalk";
3665
3959
  var VALID_CATEGORIES = ["framework", "runtime", "cli", "mcp", "package", "database", "other"];
3666
3960
  async function adminUpdateToolCommand(options) {
3667
3961
  const { supabase } = await getAuthenticatedClient();
3668
3962
  if (!options.name) {
3669
- console.error(chalk17.red("--name is required."));
3963
+ console.error(chalk18.red("--name is required."));
3670
3964
  process.exit(1);
3671
3965
  }
3672
3966
  if (options.category && !VALID_CATEGORIES.includes(options.category)) {
3673
- console.error(chalk17.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
3967
+ console.error(chalk18.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
3674
3968
  process.exit(1);
3675
3969
  }
3676
3970
  const { data: existing } = await supabase.from("tools_registry").select("id, name, display_name, category").eq("name", options.name).maybeSingle();
@@ -3692,13 +3986,13 @@ async function adminUpdateToolCommand(options) {
3692
3986
  updates.notes = options.notes;
3693
3987
  const { error } = await supabase.from("tools_registry").update(updates).eq("id", existing.id);
3694
3988
  if (error) {
3695
- console.error(chalk17.red(`Failed to update: ${error.message}`));
3989
+ console.error(chalk18.red(`Failed to update: ${error.message}`));
3696
3990
  process.exit(1);
3697
3991
  }
3698
- console.log(chalk17.green(`Updated "${existing.display_name}" in the registry.`));
3992
+ console.log(chalk18.green(`Updated "${existing.display_name}" in the registry.`));
3699
3993
  } else {
3700
3994
  if (!options.display || !options.category) {
3701
- console.error(chalk17.red("New tools require --display and --category."));
3995
+ console.error(chalk18.red("New tools require --display and --category."));
3702
3996
  process.exit(1);
3703
3997
  }
3704
3998
  const { error } = await supabase.from("tools_registry").insert({
@@ -3712,25 +4006,25 @@ async function adminUpdateToolCommand(options) {
3712
4006
  notes: options.notes ?? null
3713
4007
  });
3714
4008
  if (error) {
3715
- console.error(chalk17.red(`Failed to create: ${error.message}`));
4009
+ console.error(chalk18.red(`Failed to create: ${error.message}`));
3716
4010
  process.exit(1);
3717
4011
  }
3718
- console.log(chalk17.green(`Added "${options.display}" to the registry.`));
4012
+ console.log(chalk18.green(`Added "${options.display}" to the registry.`));
3719
4013
  }
3720
4014
  }
3721
4015
 
3722
4016
  // dist/commands/admin-list-tools.js
3723
4017
  init_auth();
3724
- import chalk18 from "chalk";
4018
+ import chalk19 from "chalk";
3725
4019
  async function adminListToolsCommand() {
3726
4020
  const { supabase } = await getAuthenticatedClient();
3727
4021
  const { data: tools, error } = await supabase.from("tools_registry").select("*").order("category").order("display_name");
3728
4022
  if (error) {
3729
- console.error(chalk18.red(`Failed to fetch tools: ${error.message}`));
4023
+ console.error(chalk19.red(`Failed to fetch tools: ${error.message}`));
3730
4024
  process.exit(1);
3731
4025
  }
3732
4026
  if (!tools?.length) {
3733
- console.log(chalk18.yellow("No tools in the registry."));
4027
+ console.log(chalk19.yellow("No tools in the registry."));
3734
4028
  return;
3735
4029
  }
3736
4030
  const nameW = Math.max(16, ...tools.map((t) => t.display_name.length));
@@ -3744,7 +4038,7 @@ async function adminListToolsCommand() {
3744
4038
  "Beta".padEnd(betaW),
3745
4039
  "Last Checked"
3746
4040
  ].join(" ");
3747
- console.log(chalk18.bold(header));
4041
+ console.log(chalk19.bold(header));
3748
4042
  console.log("\u2500".repeat(header.length));
3749
4043
  for (const tool of tools) {
3750
4044
  const lastChecked = tool.updated_at ? formatRelative(new Date(tool.updated_at)) : "\u2014";
@@ -3757,7 +4051,7 @@ async function adminListToolsCommand() {
3757
4051
  ].join(" ");
3758
4052
  console.log(row);
3759
4053
  }
3760
- console.log(chalk18.grey(`
4054
+ console.log(chalk19.grey(`
3761
4055
  ${tools.length} tool(s) in registry.`));
3762
4056
  }
3763
4057
  function formatRelative(date) {
@@ -3775,7 +4069,7 @@ function formatRelative(date) {
3775
4069
 
3776
4070
  // dist/commands/admin-fetch-versions.js
3777
4071
  init_auth();
3778
- import chalk19 from "chalk";
4072
+ import chalk20 from "chalk";
3779
4073
 
3780
4074
  // dist/commands/version-sources.js
3781
4075
  var VERSION_SOURCES = {
@@ -3804,11 +4098,11 @@ async function adminFetchVersionsCommand() {
3804
4098
  const { supabase } = await getAuthenticatedClient();
3805
4099
  const { data: tools, error } = await supabase.from("tools_registry").select("*").order("display_name");
3806
4100
  if (error) {
3807
- console.error(chalk19.red(`Failed to fetch tools: ${error.message}`));
4101
+ console.error(chalk20.red(`Failed to fetch tools: ${error.message}`));
3808
4102
  process.exit(1);
3809
4103
  }
3810
4104
  if (!tools?.length) {
3811
- console.log(chalk19.yellow("No tools in the registry."));
4105
+ console.log(chalk20.yellow("No tools in the registry."));
3812
4106
  }
3813
4107
  const { data: allProjectToolings } = await supabase.from("project_toolings").select("tool_name, detection_source").is("tool_id", null);
3814
4108
  const registeredNames = new Set((tools ?? []).map((t) => t.name));
@@ -3819,7 +4113,7 @@ async function adminFetchVersionsCommand() {
3819
4113
  }
3820
4114
  }
3821
4115
  if (unregisteredNames.size > 0) {
3822
- console.log(chalk19.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
4116
+ console.log(chalk20.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
3823
4117
  `));
3824
4118
  for (const name of unregisteredNames) {
3825
4119
  const displayName = name;
@@ -3837,10 +4131,10 @@ async function adminFetchVersionsCommand() {
3837
4131
  }
3838
4132
  }
3839
4133
  if (!tools?.length) {
3840
- console.log(chalk19.yellow("No tools to fetch."));
4134
+ console.log(chalk20.yellow("No tools to fetch."));
3841
4135
  return;
3842
4136
  }
3843
- console.log(chalk19.bold(`Fetching versions for ${tools.length} tool(s)...
4137
+ console.log(chalk20.bold(`Fetching versions for ${tools.length} tool(s)...
3844
4138
  `));
3845
4139
  const results = await Promise.all(tools.map(async (tool) => {
3846
4140
  const source = VERSION_SOURCES[tool.name] ?? { type: "npm", package: tool.name };
@@ -3879,10 +4173,10 @@ async function adminFetchVersionsCommand() {
3879
4173
  "Beta".padEnd(betaW),
3880
4174
  "Status".padEnd(statusW)
3881
4175
  ].join(" ");
3882
- console.log(chalk19.bold(header));
4176
+ console.log(chalk20.bold(header));
3883
4177
  console.log("\u2500".repeat(header.length));
3884
4178
  for (const result of results) {
3885
- const statusColour = result.status === "updated" ? chalk19.green : result.status === "unchanged" ? chalk19.grey : result.status === "failed" ? chalk19.red : chalk19.yellow;
4179
+ const statusColour = result.status === "updated" ? chalk20.green : result.status === "unchanged" ? chalk20.grey : result.status === "failed" ? chalk20.red : chalk20.yellow;
3886
4180
  const row = [
3887
4181
  result.displayName.padEnd(nameW),
3888
4182
  (result.stable ?? "\u2014").padEnd(stableW),
@@ -3895,8 +4189,8 @@ async function adminFetchVersionsCommand() {
3895
4189
  const unchanged = results.filter((r) => r.status === "unchanged").length;
3896
4190
  const failed = results.filter((r) => r.status === "failed").length;
3897
4191
  const noSource = results.filter((r) => r.status === "no source").length;
3898
- console.log(chalk19.grey(`
3899
- ${results.length} tool(s): `) + chalk19.green(`${updated} updated`) + ", " + chalk19.grey(`${unchanged} unchanged`) + ", " + chalk19.red(`${failed} failed`) + ", " + chalk19.yellow(`${noSource} no source`));
4192
+ console.log(chalk20.grey(`
4193
+ ${results.length} tool(s): `) + chalk20.green(`${updated} updated`) + ", " + chalk20.grey(`${unchanged} unchanged`) + ", " + chalk20.red(`${failed} failed`) + ", " + chalk20.yellow(`${noSource} no source`));
3900
4194
  }
3901
4195
  async function fetchVersions(source) {
3902
4196
  const controller = new AbortController();
@@ -3954,10 +4248,10 @@ async function fetchGitHubVersions(repo, signal) {
3954
4248
  init_mcp_watch();
3955
4249
 
3956
4250
  // dist/commands/init-manifest.js
3957
- import { resolve as resolve8, join as join15, relative as relative4, dirname as dirname4 } from "node:path";
3958
- import { readFile as readFile12, writeFile as writeFile5, mkdir as mkdir4 } from "node:fs/promises";
3959
- import { existsSync as existsSync13 } from "node:fs";
3960
- import chalk21 from "chalk";
4251
+ import { resolve as resolve8, join as join16, relative as relative4, dirname as dirname4 } from "node:path";
4252
+ import { readFile as readFile13, writeFile as writeFile5, mkdir as mkdir4 } from "node:fs/promises";
4253
+ import { existsSync as existsSync14 } from "node:fs";
4254
+ import chalk22 from "chalk";
3961
4255
  var SECRET_PATTERNS = [
3962
4256
  /_KEY$/i,
3963
4257
  /_SECRET$/i,
@@ -3973,8 +4267,8 @@ function isLikelySecret(name) {
3973
4267
  async function discoverEnvFiles(projectRoot) {
3974
4268
  const apps = [];
3975
4269
  for (const envName of [".env", ".env.local", ".env.example"]) {
3976
- const envPath = join15(projectRoot, envName);
3977
- if (existsSync13(envPath)) {
4270
+ const envPath = join16(projectRoot, envName);
4271
+ if (existsSync14(envPath)) {
3978
4272
  const vars = await extractVarNames(envPath);
3979
4273
  if (vars.length > 0) {
3980
4274
  apps.push({ name: "root", envFilePath: envName, vars });
@@ -3984,12 +4278,12 @@ async function discoverEnvFiles(projectRoot) {
3984
4278
  }
3985
4279
  const subdirs = ["web", "cli", "api", "app", "server", "packages"];
3986
4280
  for (const sub of subdirs) {
3987
- const subDir = join15(projectRoot, sub);
3988
- if (!existsSync13(subDir))
4281
+ const subDir = join16(projectRoot, sub);
4282
+ if (!existsSync14(subDir))
3989
4283
  continue;
3990
4284
  for (const envName of [".env.local", ".env", ".env.example"]) {
3991
- const envPath = join15(subDir, envName);
3992
- if (existsSync13(envPath)) {
4285
+ const envPath = join16(subDir, envName);
4286
+ if (existsSync14(envPath)) {
3993
4287
  const vars = await extractVarNames(envPath);
3994
4288
  if (vars.length > 0) {
3995
4289
  apps.push({
@@ -4005,7 +4299,7 @@ async function discoverEnvFiles(projectRoot) {
4005
4299
  return apps;
4006
4300
  }
4007
4301
  async function extractVarNames(envPath) {
4008
- const content = await readFile12(envPath, "utf-8");
4302
+ const content = await readFile13(envPath, "utf-8");
4009
4303
  const vars = [];
4010
4304
  for (const line of content.split("\n")) {
4011
4305
  const trimmed = line.trim();
@@ -4050,38 +4344,38 @@ function generateManifest(apps) {
4050
4344
  }
4051
4345
  async function initManifestCommand() {
4052
4346
  const projectRoot = resolve8(process.cwd());
4053
- const manifestPath = join15(projectRoot, "docs", "reference", "env-manifest.md");
4054
- if (existsSync13(manifestPath)) {
4055
- console.log(chalk21.yellow(`Manifest already exists: ${relative4(projectRoot, manifestPath)}`));
4056
- console.log(chalk21.dim("Edit it directly to make changes."));
4347
+ const manifestPath = join16(projectRoot, "docs", "reference", "env-manifest.md");
4348
+ if (existsSync14(manifestPath)) {
4349
+ console.log(chalk22.yellow(`Manifest already exists: ${relative4(projectRoot, manifestPath)}`));
4350
+ console.log(chalk22.dim("Edit it directly to make changes."));
4057
4351
  return;
4058
4352
  }
4059
- console.log(chalk21.blue("Scanning for .env files...\n"));
4353
+ console.log(chalk22.blue("Scanning for .env files...\n"));
4060
4354
  const apps = await discoverEnvFiles(projectRoot);
4061
4355
  if (apps.length === 0) {
4062
- console.log(chalk21.yellow("No .env files found. Create a manifest manually at:"));
4063
- console.log(chalk21.cyan(` docs/reference/env-manifest.md`));
4356
+ console.log(chalk22.yellow("No .env files found. Create a manifest manually at:"));
4357
+ console.log(chalk22.cyan(` docs/reference/env-manifest.md`));
4064
4358
  return;
4065
4359
  }
4066
4360
  for (const app of apps) {
4067
- console.log(` ${chalk21.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
4361
+ console.log(` ${chalk22.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
4068
4362
  }
4069
4363
  const content = generateManifest(apps);
4070
4364
  const dir = dirname4(manifestPath);
4071
- if (!existsSync13(dir)) {
4365
+ if (!existsSync14(dir)) {
4072
4366
  await mkdir4(dir, { recursive: true });
4073
4367
  }
4074
4368
  await writeFile5(manifestPath, content, "utf-8");
4075
- console.log(chalk21.green(`
4369
+ console.log(chalk22.green(`
4076
4370
  Manifest created: ${relative4(projectRoot, manifestPath)}`));
4077
- console.log(chalk21.dim("Review and edit the file \u2014 it is your source of truth."));
4078
- console.log(chalk21.dim("Then run `md4ai scan` to verify against your environments."));
4371
+ console.log(chalk22.dim("Review and edit the file \u2014 it is your source of truth."));
4372
+ console.log(chalk22.dim("Then run `md4ai scan` to verify against your environments."));
4079
4373
  }
4080
4374
 
4081
4375
  // dist/commands/update.js
4082
4376
  init_check_update();
4083
4377
  init_config();
4084
- import chalk22 from "chalk";
4378
+ import chalk23 from "chalk";
4085
4379
  import { execFileSync as execFileSync6, spawn } from "node:child_process";
4086
4380
  async function fetchLatestVersion() {
4087
4381
  try {
@@ -4130,13 +4424,13 @@ function spawnPostUpdate() {
4130
4424
  }
4131
4425
  async function postUpdateFlow() {
4132
4426
  const { confirm: confirm4 } = await import("@inquirer/prompts");
4133
- console.log(chalk22.green(`
4427
+ console.log(chalk23.green(`
4134
4428
  md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.
4135
4429
  `));
4136
4430
  const creds = await loadCredentials();
4137
4431
  const isLoggedIn = !!creds?.accessToken && Date.now() < creds.expiresAt;
4138
4432
  if (!isLoggedIn) {
4139
- console.log(chalk22.yellow(" You are not logged in.\n"));
4433
+ console.log(chalk23.yellow(" You are not logged in.\n"));
4140
4434
  const wantLogin = await confirm4({
4141
4435
  message: "Log in now?",
4142
4436
  default: true
@@ -4147,7 +4441,7 @@ async function postUpdateFlow() {
4147
4441
  await loginCommand2();
4148
4442
  console.log("");
4149
4443
  } else {
4150
- console.log(chalk22.dim("\nRun md4ai login when you're ready.\n"));
4444
+ console.log(chalk23.dim("\nRun md4ai login when you're ready.\n"));
4151
4445
  return;
4152
4446
  }
4153
4447
  }
@@ -4181,7 +4475,7 @@ async function postUpdateFlow() {
4181
4475
  const { mcpWatchCommand: mcpWatchCommand2 } = await Promise.resolve().then(() => (init_mcp_watch(), mcp_watch_exports));
4182
4476
  await mcpWatchCommand2();
4183
4477
  } else {
4184
- console.log(chalk22.dim("\nYou can start monitoring later with: md4ai mcp-watch\n"));
4478
+ console.log(chalk23.dim("\nYou can start monitoring later with: md4ai mcp-watch\n"));
4185
4479
  }
4186
4480
  }
4187
4481
  async function updateCommand(options) {
@@ -4189,19 +4483,19 @@ async function updateCommand(options) {
4189
4483
  await postUpdateFlow();
4190
4484
  return;
4191
4485
  }
4192
- console.log(chalk22.blue(`
4486
+ console.log(chalk23.blue(`
4193
4487
  Current version: v${CURRENT_VERSION}`));
4194
- console.log(chalk22.dim(" Checking for updates...\n"));
4488
+ console.log(chalk23.dim(" Checking for updates...\n"));
4195
4489
  const latest = await fetchLatestVersion();
4196
4490
  if (!latest) {
4197
- console.log(chalk22.yellow(" Could not reach the npm registry. Check your internet connection."));
4491
+ console.log(chalk23.yellow(" Could not reach the npm registry. Check your internet connection."));
4198
4492
  process.exit(1);
4199
4493
  }
4200
4494
  if (!isNewer2(latest, CURRENT_VERSION)) {
4201
4495
  await postUpdateFlow();
4202
4496
  return;
4203
4497
  }
4204
- console.log(chalk22.white(" Update available: ") + chalk22.dim(`v${CURRENT_VERSION}`) + chalk22.white(" \u2192 ") + chalk22.green.bold(`v${latest}
4498
+ console.log(chalk23.white(" Update available: ") + chalk23.dim(`v${CURRENT_VERSION}`) + chalk23.white(" \u2192 ") + chalk23.green.bold(`v${latest}
4205
4499
  `));
4206
4500
  const { confirm: confirm4 } = await import("@inquirer/prompts");
4207
4501
  const wantUpdate = await confirm4({
@@ -4209,17 +4503,17 @@ async function updateCommand(options) {
4209
4503
  default: true
4210
4504
  });
4211
4505
  if (!wantUpdate) {
4212
- console.log(chalk22.dim("\nUpdate skipped.\n"));
4506
+ console.log(chalk23.dim("\nUpdate skipped.\n"));
4213
4507
  return;
4214
4508
  }
4215
- console.log(chalk22.blue("\n Installing...\n"));
4509
+ console.log(chalk23.blue("\n Installing...\n"));
4216
4510
  const ok = runNpmInstall();
4217
4511
  if (!ok) {
4218
- console.log(chalk22.red("\n npm install failed. Try running manually:"));
4219
- console.log(chalk22.cyan(" npm install -g md4ai\n"));
4512
+ console.log(chalk23.red("\n npm install failed. Try running manually:"));
4513
+ console.log(chalk23.cyan(" npm install -g md4ai\n"));
4220
4514
  process.exit(1);
4221
4515
  }
4222
- console.log(chalk22.green("\n Updated successfully.\n"));
4516
+ console.log(chalk23.green("\n Updated successfully.\n"));
4223
4517
  spawnPostUpdate();
4224
4518
  }
4225
4519
 
@@ -4227,7 +4521,7 @@ async function updateCommand(options) {
4227
4521
  init_check_update();
4228
4522
  init_map();
4229
4523
  init_mcp_watch();
4230
- import chalk23 from "chalk";
4524
+ import chalk24 from "chalk";
4231
4525
  import { resolve as resolve9 } from "node:path";
4232
4526
  async function fetchLatestVersion2() {
4233
4527
  try {
@@ -4259,13 +4553,13 @@ function isNewer3(a, b) {
4259
4553
  async function startCommand() {
4260
4554
  const projectRoot = resolve9(process.cwd());
4261
4555
  console.log("");
4262
- console.log(chalk23.bold.cyan(` MD4AI v${CURRENT_VERSION}`));
4263
- console.log(chalk23.dim(` ${projectRoot}`));
4556
+ console.log(chalk24.bold.cyan(` MD4AI v${CURRENT_VERSION}`));
4557
+ console.log(chalk24.dim(` ${projectRoot}`));
4264
4558
  console.log("");
4265
- console.log(chalk23.blue(" \u2460 Checking for updates..."));
4559
+ console.log(chalk24.blue(" \u2460 Checking for updates..."));
4266
4560
  const latest = await fetchLatestVersion2();
4267
4561
  if (latest && isNewer3(latest, CURRENT_VERSION)) {
4268
- console.log(chalk23.yellow(` Update available: v${CURRENT_VERSION} \u2192 v${latest}`));
4562
+ console.log(chalk24.yellow(` Update available: v${CURRENT_VERSION} \u2192 v${latest}`));
4269
4563
  if (process.stdin.isTTY) {
4270
4564
  const { confirm: confirm4 } = await import("@inquirer/prompts");
4271
4565
  const wantUpdate = await confirm4({
@@ -4274,61 +4568,179 @@ async function startCommand() {
4274
4568
  });
4275
4569
  if (wantUpdate) {
4276
4570
  const { execFileSync: execFileSync7 } = await import("node:child_process");
4277
- console.log(chalk23.blue("\n Installing...\n"));
4571
+ console.log(chalk24.blue("\n Installing...\n"));
4278
4572
  try {
4279
4573
  execFileSync7("npm", ["install", "-g", "md4ai"], {
4280
4574
  stdio: "inherit",
4281
4575
  timeout: 6e4
4282
4576
  });
4283
- console.log(chalk23.green(" Updated successfully."));
4284
- console.log(chalk23.dim(" Restarting with the new version...\n"));
4577
+ console.log(chalk24.green(" Updated successfully."));
4578
+ console.log(chalk24.dim(" Restarting with the new version...\n"));
4285
4579
  const { spawn: spawn2 } = await import("node:child_process");
4286
4580
  const child = spawn2("md4ai", ["start"], { stdio: "inherit", shell: false });
4287
4581
  child.on("exit", (code) => process.exit(code ?? 0));
4288
4582
  return;
4289
4583
  } catch {
4290
- console.log(chalk23.yellow(" Update failed \u2014 continuing with current version.\n"));
4584
+ console.log(chalk24.yellow(" Update failed \u2014 continuing with current version.\n"));
4291
4585
  }
4292
4586
  } else {
4293
4587
  console.log("");
4294
4588
  }
4295
4589
  } else {
4296
- console.log(chalk23.dim(" Run md4ai update to install it.\n"));
4590
+ console.log(chalk24.dim(" Run md4ai update to install it.\n"));
4297
4591
  }
4298
4592
  } else if (latest) {
4299
- console.log(chalk23.green(" You're on the latest version.\n"));
4593
+ console.log(chalk24.green(" You're on the latest version.\n"));
4300
4594
  } else {
4301
- console.log(chalk23.dim(" Could not reach npm registry \u2014 skipping.\n"));
4595
+ console.log(chalk24.dim(" Could not reach npm registry \u2014 skipping.\n"));
4302
4596
  }
4303
- console.log(chalk23.blue(" \u2461 Scanning project files..."));
4597
+ console.log(chalk24.blue(" \u2461 Scanning project files..."));
4304
4598
  console.log("");
4305
4599
  await mapCommand(projectRoot, {});
4306
4600
  if (!process.stdin.isTTY) {
4307
- console.log(chalk23.dim("\n Non-interactive mode \u2014 skipping MCP monitor."));
4601
+ console.log(chalk24.dim("\n Non-interactive mode \u2014 skipping MCP monitor."));
4308
4602
  return;
4309
4603
  }
4310
4604
  console.log("");
4311
- console.log(chalk23.blue(" \u2462 Starting MCP monitor..."));
4605
+ console.log(chalk24.blue(" \u2462 Starting MCP monitor..."));
4312
4606
  console.log("");
4313
4607
  await mcpWatchCommand();
4314
4608
  }
4315
4609
 
4316
4610
  // dist/commands/config.js
4317
4611
  init_config();
4318
- import chalk24 from "chalk";
4319
- var ALLOWED_KEYS = ["vercel-token"];
4612
+ import chalk25 from "chalk";
4613
+ var ALLOWED_KEYS = ["vercel-token", "doppler-token"];
4320
4614
  var KEY_MAP = {
4321
- "vercel-token": "vercelToken"
4615
+ "vercel-token": "vercelToken",
4616
+ "doppler-token": "dopplerToken"
4322
4617
  };
4323
4618
  async function configSetCommand(key, value) {
4324
4619
  if (!ALLOWED_KEYS.includes(key)) {
4325
- console.error(chalk24.red(`Unknown config key: ${key}`));
4620
+ console.error(chalk25.red(`Unknown config key: ${key}`));
4326
4621
  console.log(` Allowed keys: ${ALLOWED_KEYS.join(", ")}`);
4327
4622
  process.exit(1);
4328
4623
  }
4329
4624
  const credKey = KEY_MAP[key];
4330
4625
  await mergeCredentials({ [credKey]: value });
4331
- console.log(chalk24.green(`Saved ${key}.`));
4626
+ console.log(chalk25.green(`Saved ${key}.`));
4627
+ }
4628
+
4629
+ // dist/commands/doppler.js
4630
+ init_config();
4631
+ init_api();
4632
+ init_auth();
4633
+ init_dist();
4634
+ import chalk26 from "chalk";
4635
+ import { input as input7 } from "@inquirer/prompts";
4636
+ async function dopplerConnectCommand() {
4637
+ const token = await input7({
4638
+ message: "Enter your Doppler service account token:",
4639
+ transformer: (val) => val.replace(/./g, "*"),
4640
+ validate: (val) => val.length > 0 || "Token is required"
4641
+ });
4642
+ console.log(chalk26.dim(" Validating token..."));
4643
+ const { valid, projectCount } = await validateDopplerToken(token);
4644
+ if (!valid) {
4645
+ console.error(chalk26.red("Token is invalid or has expired."));
4646
+ console.log(chalk26.yellow("Generate a service account token at https://dashboard.doppler.com"));
4647
+ process.exit(1);
4648
+ }
4649
+ await mergeCredentials({ dopplerToken: token });
4650
+ console.log(chalk26.green(`Token validated \u2014 ${projectCount} project${projectCount !== 1 ? "s" : ""} accessible.`));
4651
+ console.log(chalk26.green("Saved to ~/.md4ai/credentials.json"));
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.'));
4697
+ }
4698
+ async function dopplerDisconnectCommand() {
4699
+ const creds = await loadCredentials();
4700
+ if (!creds?.dopplerToken) {
4701
+ console.log(chalk26.yellow("No Doppler token configured."));
4702
+ return;
4703
+ }
4704
+ const { dopplerToken: _, ...rest } = creds;
4705
+ const { writeFile: writeFile6, chmod: chmod2 } = await import("node:fs/promises");
4706
+ const { join: join17 } = await import("node:path");
4707
+ const { homedir: homedir10 } = await import("node:os");
4708
+ const credPath = join17(homedir10(), ".md4ai", "credentials.json");
4709
+ await writeFile6(credPath, JSON.stringify(rest, null, 2), "utf-8");
4710
+ await chmod2(credPath, 384);
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
+ }
4731
+ }
4732
+ async function dopplerSetProjectCommand(slug) {
4733
+ const state = await loadState();
4734
+ const folderId = state.lastFolderId;
4735
+ if (!folderId) {
4736
+ console.error(chalk26.red('No project linked yet. Run "md4ai scan" first to link a project.'));
4737
+ process.exit(1);
4738
+ }
4739
+ const dopplerProjects = state.dopplerProjects ?? {};
4740
+ dopplerProjects[folderId] = slug;
4741
+ await saveState({ dopplerProjects });
4742
+ console.log(chalk26.green(`Doppler project "${slug}" linked to folder ${folderId.slice(0, 8)}...`));
4743
+ console.log(chalk26.dim("This override will be used instead of doppler.yaml."));
4332
4744
  }
4333
4745
 
4334
4746
  // dist/index.js
@@ -4357,6 +4769,10 @@ program.command("mcp-watch").description("Monitor MCP server status on this devi
4357
4769
  program.command("init-manifest").description("Scaffold a starter env-manifest.md from detected .env files").action(initManifestCommand);
4358
4770
  var config = program.command("config").description("Manage CLI configuration");
4359
4771
  config.command("set <key> <value>").description("Set a configuration value (e.g. vercel-token)").action(configSetCommand);
4772
+ var doppler = program.command("doppler").description("Manage Doppler integration");
4773
+ doppler.command("connect").description("Configure Doppler token for secrets scanning").action(dopplerConnectCommand);
4774
+ doppler.command("disconnect").description("Remove stored Doppler token").action(dopplerDisconnectCommand);
4775
+ doppler.command("set-project <slug>").description("Override Doppler project slug for the current linked project").action(dopplerSetProjectCommand);
4360
4776
  var admin = program.command("admin").description("Admin commands for managing the tools registry");
4361
4777
  admin.command("update-tool").description("Add or update a tool in the master registry").requiredOption("--name <name>", "Canonical tool name (e.g. next, playwright)").option("--display <display>", 'Human-friendly display name (e.g. "Next.js")').option("--category <category>", "Tool category (framework|runtime|cli|mcp|package|database|other)").option("--stable <version>", "Latest stable version").option("--beta <version>", "Latest beta/RC version").option("--source <url>", "Source of truth URL for checking versions").option("--install <url>", "Download/install link").option("--notes <text>", "Compatibility notes or warnings").action(adminUpdateToolCommand);
4362
4778
  admin.command("list-tools").description("List all tools in the master registry").action(adminListToolsCommand);