md4ai 0.10.4 → 0.11.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 +570 -267
  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.11.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,187 @@ 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
+ var DopplerApiError, MAX_RETRIES;
1717
+ var init_api = __esm({
1718
+ "dist/doppler/api.js"() {
1719
+ "use strict";
1720
+ DopplerApiError = class extends Error {
1721
+ status;
1722
+ isAuthError;
1723
+ constructor(message, status, isAuthError) {
1724
+ super(message);
1725
+ this.status = status;
1726
+ this.isAuthError = isAuthError;
1727
+ this.name = "DopplerApiError";
1728
+ }
1729
+ };
1730
+ MAX_RETRIES = 2;
1731
+ }
1732
+ });
1733
+
1734
+ // dist/scanner/doppler-scanner.js
1735
+ import { readFile as readFile9 } from "node:fs/promises";
1736
+ import { join as join11 } from "node:path";
1737
+ import { existsSync as existsSync6 } from "node:fs";
1738
+ import chalk10 from "chalk";
1739
+ async function parseDopplerYaml(projectRoot) {
1740
+ const yamlPath = join11(projectRoot, "doppler.yaml");
1741
+ if (!existsSync6(yamlPath))
1742
+ return null;
1743
+ try {
1744
+ const content = await readFile9(yamlPath, "utf-8");
1745
+ const match = content.match(/^project:\s*["']?([a-z0-9_-]+)["']?\s*$/m);
1746
+ return match?.[1] ?? null;
1747
+ } catch {
1748
+ return null;
1749
+ }
1750
+ }
1751
+ async function scanDoppler(projectRoot, folderId) {
1752
+ const tokenResult = await resolveDopplerToken();
1753
+ if (!tokenResult) {
1754
+ return null;
1755
+ }
1756
+ let projectSlug = await parseDopplerYaml(projectRoot);
1757
+ let matchedVia = "doppler-yaml";
1758
+ if (!projectSlug && folderId) {
1759
+ const state = await loadState();
1760
+ projectSlug = state.dopplerProjects?.[folderId] ?? null;
1761
+ if (projectSlug)
1762
+ matchedVia = "manual";
1763
+ }
1764
+ if (!projectSlug) {
1765
+ return null;
1766
+ }
1767
+ console.log(chalk10.dim(` Doppler: checking project "${projectSlug}" (via ${matchedVia})...`));
1768
+ try {
1769
+ const apiConfigs = await fetchDopplerConfigs(projectSlug, tokenResult.token);
1770
+ const CONCURRENCY = 4;
1771
+ const configs = [];
1772
+ for (let i = 0; i < apiConfigs.length; i += CONCURRENCY) {
1773
+ const batch = apiConfigs.slice(i, i + CONCURRENCY);
1774
+ const results = await Promise.all(batch.map(async (cfg) => {
1775
+ try {
1776
+ const secretNames = await fetchDopplerSecretNames(projectSlug, cfg.name, tokenResult.token);
1777
+ return {
1778
+ name: cfg.name,
1779
+ environment: cfg.environment,
1780
+ secretNames
1781
+ };
1782
+ } catch (err) {
1783
+ if (err instanceof DopplerApiError && err.status === 429) {
1784
+ console.log(chalk10.yellow(` Doppler: rate limited on config "${cfg.name}", skipping remaining.`));
1785
+ return null;
1786
+ }
1787
+ console.log(chalk10.dim(` Doppler: failed to fetch config "${cfg.name}": ${err instanceof Error ? err.message : String(err)}`));
1788
+ return null;
1789
+ }
1790
+ }));
1791
+ for (const r of results) {
1792
+ if (r)
1793
+ configs.push(r);
1794
+ }
1795
+ }
1796
+ console.log(chalk10.dim(` Doppler: ${configs.length} config(s), ${configs.reduce((n, c) => n + c.secretNames.length, 0)} total secrets`));
1797
+ return {
1798
+ project: projectSlug,
1799
+ configs,
1800
+ matchedVia,
1801
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
1802
+ };
1803
+ } catch (err) {
1804
+ if (err instanceof DopplerApiError) {
1805
+ if (err.isAuthError) {
1806
+ console.log(chalk10.yellow(' Doppler: token invalid or expired. Run "md4ai doppler connect" to reconfigure.'));
1807
+ } else {
1808
+ console.log(chalk10.yellow(` Doppler: API error \u2014 ${err.message}`));
1809
+ }
1810
+ } else {
1811
+ console.log(chalk10.yellow(` Doppler: ${err instanceof Error ? err.message : String(err)}`));
1812
+ }
1813
+ return null;
1814
+ }
1815
+ }
1816
+ var init_doppler_scanner = __esm({
1817
+ "dist/scanner/doppler-scanner.js"() {
1818
+ "use strict";
1819
+ init_auth3();
1820
+ init_api();
1821
+ init_config();
1822
+ }
1823
+ });
1824
+
1594
1825
  // dist/scanner/index.js
1595
1826
  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";
1827
+ import { join as join12, relative as relative3 } from "node:path";
1828
+ import { existsSync as existsSync7 } from "node:fs";
1598
1829
  import { homedir as homedir7 } from "node:os";
1599
1830
  import { createHash } from "node:crypto";
1600
- async function scanProject(projectRoot) {
1831
+ async function scanProject(projectRoot, folderId) {
1601
1832
  const allFiles = await discoverFiles(projectRoot);
1602
1833
  const rootFiles = identifyRoots(allFiles, projectRoot);
1603
1834
  const allRefs = [];
1604
1835
  const allBrokenRefs = [];
1605
1836
  for (const file of allFiles) {
1606
- const fullPath = file.startsWith("/") ? file : join11(projectRoot, file);
1837
+ const fullPath = file.startsWith("/") ? file : join12(projectRoot, file);
1607
1838
  try {
1608
1839
  const { refs, brokenRefs: brokenRefs2 } = await parseFileReferences(fullPath, projectRoot);
1609
1840
  allRefs.push(...refs);
@@ -1618,6 +1849,12 @@ async function scanProject(projectRoot) {
1618
1849
  const toolings = await detectToolings(projectRoot);
1619
1850
  const envManifest = await scanEnvManifest(projectRoot);
1620
1851
  const marketplacePlugins = await scanMarketplacePlugins();
1852
+ const doppler2 = await scanDoppler(projectRoot, folderId);
1853
+ if (doppler2 && envManifest) {
1854
+ const dopplerDrift = computeDopplerDrift(doppler2, envManifest);
1855
+ envManifest.drift.dopplerNotDeployed = dopplerDrift.dopplerNotDeployed;
1856
+ envManifest.drift.notInDoppler = dopplerDrift.notInDoppler;
1857
+ }
1621
1858
  const depthMap = /* @__PURE__ */ new Map();
1622
1859
  const queue = [...rootFiles];
1623
1860
  for (const r of queue)
@@ -1638,7 +1875,7 @@ async function scanProject(projectRoot) {
1638
1875
  depth: depthMap.get(br.from) ?? 999
1639
1876
  }));
1640
1877
  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 });
1878
+ const scanData = JSON.stringify({ graph, orphans, brokenRefs, skills, staleFiles, toolings, envManifest, marketplacePlugins, doppler: doppler2 });
1642
1879
  const dataHash = createHash("sha256").update(scanData).digest("hex");
1643
1880
  return {
1644
1881
  graph,
@@ -1648,6 +1885,7 @@ async function scanProject(projectRoot) {
1648
1885
  staleFiles,
1649
1886
  toolings,
1650
1887
  envManifest,
1888
+ doppler: doppler2,
1651
1889
  marketplacePlugins,
1652
1890
  scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
1653
1891
  dataHash
@@ -1655,18 +1893,18 @@ async function scanProject(projectRoot) {
1655
1893
  }
1656
1894
  async function discoverFiles(projectRoot) {
1657
1895
  const files = [];
1658
- const claudeDir = join11(projectRoot, ".claude");
1659
- if (existsSync6(claudeDir)) {
1896
+ const claudeDir = join12(projectRoot, ".claude");
1897
+ if (existsSync7(claudeDir)) {
1660
1898
  await walkDir(claudeDir, projectRoot, files);
1661
1899
  }
1662
- if (existsSync6(join11(projectRoot, "CLAUDE.md"))) {
1900
+ if (existsSync7(join12(projectRoot, "CLAUDE.md"))) {
1663
1901
  files.push("CLAUDE.md");
1664
1902
  }
1665
- if (existsSync6(join11(projectRoot, "skills.md"))) {
1903
+ if (existsSync7(join12(projectRoot, "skills.md"))) {
1666
1904
  files.push("skills.md");
1667
1905
  }
1668
- const plansDir = join11(projectRoot, "docs", "plans");
1669
- if (existsSync6(plansDir)) {
1906
+ const plansDir = join12(projectRoot, "docs", "plans");
1907
+ if (existsSync7(plansDir)) {
1670
1908
  await walkDir(plansDir, projectRoot, files);
1671
1909
  }
1672
1910
  return [...new Set(files)];
@@ -1674,7 +1912,7 @@ async function discoverFiles(projectRoot) {
1674
1912
  async function walkDir(dir, projectRoot, files) {
1675
1913
  const entries = await readdir3(dir, { withFileTypes: true });
1676
1914
  for (const entry of entries) {
1677
- const fullPath = join11(dir, entry.name);
1915
+ const fullPath = join12(dir, entry.name);
1678
1916
  if (entry.isDirectory()) {
1679
1917
  if (["node_modules", ".git", ".turbo", "cache", "session-env"].includes(entry.name))
1680
1918
  continue;
@@ -1694,15 +1932,15 @@ function identifyRoots(allFiles, projectRoot) {
1694
1932
  }
1695
1933
  for (const globalFile of GLOBAL_ROOT_FILES) {
1696
1934
  const expanded = globalFile.replace("~", homedir7());
1697
- if (existsSync6(expanded)) {
1935
+ if (existsSync7(expanded)) {
1698
1936
  roots.push(globalFile);
1699
1937
  }
1700
1938
  }
1701
1939
  return roots;
1702
1940
  }
1703
1941
  async function readClaudeConfigFiles(projectRoot, graphFilePaths) {
1704
- const { readFile: readFile13, stat: stat2 } = await import("node:fs/promises");
1705
- const { existsSync: existsSync14 } = await import("node:fs");
1942
+ const { readFile: readFile14, stat: stat2 } = await import("node:fs/promises");
1943
+ const { existsSync: existsSync15 } = await import("node:fs");
1706
1944
  const filePaths = graphFilePaths ?? await discoverFiles(projectRoot);
1707
1945
  const files = [];
1708
1946
  const seen = /* @__PURE__ */ new Set();
@@ -1712,11 +1950,11 @@ async function readClaudeConfigFiles(projectRoot, graphFilePaths) {
1712
1950
  if (relPath.startsWith("~") || relPath.startsWith("/"))
1713
1951
  continue;
1714
1952
  seen.add(relPath);
1715
- const fullPath = join11(projectRoot, relPath);
1716
- if (!existsSync14(fullPath))
1953
+ const fullPath = join12(projectRoot, relPath);
1954
+ if (!existsSync15(fullPath))
1717
1955
  continue;
1718
1956
  try {
1719
- const content = await readFile13(fullPath, "utf-8");
1957
+ const content = await readFile14(fullPath, "utf-8");
1720
1958
  const fileStat = await stat2(fullPath);
1721
1959
  const lastMod = getGitLastModified(fullPath, projectRoot);
1722
1960
  files.push({ filePath: relPath, content, sizeBytes: fileStat.size, lastModified: lastMod });
@@ -1729,7 +1967,7 @@ function detectStaleFiles(allFiles, projectRoot) {
1729
1967
  const stale = [];
1730
1968
  const now = Date.now();
1731
1969
  for (const file of allFiles) {
1732
- const fullPath = join11(projectRoot, file);
1970
+ const fullPath = join12(projectRoot, file);
1733
1971
  const lastMod = getGitLastModified(fullPath, projectRoot);
1734
1972
  if (lastMod) {
1735
1973
  const days = Math.floor((now - new Date(lastMod).getTime()) / (1e3 * 60 * 60 * 24));
@@ -1752,6 +1990,7 @@ var init_scanner = __esm({
1752
1990
  init_tooling_detector();
1753
1991
  init_env_manifest_scanner();
1754
1992
  init_marketplace_scanner();
1993
+ init_doppler_scanner();
1755
1994
  }
1756
1995
  });
1757
1996
 
@@ -1896,7 +2135,7 @@ var init_push_toolings = __esm({
1896
2135
  });
1897
2136
 
1898
2137
  // dist/commands/push-health-results.js
1899
- import chalk10 from "chalk";
2138
+ import chalk11 from "chalk";
1900
2139
  async function pushHealthResults(supabase, folderId, manifest) {
1901
2140
  const { data: checks } = await supabase.from("env_health_checks").select("id, check_type, check_config").eq("folder_id", folderId).eq("enabled", true);
1902
2141
  if (!checks?.length)
@@ -1964,9 +2203,9 @@ async function pushHealthResults(supabase, folderId, manifest) {
1964
2203
  if (results.length > 0) {
1965
2204
  const { error } = await supabase.from("env_health_results").insert(results);
1966
2205
  if (error) {
1967
- console.error(chalk10.yellow(`Health results warning: ${error.message}`));
2206
+ console.error(chalk11.yellow(`Health results warning: ${error.message}`));
1968
2207
  } else {
1969
- console.log(chalk10.green(` Updated ${results.length} health check result(s).`));
2208
+ console.log(chalk11.green(` Updated ${results.length} health check result(s).`));
1970
2209
  const checkIds = results.map((r) => r.check_id);
1971
2210
  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
2211
  if (allResults) {
@@ -2037,17 +2276,17 @@ __export(map_exports, {
2037
2276
  });
2038
2277
  import { resolve as resolve4, basename } from "node:path";
2039
2278
  import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
2040
- import { existsSync as existsSync7 } from "node:fs";
2041
- import chalk11 from "chalk";
2279
+ import { existsSync as existsSync8 } from "node:fs";
2280
+ import chalk12 from "chalk";
2042
2281
  import { select as select3, input as input5, confirm as confirm2 } from "@inquirer/prompts";
2043
2282
  async function mapCommand(path, options) {
2044
2283
  await checkForUpdate();
2045
2284
  const projectRoot = resolve4(path ?? process.cwd());
2046
- if (!existsSync7(projectRoot)) {
2047
- console.error(chalk11.red(`Path not found: ${projectRoot}`));
2285
+ if (!existsSync8(projectRoot)) {
2286
+ console.error(chalk12.red(`Path not found: ${projectRoot}`));
2048
2287
  process.exit(1);
2049
2288
  }
2050
- console.log(chalk11.blue(`Scanning: ${projectRoot}
2289
+ console.log(chalk12.blue(`Scanning: ${projectRoot}
2051
2290
  `));
2052
2291
  const result = await scanProject(projectRoot);
2053
2292
  console.log(` Files found: ${result.graph.nodes.length}`);
@@ -2058,26 +2297,27 @@ async function mapCommand(path, options) {
2058
2297
  console.log(` Skills: ${result.skills.length}`);
2059
2298
  console.log(` Toolings: ${result.toolings.length}`);
2060
2299
  console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
2300
+ console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s)` : "not configured"}`);
2061
2301
  console.log(` Plugins: ${result.marketplacePlugins.length} (${result.marketplacePlugins.reduce((n, p) => n + p.skills.length, 0)} skills)`);
2062
2302
  console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
2063
2303
  if (result.brokenRefs.length > 0) {
2064
- console.log(chalk11.red(`
2304
+ console.log(chalk12.red(`
2065
2305
  Warning: ${result.brokenRefs.length} broken reference(s) found:`));
2066
2306
  for (const br of result.brokenRefs.slice(0, 5)) {
2067
- console.log(chalk11.red(` ${br.from} -> ${br.to}`));
2307
+ console.log(chalk12.red(` ${br.from} -> ${br.to}`));
2068
2308
  }
2069
2309
  if (result.brokenRefs.length > 5) {
2070
- console.log(chalk11.red(` ... and ${result.brokenRefs.length - 5} more`));
2310
+ console.log(chalk12.red(` ... and ${result.brokenRefs.length - 5} more`));
2071
2311
  }
2072
2312
  }
2073
2313
  const outputDir = resolve4(projectRoot, "output");
2074
- if (!existsSync7(outputDir)) {
2314
+ if (!existsSync8(outputDir)) {
2075
2315
  await mkdir2(outputDir, { recursive: true });
2076
2316
  }
2077
2317
  const htmlPath = resolve4(outputDir, "index.html");
2078
2318
  const html = generateOfflineHtml(result, projectRoot);
2079
2319
  await writeFile2(htmlPath, html, "utf-8");
2080
- console.log(chalk11.green(`
2320
+ console.log(chalk12.green(`
2081
2321
  Local preview: ${htmlPath}`));
2082
2322
  if (!options.offline) {
2083
2323
  try {
@@ -2088,7 +2328,7 @@ Local preview: ${htmlPath}`));
2088
2328
  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
2329
  if (proposedFiles?.length && process.stdin.isTTY) {
2090
2330
  const { checkbox } = await import("@inquirer/prompts");
2091
- console.log(chalk11.yellow(`
2331
+ console.log(chalk12.yellow(`
2092
2332
  ${proposedFiles.length} file(s) proposed for deletion:
2093
2333
  `));
2094
2334
  const toDelete = await checkbox({
@@ -2102,16 +2342,16 @@ ${proposedFiles.length} file(s) proposed for deletion:
2102
2342
  for (const file of toDelete) {
2103
2343
  const fullPath = resolve4(projectRoot, file.file_path);
2104
2344
  if (!fullPath.startsWith(resolvedRoot + "/")) {
2105
- console.error(chalk11.red(` Blocked path traversal: ${file.file_path}`));
2345
+ console.error(chalk12.red(` Blocked path traversal: ${file.file_path}`));
2106
2346
  continue;
2107
2347
  }
2108
2348
  try {
2109
2349
  const { unlink } = await import("node:fs/promises");
2110
2350
  await unlink(fullPath);
2111
2351
  await supabase.from("folder_files").delete().eq("id", file.id);
2112
- console.log(chalk11.green(` Deleted: ${file.file_path}`));
2352
+ console.log(chalk12.green(` Deleted: ${file.file_path}`));
2113
2353
  } catch (err) {
2114
- console.error(chalk11.red(` Failed to delete ${file.file_path}: ${err instanceof Error ? err.message : String(err)}`));
2354
+ console.error(chalk12.red(` Failed to delete ${file.file_path}: ${err instanceof Error ? err.message : String(err)}`));
2115
2355
  }
2116
2356
  }
2117
2357
  const keptIds = proposedFiles.filter((f) => !toDelete.some((d) => d.id === f.id)).map((f) => f.id);
@@ -2119,11 +2359,11 @@ ${proposedFiles.length} file(s) proposed for deletion:
2119
2359
  for (const id of keptIds) {
2120
2360
  await supabase.from("folder_files").update({ proposed_for_deletion: false, proposed_at: null, proposed_by: null }).eq("id", id);
2121
2361
  }
2122
- console.log(chalk11.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
2362
+ console.log(chalk12.cyan(` Kept ${keptIds.length} file(s) \u2014 proposals cleared.`));
2123
2363
  }
2124
2364
  console.log("");
2125
2365
  } else if (proposedFiles?.length) {
2126
- console.log(chalk11.dim(` ${proposedFiles.length} file(s) proposed for deletion \u2014 skipped (non-interactive mode).`));
2366
+ console.log(chalk12.dim(` ${proposedFiles.length} file(s) proposed for deletion \u2014 skipped (non-interactive mode).`));
2127
2367
  }
2128
2368
  let storedManifest = null;
2129
2369
  if (!result.envManifest) {
@@ -2145,9 +2385,12 @@ ${proposedFiles.length} file(s) proposed for deletion:
2145
2385
  if (result.envManifest) {
2146
2386
  updatePayload.env_manifest_json = result.envManifest;
2147
2387
  }
2388
+ if (result.doppler) {
2389
+ updatePayload.doppler_json = result.doppler;
2390
+ }
2148
2391
  const { error } = await supabase.from("claude_folders").update(updatePayload).eq("id", folder_id);
2149
2392
  if (error) {
2150
- console.error(chalk11.yellow(`Sync warning: ${error.message}`));
2393
+ console.error(chalk12.yellow(`Sync warning: ${error.message}`));
2151
2394
  } else {
2152
2395
  await pushToolings(supabase, folder_id, result.toolings);
2153
2396
  const manifestForHealth = result.envManifest ?? storedManifest;
@@ -2160,8 +2403,8 @@ ${proposedFiles.length} file(s) proposed for deletion:
2160
2403
  lastDeviceName: device_name,
2161
2404
  lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
2162
2405
  });
2163
- console.log(chalk11.green("Synced to Supabase."));
2164
- console.log(chalk11.cyan(`
2406
+ console.log(chalk12.green("Synced to Supabase."));
2407
+ console.log(chalk12.cyan(`
2165
2408
  https://www.md4ai.com/project/${folder_id}
2166
2409
  `));
2167
2410
  const graphPaths = result.graph.nodes.map((n) => n.filePath);
@@ -2176,18 +2419,18 @@ ${proposedFiles.length} file(s) proposed for deletion:
2176
2419
  last_modified: file.lastModified
2177
2420
  }, { onConflict: "folder_id,file_path" });
2178
2421
  }
2179
- console.log(chalk11.green(` Uploaded ${configFiles.length} config file(s).`));
2422
+ console.log(chalk12.green(` Uploaded ${configFiles.length} config file(s).`));
2180
2423
  }
2181
2424
  }
2182
2425
  } else {
2183
- console.log(chalk11.yellow("\nThis folder is not linked to a project on your dashboard."));
2426
+ console.log(chalk12.yellow("\nThis folder is not linked to a project on your dashboard."));
2184
2427
  if (!process.stdin.isTTY) {
2185
- console.log(chalk11.dim("Non-interactive mode \u2014 skipping link prompt."));
2428
+ console.log(chalk12.dim("Non-interactive mode \u2014 skipping link prompt."));
2186
2429
  return;
2187
2430
  }
2188
2431
  const shouldLink = await confirm2({ message: "Would you like to link it now?" });
2189
2432
  if (!shouldLink) {
2190
- console.log(chalk11.dim("Skipped \u2014 local preview still generated."));
2433
+ console.log(chalk12.dim("Skipped \u2014 local preview still generated."));
2191
2434
  } else {
2192
2435
  const { supabase: sb, userId } = await getAuthenticatedClient();
2193
2436
  const { data: folders } = await sb.from("claude_folders").select("id, name").order("name");
@@ -2207,11 +2450,11 @@ ${proposedFiles.length} file(s) proposed for deletion:
2207
2450
  });
2208
2451
  const { data: newFolder, error: createErr } = await sb.from("claude_folders").insert({ user_id: userId, name: projectName }).select("id").single();
2209
2452
  if (createErr || !newFolder) {
2210
- console.error(chalk11.red(`Failed to create project: ${createErr?.message}`));
2453
+ console.error(chalk12.red(`Failed to create project: ${createErr?.message}`));
2211
2454
  return;
2212
2455
  }
2213
2456
  folderId = newFolder.id;
2214
- console.log(chalk11.green(`Created project "${projectName}".`));
2457
+ console.log(chalk12.green(`Created project "${projectName}".`));
2215
2458
  } else {
2216
2459
  folderId = chosen;
2217
2460
  }
@@ -2237,6 +2480,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
2237
2480
  skills_table_json: result.skills,
2238
2481
  stale_files_json: result.staleFiles,
2239
2482
  env_manifest_json: result.envManifest,
2483
+ doppler_json: result.doppler,
2240
2484
  marketplace_plugins_json: result.marketplacePlugins,
2241
2485
  last_scanned: result.scannedAt,
2242
2486
  data_hash: result.dataHash
@@ -2261,14 +2505,14 @@ ${proposedFiles.length} file(s) proposed for deletion:
2261
2505
  lastDeviceName: deviceName,
2262
2506
  lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
2263
2507
  });
2264
- console.log(chalk11.green("\nLinked and synced."));
2265
- console.log(chalk11.cyan(`
2508
+ console.log(chalk12.green("\nLinked and synced."));
2509
+ console.log(chalk12.cyan(`
2266
2510
  https://www.md4ai.com/project/${folderId}
2267
2511
  `));
2268
2512
  }
2269
2513
  }
2270
2514
  } catch {
2271
- console.log(chalk11.yellow("Not logged in \u2014 local preview only."));
2515
+ console.log(chalk12.yellow("Not logged in \u2014 local preview only."));
2272
2516
  }
2273
2517
  }
2274
2518
  }
@@ -2292,29 +2536,29 @@ __export(sync_exports, {
2292
2536
  syncCommand: () => syncCommand
2293
2537
  });
2294
2538
  import { resolve as resolve5 } from "node:path";
2295
- import { existsSync as existsSync10 } from "node:fs";
2296
- import chalk14 from "chalk";
2539
+ import { existsSync as existsSync11 } from "node:fs";
2540
+ import chalk15 from "chalk";
2297
2541
  function isValidProjectPath(p) {
2298
2542
  const resolved = resolve5(p);
2299
- return resolved.startsWith("/") && !resolved.includes("..") && existsSync10(resolved);
2543
+ return resolved.startsWith("/") && !resolved.includes("..") && existsSync11(resolved);
2300
2544
  }
2301
2545
  async function syncCommand(options) {
2302
2546
  const { supabase } = await getAuthenticatedClient();
2303
2547
  if (options.all) {
2304
2548
  const { data: devices, error } = await supabase.from("device_paths").select("folder_id, device_name, path");
2305
2549
  if (error || !devices?.length) {
2306
- console.error(chalk14.red("No devices found."));
2550
+ console.error(chalk15.red("No devices found."));
2307
2551
  process.exit(1);
2308
2552
  }
2309
2553
  for (const device of devices) {
2310
2554
  if (!isValidProjectPath(device.path)) {
2311
- console.error(chalk14.red(` Skipping invalid path: ${device.path}`));
2555
+ console.error(chalk15.red(` Skipping invalid path: ${device.path}`));
2312
2556
  continue;
2313
2557
  }
2314
- console.log(chalk14.blue(`Syncing: ${device.path}`));
2558
+ console.log(chalk15.blue(`Syncing: ${device.path}`));
2315
2559
  const { data: proposedAll } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
2316
2560
  if (proposedAll?.length) {
2317
- console.log(chalk14.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2561
+ console.log(chalk15.yellow(` ${proposedAll.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2318
2562
  }
2319
2563
  try {
2320
2564
  const result = await scanProject(device.path);
@@ -2330,32 +2574,32 @@ async function syncCommand(options) {
2330
2574
  }).eq("id", device.folder_id);
2331
2575
  await pushToolings(supabase, device.folder_id, result.toolings);
2332
2576
  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}`));
2577
+ console.log(chalk15.green(` Done: ${device.device_name}`));
2334
2578
  } catch (err) {
2335
- console.error(chalk14.red(` Failed: ${device.path}: ${err}`));
2579
+ console.error(chalk15.red(` Failed: ${device.path}: ${err}`));
2336
2580
  }
2337
2581
  }
2338
2582
  await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
2339
- console.log(chalk14.green("\nAll devices synced."));
2583
+ console.log(chalk15.green("\nAll devices synced."));
2340
2584
  } else {
2341
2585
  const state = await loadState();
2342
2586
  if (!state.lastFolderId) {
2343
- console.error(chalk14.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
2587
+ console.error(chalk15.yellow("No recent sync. Use: md4ai sync --all, or md4ai scan <path> first."));
2344
2588
  process.exit(1);
2345
2589
  }
2346
2590
  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
2591
  if (!device) {
2348
- console.error(chalk14.red("Could not find last synced device/folder."));
2592
+ console.error(chalk15.red("Could not find last synced device/folder."));
2349
2593
  process.exit(1);
2350
2594
  }
2351
2595
  if (!isValidProjectPath(device.path)) {
2352
- console.error(chalk14.red(`Invalid project path: ${device.path}`));
2596
+ console.error(chalk15.red(`Invalid project path: ${device.path}`));
2353
2597
  process.exit(1);
2354
2598
  }
2355
- console.log(chalk14.blue(`Syncing: ${device.path}`));
2599
+ console.log(chalk15.blue(`Syncing: ${device.path}`));
2356
2600
  const { data: proposedSingle } = await supabase.from("folder_files").select("file_path").eq("folder_id", device.folder_id).eq("proposed_for_deletion", true);
2357
2601
  if (proposedSingle?.length) {
2358
- console.log(chalk14.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2602
+ console.log(chalk15.yellow(` ${proposedSingle.length} file(s) proposed for deletion \u2014 run \`md4ai scan\` to review.`));
2359
2603
  }
2360
2604
  const result = await scanProject(device.path);
2361
2605
  await supabase.from("claude_folders").update({
@@ -2370,7 +2614,7 @@ async function syncCommand(options) {
2370
2614
  await pushToolings(supabase, device.folder_id, result.toolings);
2371
2615
  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
2616
  await saveState({ lastSyncAt: (/* @__PURE__ */ new Date()).toISOString() });
2373
- console.log(chalk14.green("Synced."));
2617
+ console.log(chalk15.green("Synced."));
2374
2618
  }
2375
2619
  }
2376
2620
  var init_sync = __esm({
@@ -2384,16 +2628,16 @@ var init_sync = __esm({
2384
2628
  });
2385
2629
 
2386
2630
  // dist/mcp/read-configs.js
2387
- import { readFile as readFile11 } from "node:fs/promises";
2388
- import { join as join14 } from "node:path";
2631
+ import { readFile as readFile12 } from "node:fs/promises";
2632
+ import { join as join15 } from "node:path";
2389
2633
  import { homedir as homedir9 } from "node:os";
2390
- import { existsSync as existsSync12 } from "node:fs";
2634
+ import { existsSync as existsSync13 } from "node:fs";
2391
2635
  import { readdir as readdir4 } from "node:fs/promises";
2392
2636
  async function readJsonSafe(path) {
2393
2637
  try {
2394
- if (!existsSync12(path))
2638
+ if (!existsSync13(path))
2395
2639
  return null;
2396
- const raw = await readFile11(path, "utf-8");
2640
+ const raw = await readFile12(path, "utf-8");
2397
2641
  return JSON.parse(raw);
2398
2642
  } catch {
2399
2643
  return null;
@@ -2455,50 +2699,50 @@ function parseFlatConfig(data, source) {
2455
2699
  async function readAllMcpConfigs() {
2456
2700
  const home = homedir9();
2457
2701
  const entries = [];
2458
- const userConfig = await readJsonSafe(join14(home, ".claude.json"));
2702
+ const userConfig = await readJsonSafe(join15(home, ".claude.json"));
2459
2703
  entries.push(...parseServers(userConfig, "global"));
2460
- const globalMcp = await readJsonSafe(join14(home, ".claude", "mcp.json"));
2704
+ const globalMcp = await readJsonSafe(join15(home, ".claude", "mcp.json"));
2461
2705
  entries.push(...parseServers(globalMcp, "global"));
2462
- const cwdMcp = await readJsonSafe(join14(process.cwd(), ".mcp.json"));
2706
+ const cwdMcp = await readJsonSafe(join15(process.cwd(), ".mcp.json"));
2463
2707
  entries.push(...parseServers(cwdMcp, "project"));
2464
- const pluginsBase = join14(home, ".claude", "plugins", "marketplaces");
2465
- if (existsSync12(pluginsBase)) {
2708
+ const pluginsBase = join15(home, ".claude", "plugins", "marketplaces");
2709
+ if (existsSync13(pluginsBase)) {
2466
2710
  try {
2467
2711
  const marketplaces = await readdir4(pluginsBase, { withFileTypes: true });
2468
2712
  for (const mp of marketplaces) {
2469
2713
  if (!mp.isDirectory())
2470
2714
  continue;
2471
- const extDir = join14(pluginsBase, mp.name, "external_plugins");
2472
- if (!existsSync12(extDir))
2715
+ const extDir = join15(pluginsBase, mp.name, "external_plugins");
2716
+ if (!existsSync13(extDir))
2473
2717
  continue;
2474
2718
  const plugins = await readdir4(extDir, { withFileTypes: true });
2475
2719
  for (const plugin of plugins) {
2476
2720
  if (!plugin.isDirectory())
2477
2721
  continue;
2478
- const pluginMcp = await readJsonSafe(join14(extDir, plugin.name, ".mcp.json"));
2722
+ const pluginMcp = await readJsonSafe(join15(extDir, plugin.name, ".mcp.json"));
2479
2723
  entries.push(...parseServers(pluginMcp, "plugin"));
2480
2724
  }
2481
2725
  }
2482
2726
  } catch {
2483
2727
  }
2484
2728
  }
2485
- const cacheBase = join14(home, ".claude", "plugins", "cache");
2486
- if (existsSync12(cacheBase)) {
2729
+ const cacheBase = join15(home, ".claude", "plugins", "cache");
2730
+ if (existsSync13(cacheBase)) {
2487
2731
  try {
2488
2732
  const registries = await readdir4(cacheBase, { withFileTypes: true });
2489
2733
  for (const reg of registries) {
2490
2734
  if (!reg.isDirectory())
2491
2735
  continue;
2492
- const regDir = join14(cacheBase, reg.name);
2736
+ const regDir = join15(cacheBase, reg.name);
2493
2737
  const plugins = await readdir4(regDir, { withFileTypes: true });
2494
2738
  for (const plugin of plugins) {
2495
2739
  if (!plugin.isDirectory())
2496
2740
  continue;
2497
- const versionDirs = await readdir4(join14(regDir, plugin.name), { withFileTypes: true });
2741
+ const versionDirs = await readdir4(join15(regDir, plugin.name), { withFileTypes: true });
2498
2742
  for (const ver of versionDirs) {
2499
2743
  if (!ver.isDirectory())
2500
2744
  continue;
2501
- const mcpPath = join14(regDir, plugin.name, ver.name, ".mcp.json");
2745
+ const mcpPath = join15(regDir, plugin.name, ver.name, ".mcp.json");
2502
2746
  const flatData = await readJsonSafe(mcpPath);
2503
2747
  if (flatData) {
2504
2748
  entries.push(...parseFlatConfig(flatData, "plugin"));
@@ -2648,7 +2892,7 @@ var mcp_watch_exports = {};
2648
2892
  __export(mcp_watch_exports, {
2649
2893
  mcpWatchCommand: () => mcpWatchCommand
2650
2894
  });
2651
- import chalk20 from "chalk";
2895
+ import chalk21 from "chalk";
2652
2896
  import { execFileSync as execFileSync5 } from "node:child_process";
2653
2897
  import { createHash as createHash2 } from "node:crypto";
2654
2898
  function detectTty() {
@@ -2802,20 +3046,20 @@ function buildRows(configs, httpResults, cdpStatus) {
2802
3046
  }
2803
3047
  function printTable(rows, deviceName, watcherPid, cdpResult) {
2804
3048
  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
3049
+ console.log(chalk21.bold.cyan(`
3050
+ MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) + chalk21.dim(` (PID ${watcherPid})`));
3051
+ console.log(chalk21.dim(` ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \xB7 refreshes every 30s \xB7 Ctrl+C to stop
2808
3052
  `));
2809
3053
  if (cdpResult) {
2810
3054
  if (cdpResult.status === "reachable") {
2811
3055
  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"));
3056
+ console.log(chalk21.green(" \u2714 Chrome browser connected") + chalk21.dim(browserInfo));
3057
+ console.log(chalk21.dim(" Claude Code can control Chrome via DevTools Protocol on localhost:9222"));
2814
3058
  } 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"));
3059
+ console.log(chalk21.red(" \u2717 Chrome browser not connected"));
3060
+ console.log(chalk21.dim(" Claude Code cannot reach Chrome. To fix:"));
3061
+ console.log(chalk21.dim(" 1. Open Chrome with: google-chrome --remote-debugging-port=9222"));
3062
+ console.log(chalk21.dim(" 2. Or add --remote-debugging-port=9222 to your Chrome shortcut"));
2819
3063
  }
2820
3064
  console.log("");
2821
3065
  }
@@ -2836,41 +3080,41 @@ function printTable(rows, deviceName, watcherPid, cdpResult) {
2836
3080
  sessionNum++;
2837
3081
  const totalMem = servers.reduce((sum, s) => sum + (s.memory_mb ?? 0), 0);
2838
3082
  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}`));
3083
+ console.log(chalk21.green(` ${label}`) + chalk21.dim(` \u2014 ${servers.length} server${servers.length !== 1 ? "s" : ""} \xB7 ${totalMem} MB \xB7 terminal ${tty}`));
2840
3084
  for (const s of servers) {
2841
3085
  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}`);
3086
+ const source = chalk21.dim(`[${s.config_source}]`);
3087
+ 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
3088
  }
2845
3089
  console.log("");
2846
3090
  }
2847
3091
  if (byTty.size > 1) {
2848
- console.log(chalk20.dim(" Each open Claude Code window runs its own set of MCP servers.\n"));
3092
+ console.log(chalk21.dim(" Each open Claude Code window runs its own set of MCP servers.\n"));
2849
3093
  }
2850
3094
  }
2851
3095
  if (runningHttp.length > 0) {
2852
- console.log(chalk20.blue(` Remote Services (${runningHttp.length})`) + chalk20.dim(" \u2014 HTTP endpoints reachable"));
3096
+ console.log(chalk21.blue(` Remote Services (${runningHttp.length})`) + chalk21.dim(" \u2014 HTTP endpoints reachable"));
2853
3097
  for (const s of runningHttp) {
2854
- console.log(` ${chalk20.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk20.dim((s.http_url ?? "").padEnd(30))}`);
3098
+ console.log(` ${chalk21.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk21.dim((s.http_url ?? "").padEnd(30))}`);
2855
3099
  }
2856
3100
  console.log("");
2857
3101
  }
2858
3102
  if (stopped.length > 0 || errored.length > 0) {
2859
3103
  const notRunning = [...stopped, ...errored];
2860
- console.log(chalk20.yellow(` Not Running (${notRunning.length})`) + chalk20.dim(" \u2014 configured but no process detected"));
3104
+ console.log(chalk21.yellow(` Not Running (${notRunning.length})`) + chalk21.dim(" \u2014 configured but no process detected"));
2861
3105
  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}`) : "";
3106
+ const icon = s.status === "error" ? chalk21.red("\u2717") : chalk21.yellow("\u25CB");
3107
+ const source = chalk21.dim(`[${s.config_source}]`);
3108
+ const detail = s.error_detail ? chalk21.dim(` \u2014 ${s.error_detail}`) : "";
2865
3109
  console.log(` ${icon} ${s.server_name.padEnd(20)} ${source}${detail}`);
2866
3110
  }
2867
3111
  console.log("");
2868
3112
  }
2869
3113
  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"));
3114
+ console.log(chalk21.yellow(" No MCP servers configured."));
3115
+ console.log(chalk21.dim(" Configure servers in ~/.claude/mcp.json or .mcp.json\n"));
2872
3116
  }
2873
- console.log(chalk20.bgYellow.black.bold(" \u26A0 DO NOT CLOSE THIS WINDOW \u2014 it feeds live data to the dashboard \u26A0 "));
3117
+ console.log(chalk21.bgYellow.black.bold(" \u26A0 DO NOT CLOSE THIS WINDOW \u2014 it feeds live data to the dashboard \u26A0 "));
2874
3118
  console.log("");
2875
3119
  }
2876
3120
  function formatUptime(seconds) {
@@ -2899,7 +3143,7 @@ async function mcpWatchCommand() {
2899
3143
  const { data: existingWatchers } = await supabase.from("mcp_watchers").select("pid, tty, cli_version, started_at").eq("device_id", deviceId);
2900
3144
  if (existingWatchers && existingWatchers.length > 0) {
2901
3145
  console.log("");
2902
- console.log(chalk20.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
3146
+ console.log(chalk21.yellow(` Replacing ${existingWatchers.length} existing watcher${existingWatchers.length !== 1 ? "s" : ""} on this device...`));
2903
3147
  for (const w of existingWatchers) {
2904
3148
  try {
2905
3149
  if (typeof w.pid === "number" && w.pid > 1 && w.pid !== process.pid) {
@@ -2910,15 +3154,15 @@ async function mcpWatchCommand() {
2910
3154
  }
2911
3155
  await supabase.from("mcp_watchers").delete().eq("device_id", deviceId);
2912
3156
  await new Promise((r) => setTimeout(r, 1e3));
2913
- console.log(chalk20.dim(" Previous watcher stopped.\n"));
3157
+ console.log(chalk21.dim(" Previous watcher stopped.\n"));
2914
3158
  }
2915
3159
  process.stdout.write(`\x1B]0;MCP mon\x07`);
2916
- console.log(chalk20.blue(`Starting MCP monitor for ${deviceName}...`));
3160
+ console.log(chalk21.blue(`Starting MCP monitor for ${deviceName}...`));
2917
3161
  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}`));
3162
+ console.log(chalk21.dim(" View this in the online dashboard at:"));
3163
+ console.log(chalk21.cyan(` https://www.md4ai.com/device/${deviceId}`));
2920
3164
  console.log("");
2921
- console.log(chalk20.yellow(" Please note, closing this window will stop ALL watch reports."));
3165
+ console.log(chalk21.yellow(" Please note, closing this window will stop ALL watch reports."));
2922
3166
  console.log("");
2923
3167
  await supabase.from("mcp_watchers").upsert({
2924
3168
  device_id: deviceId,
@@ -2948,7 +3192,7 @@ async function mcpWatchCommand() {
2948
3192
  return;
2949
3193
  }
2950
3194
  if (deleteError) {
2951
- console.error(chalk20.red(` [debug] Failed to delete old status: ${deleteError.message}`));
3195
+ console.error(chalk21.red(` [debug] Failed to delete old status: ${deleteError.message}`));
2952
3196
  }
2953
3197
  if (rows.length > 0) {
2954
3198
  const { error: insertError } = await supabase.from("mcp_server_status").insert(rows.map((row) => ({
@@ -2961,7 +3205,7 @@ async function mcpWatchCommand() {
2961
3205
  return;
2962
3206
  }
2963
3207
  if (insertError) {
2964
- console.error(chalk20.red(` [debug] Failed to write MCP status: ${insertError.message}`));
3208
+ console.error(chalk21.red(` [debug] Failed to write MCP status: ${insertError.message}`));
2965
3209
  }
2966
3210
  }
2967
3211
  const { error: heartbeatError } = await supabase.from("mcp_watchers").update({ last_heartbeat: now }).eq("device_id", deviceId).eq("pid", myPid);
@@ -2979,11 +3223,11 @@ async function mcpWatchCommand() {
2979
3223
  return;
2980
3224
  }
2981
3225
  jwtRefreshAttempted = true;
2982
- console.log(chalk20.yellow("\n Session token expired \u2014 attempting to refresh..."));
3226
+ console.log(chalk21.yellow("\n Session token expired \u2014 attempting to refresh..."));
2983
3227
  const refreshed = await refreshSession();
2984
3228
  if (refreshed) {
2985
3229
  supabase = refreshed.supabase;
2986
- console.log(chalk20.green(" Session refreshed successfully. Resuming monitoring.\n"));
3230
+ console.log(chalk21.green(" Session refreshed successfully. Resuming monitoring.\n"));
2987
3231
  jwtRefreshAttempted = false;
2988
3232
  await supabase.from("mcp_watchers").upsert({
2989
3233
  device_id: deviceId,
@@ -3001,14 +3245,14 @@ async function mcpWatchCommand() {
3001
3245
  function printSessionExpired() {
3002
3246
  process.stdout.write("\x1B[3J\x1B[2J\x1B[H");
3003
3247
  console.log("");
3004
- console.log(chalk20.bgRed.white.bold(" \u2717 SESSION EXPIRED \u2014 this watcher is no longer sending data to the dashboard \u2717 "));
3248
+ console.log(chalk21.bgRed.white.bold(" \u2717 SESSION EXPIRED \u2014 this watcher is no longer sending data to the dashboard \u2717 "));
3005
3249
  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.'));
3250
+ console.log(chalk21.yellow(" Your authentication token has expired and could not be refreshed."));
3251
+ console.log(chalk21.yellow(' The dashboard will show "No watchers" because this process can no longer update it.'));
3008
3252
  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"));
3253
+ console.log(chalk21.white(" To fix this:"));
3254
+ console.log(chalk21.cyan(" 1. Run: md4ai login"));
3255
+ console.log(chalk21.cyan(" 2. Then restart the watcher: md4ai mcp-watch"));
3012
3256
  console.log("");
3013
3257
  }
3014
3258
  let interval;
@@ -3051,7 +3295,7 @@ async function mcpWatchCommand() {
3051
3295
  clearInterval(interval);
3052
3296
  clearInterval(envInterval);
3053
3297
  await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).eq("pid", myPid);
3054
- console.log(chalk20.dim("\nMCP monitor stopped."));
3298
+ console.log(chalk21.dim("\nMCP monitor stopped."));
3055
3299
  process.exit(0);
3056
3300
  };
3057
3301
  process.on("SIGINT", () => {
@@ -3359,71 +3603,71 @@ init_map();
3359
3603
 
3360
3604
  // dist/commands/simulate.js
3361
3605
  init_dist();
3362
- import { join as join12 } from "node:path";
3363
- import { existsSync as existsSync8 } from "node:fs";
3606
+ import { join as join13 } from "node:path";
3607
+ import { existsSync as existsSync9 } from "node:fs";
3364
3608
  import { homedir as homedir8 } from "node:os";
3365
- import chalk12 from "chalk";
3609
+ import chalk13 from "chalk";
3366
3610
  async function simulateCommand(prompt) {
3367
3611
  const projectRoot = process.cwd();
3368
- console.log(chalk12.blue(`Simulating prompt: "${prompt}"
3612
+ console.log(chalk13.blue(`Simulating prompt: "${prompt}"
3369
3613
  `));
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)"));
3614
+ console.log(chalk13.dim("Files Claude would load:\n"));
3615
+ const globalClaude = join13(homedir8(), ".claude", "CLAUDE.md");
3616
+ if (existsSync9(globalClaude)) {
3617
+ console.log(chalk13.green(" \u2713 ~/.claude/CLAUDE.md (global)"));
3374
3618
  }
3375
3619
  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)`));
3620
+ const fullPath = join13(projectRoot, rootFile);
3621
+ if (existsSync9(fullPath)) {
3622
+ console.log(chalk13.green(` \u2713 ${rootFile} (project root)`));
3379
3623
  }
3380
3624
  }
3381
3625
  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")
3626
+ join13(homedir8(), ".claude", "settings.json"),
3627
+ join13(homedir8(), ".claude", "settings.local.json"),
3628
+ join13(projectRoot, ".claude", "settings.json"),
3629
+ join13(projectRoot, ".claude", "settings.local.json")
3386
3630
  ];
3387
3631
  for (const sf of settingsFiles) {
3388
- if (existsSync8(sf)) {
3632
+ if (existsSync9(sf)) {
3389
3633
  const display = sf.startsWith(homedir8()) ? sf.replace(homedir8(), "~") : sf.replace(projectRoot + "/", "");
3390
- console.log(chalk12.green(` \u2713 ${display} (settings)`));
3634
+ console.log(chalk13.green(` \u2713 ${display} (settings)`));
3391
3635
  }
3392
3636
  }
3393
3637
  const words = prompt.split(/\s+/);
3394
3638
  for (const word of words) {
3395
3639
  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)`));
3640
+ const candidatePath = join13(projectRoot, cleaned);
3641
+ if (existsSync9(candidatePath) && cleaned.includes("/")) {
3642
+ console.log(chalk13.cyan(` \u2192 ${cleaned} (referenced in prompt)`));
3399
3643
  }
3400
3644
  }
3401
- console.log(chalk12.dim("\nNote: This is an approximation. Actual file loading depends on Claude Code internals."));
3645
+ console.log(chalk13.dim("\nNote: This is an approximation. Actual file loading depends on Claude Code internals."));
3402
3646
  }
3403
3647
 
3404
3648
  // 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";
3649
+ import { join as join14 } from "node:path";
3650
+ import { readFile as readFile10, writeFile as writeFile3 } from "node:fs/promises";
3651
+ import { existsSync as existsSync10 } from "node:fs";
3652
+ import chalk14 from "chalk";
3409
3653
  async function printCommand(title) {
3410
3654
  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"));
3655
+ const scanDataPath = join14(projectRoot, "output", "index.html");
3656
+ if (!existsSync10(scanDataPath)) {
3657
+ console.error(chalk14.red("No scan data found. Run: md4ai scan"));
3414
3658
  process.exit(1);
3415
3659
  }
3416
- const html = await readFile9(scanDataPath, "utf-8");
3660
+ const html = await readFile10(scanDataPath, "utf-8");
3417
3661
  const match = html.match(/<script type="application\/json" id="scan-data">([\s\S]*?)<\/script>/);
3418
3662
  if (!match) {
3419
- console.error(chalk13.red("Could not extract scan data from output/index.html"));
3663
+ console.error(chalk14.red("Could not extract scan data from output/index.html"));
3420
3664
  process.exit(1);
3421
3665
  }
3422
3666
  const result = JSON.parse(match[1]);
3423
3667
  const printHtml = generatePrintHtml(result, title);
3424
- const outputPath = join13(projectRoot, "output", `print-${Date.now()}.html`);
3668
+ const outputPath = join14(projectRoot, "output", `print-${Date.now()}.html`);
3425
3669
  await writeFile3(outputPath, printHtml, "utf-8");
3426
- console.log(chalk13.green(`Print-ready wall sheet: ${outputPath}`));
3670
+ console.log(chalk14.green(`Print-ready wall sheet: ${outputPath}`));
3427
3671
  }
3428
3672
  function escapeHtml2(text) {
3429
3673
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -3489,7 +3733,7 @@ init_scanner();
3489
3733
  init_push_toolings();
3490
3734
  init_device_utils();
3491
3735
  import { resolve as resolve6 } from "node:path";
3492
- import chalk15 from "chalk";
3736
+ import chalk16 from "chalk";
3493
3737
  async function linkCommand(projectId) {
3494
3738
  const { supabase, userId } = await getAuthenticatedClient();
3495
3739
  const cwd = resolve6(process.cwd());
@@ -3497,11 +3741,11 @@ async function linkCommand(projectId) {
3497
3741
  const osType = detectOs2();
3498
3742
  const { data: folder, error: folderErr } = await supabase.from("claude_folders").select("id, name").eq("id", projectId).single();
3499
3743
  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."));
3744
+ console.error(chalk16.red("Project not found, or you do not have access."));
3745
+ console.error(chalk16.yellow("Check the project ID in the MD4AI web dashboard."));
3502
3746
  process.exit(1);
3503
3747
  }
3504
- console.log(chalk15.blue(`
3748
+ console.log(chalk16.blue(`
3505
3749
  Linking "${folder.name}" to this device...
3506
3750
  `));
3507
3751
  console.log(` Project: ${folder.name}`);
@@ -3525,19 +3769,20 @@ Linking "${folder.name}" to this device...
3525
3769
  path: cwd
3526
3770
  });
3527
3771
  if (pathErr) {
3528
- console.error(chalk15.red(`Failed to link: ${pathErr.message}`));
3772
+ console.error(chalk16.red(`Failed to link: ${pathErr.message}`));
3529
3773
  process.exit(1);
3530
3774
  }
3531
3775
  }
3532
- console.log(chalk15.green("\nLinked successfully."));
3533
- console.log(chalk15.blue("\nRunning initial scan...\n"));
3534
- const result = await scanProject(cwd);
3776
+ console.log(chalk16.green("\nLinked successfully."));
3777
+ console.log(chalk16.blue("\nRunning initial scan...\n"));
3778
+ const result = await scanProject(cwd, folder.id);
3535
3779
  console.log(` Files: ${result.graph.nodes.length}`);
3536
3780
  console.log(` References:${result.graph.edges.length}`);
3537
3781
  console.log(` Orphans: ${result.orphans.length}`);
3538
3782
  console.log(` Skills: ${result.skills.length}`);
3539
3783
  console.log(` Toolings: ${result.toolings.length}`);
3540
3784
  console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
3785
+ console.log(` Doppler: ${result.doppler ? `${result.doppler.configs.length} config(s)` : "not configured"}`);
3541
3786
  console.log(` Plugins: ${result.marketplacePlugins.length} (${result.marketplacePlugins.reduce((n, p) => n + p.skills.length, 0)} skills)`);
3542
3787
  const { error: scanErr } = await supabase.from("claude_folders").update({
3543
3788
  graph_json: result.graph,
@@ -3546,12 +3791,13 @@ Linking "${folder.name}" to this device...
3546
3791
  skills_table_json: result.skills,
3547
3792
  stale_files_json: result.staleFiles,
3548
3793
  env_manifest_json: result.envManifest,
3794
+ doppler_json: result.doppler,
3549
3795
  marketplace_plugins_json: result.marketplacePlugins,
3550
3796
  last_scanned: result.scannedAt,
3551
3797
  data_hash: result.dataHash
3552
3798
  }).eq("id", folder.id);
3553
3799
  if (scanErr) {
3554
- console.error(chalk15.yellow(`Scan upload warning: ${scanErr.message}`));
3800
+ console.error(chalk16.yellow(`Scan upload warning: ${scanErr.message}`));
3555
3801
  }
3556
3802
  await pushToolings(supabase, folder.id, result.toolings);
3557
3803
  const graphPaths = result.graph.nodes.map((n) => n.filePath);
@@ -3566,7 +3812,7 @@ Linking "${folder.name}" to this device...
3566
3812
  last_modified: file.lastModified
3567
3813
  }, { onConflict: "folder_id,file_path" });
3568
3814
  }
3569
- console.log(chalk15.green(` Uploaded ${configFiles.length} config file(s).`));
3815
+ console.log(chalk16.green(` Uploaded ${configFiles.length} config file(s).`));
3570
3816
  }
3571
3817
  await supabase.from("device_paths").update({ last_synced: (/* @__PURE__ */ new Date()).toISOString() }).eq("folder_id", folder.id).eq("device_name", deviceName);
3572
3818
  await saveState({
@@ -3575,40 +3821,40 @@ Linking "${folder.name}" to this device...
3575
3821
  lastSyncAt: (/* @__PURE__ */ new Date()).toISOString()
3576
3822
  });
3577
3823
  const projectUrl = `https://www.md4ai.com/project/${folder.id}`;
3578
- console.log(chalk15.green("\nDone! Project linked and scanned."));
3579
- console.log(chalk15.cyan(`
3824
+ console.log(chalk16.green("\nDone! Project linked and scanned."));
3825
+ console.log(chalk16.cyan(`
3580
3826
  ${projectUrl}
3581
3827
  `));
3582
- console.log(chalk15.grey('Run "md4ai scan" to rescan at any time.'));
3828
+ console.log(chalk16.grey('Run "md4ai scan" to rescan at any time.'));
3583
3829
  }
3584
3830
 
3585
3831
  // dist/commands/import-bundle.js
3586
- import { readFile as readFile10, writeFile as writeFile4, mkdir as mkdir3, stat as fsStat } from "node:fs/promises";
3832
+ import { readFile as readFile11, writeFile as writeFile4, mkdir as mkdir3, stat as fsStat } from "node:fs/promises";
3587
3833
  import { dirname as dirname3, resolve as resolve7 } from "node:path";
3588
- import { existsSync as existsSync11 } from "node:fs";
3589
- import chalk16 from "chalk";
3834
+ import { existsSync as existsSync12 } from "node:fs";
3835
+ import chalk17 from "chalk";
3590
3836
  import { confirm as confirm3, input as input6 } from "@inquirer/prompts";
3591
3837
  var MAX_BUNDLE_SIZE_BYTES = 50 * 1024 * 1024;
3592
3838
  async function importBundleCommand(zipPath) {
3593
- if (!existsSync11(zipPath)) {
3594
- console.error(chalk16.red(`File not found: ${zipPath}`));
3839
+ if (!existsSync12(zipPath)) {
3840
+ console.error(chalk17.red(`File not found: ${zipPath}`));
3595
3841
  process.exit(1);
3596
3842
  }
3597
3843
  const fileStat = await fsStat(zipPath);
3598
3844
  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.`));
3845
+ console.error(chalk17.red(`Bundle too large (${Math.round(fileStat.size / 1024 / 1024)} MB). Maximum is 50 MB.`));
3600
3846
  process.exit(1);
3601
3847
  }
3602
3848
  const JSZip = (await import("jszip")).default;
3603
- const zipData = await readFile10(zipPath);
3849
+ const zipData = await readFile11(zipPath);
3604
3850
  const zip = await JSZip.loadAsync(zipData);
3605
3851
  const manifestFile = zip.file("manifest.json");
3606
3852
  if (!manifestFile) {
3607
- console.error(chalk16.red("Invalid bundle: missing manifest.json"));
3853
+ console.error(chalk17.red("Invalid bundle: missing manifest.json"));
3608
3854
  process.exit(1);
3609
3855
  }
3610
3856
  const manifest = JSON.parse(await manifestFile.async("string"));
3611
- console.log(chalk16.blue(`
3857
+ console.log(chalk17.blue(`
3612
3858
  Bundle: ${manifest.folderName}`));
3613
3859
  console.log(` Exported by: ${manifest.ownerEmail}`);
3614
3860
  console.log(` Exported at: ${manifest.exportedAt}`);
@@ -3622,10 +3868,10 @@ Bundle: ${manifest.folderName}`));
3622
3868
  }
3623
3869
  }
3624
3870
  if (files.length === 0) {
3625
- console.log(chalk16.yellow("No Claude config files found in bundle."));
3871
+ console.log(chalk17.yellow("No Claude config files found in bundle."));
3626
3872
  return;
3627
3873
  }
3628
- console.log(chalk16.blue(`
3874
+ console.log(chalk17.blue(`
3629
3875
  Files to extract:`));
3630
3876
  for (const f of files) {
3631
3877
  console.log(` ${f.filePath}`);
@@ -3638,39 +3884,39 @@ Files to extract:`));
3638
3884
  message: `Extract ${files.length} file(s) to ${targetDir}?`
3639
3885
  });
3640
3886
  if (!proceed) {
3641
- console.log(chalk16.yellow("Cancelled."));
3887
+ console.log(chalk17.yellow("Cancelled."));
3642
3888
  return;
3643
3889
  }
3644
3890
  const resolvedTarget = resolve7(targetDir);
3645
3891
  for (const file of files) {
3646
3892
  const fullPath = resolve7(targetDir, file.filePath);
3647
3893
  if (!fullPath.startsWith(resolvedTarget + "/") && fullPath !== resolvedTarget) {
3648
- console.error(chalk16.red(` Blocked path traversal: ${file.filePath}`));
3894
+ console.error(chalk17.red(` Blocked path traversal: ${file.filePath}`));
3649
3895
  continue;
3650
3896
  }
3651
3897
  const dir = dirname3(fullPath);
3652
- if (!existsSync11(dir)) {
3898
+ if (!existsSync12(dir)) {
3653
3899
  await mkdir3(dir, { recursive: true });
3654
3900
  }
3655
3901
  await writeFile4(fullPath, file.content, "utf-8");
3656
- console.log(chalk16.green(` \u2713 ${file.filePath}`));
3902
+ console.log(chalk17.green(` \u2713 ${file.filePath}`));
3657
3903
  }
3658
- console.log(chalk16.green(`
3904
+ console.log(chalk17.green(`
3659
3905
  Done! ${files.length} file(s) extracted to ${targetDir}`));
3660
3906
  }
3661
3907
 
3662
3908
  // dist/commands/admin-update-tool.js
3663
3909
  init_auth();
3664
- import chalk17 from "chalk";
3910
+ import chalk18 from "chalk";
3665
3911
  var VALID_CATEGORIES = ["framework", "runtime", "cli", "mcp", "package", "database", "other"];
3666
3912
  async function adminUpdateToolCommand(options) {
3667
3913
  const { supabase } = await getAuthenticatedClient();
3668
3914
  if (!options.name) {
3669
- console.error(chalk17.red("--name is required."));
3915
+ console.error(chalk18.red("--name is required."));
3670
3916
  process.exit(1);
3671
3917
  }
3672
3918
  if (options.category && !VALID_CATEGORIES.includes(options.category)) {
3673
- console.error(chalk17.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
3919
+ console.error(chalk18.red(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`));
3674
3920
  process.exit(1);
3675
3921
  }
3676
3922
  const { data: existing } = await supabase.from("tools_registry").select("id, name, display_name, category").eq("name", options.name).maybeSingle();
@@ -3692,13 +3938,13 @@ async function adminUpdateToolCommand(options) {
3692
3938
  updates.notes = options.notes;
3693
3939
  const { error } = await supabase.from("tools_registry").update(updates).eq("id", existing.id);
3694
3940
  if (error) {
3695
- console.error(chalk17.red(`Failed to update: ${error.message}`));
3941
+ console.error(chalk18.red(`Failed to update: ${error.message}`));
3696
3942
  process.exit(1);
3697
3943
  }
3698
- console.log(chalk17.green(`Updated "${existing.display_name}" in the registry.`));
3944
+ console.log(chalk18.green(`Updated "${existing.display_name}" in the registry.`));
3699
3945
  } else {
3700
3946
  if (!options.display || !options.category) {
3701
- console.error(chalk17.red("New tools require --display and --category."));
3947
+ console.error(chalk18.red("New tools require --display and --category."));
3702
3948
  process.exit(1);
3703
3949
  }
3704
3950
  const { error } = await supabase.from("tools_registry").insert({
@@ -3712,25 +3958,25 @@ async function adminUpdateToolCommand(options) {
3712
3958
  notes: options.notes ?? null
3713
3959
  });
3714
3960
  if (error) {
3715
- console.error(chalk17.red(`Failed to create: ${error.message}`));
3961
+ console.error(chalk18.red(`Failed to create: ${error.message}`));
3716
3962
  process.exit(1);
3717
3963
  }
3718
- console.log(chalk17.green(`Added "${options.display}" to the registry.`));
3964
+ console.log(chalk18.green(`Added "${options.display}" to the registry.`));
3719
3965
  }
3720
3966
  }
3721
3967
 
3722
3968
  // dist/commands/admin-list-tools.js
3723
3969
  init_auth();
3724
- import chalk18 from "chalk";
3970
+ import chalk19 from "chalk";
3725
3971
  async function adminListToolsCommand() {
3726
3972
  const { supabase } = await getAuthenticatedClient();
3727
3973
  const { data: tools, error } = await supabase.from("tools_registry").select("*").order("category").order("display_name");
3728
3974
  if (error) {
3729
- console.error(chalk18.red(`Failed to fetch tools: ${error.message}`));
3975
+ console.error(chalk19.red(`Failed to fetch tools: ${error.message}`));
3730
3976
  process.exit(1);
3731
3977
  }
3732
3978
  if (!tools?.length) {
3733
- console.log(chalk18.yellow("No tools in the registry."));
3979
+ console.log(chalk19.yellow("No tools in the registry."));
3734
3980
  return;
3735
3981
  }
3736
3982
  const nameW = Math.max(16, ...tools.map((t) => t.display_name.length));
@@ -3744,7 +3990,7 @@ async function adminListToolsCommand() {
3744
3990
  "Beta".padEnd(betaW),
3745
3991
  "Last Checked"
3746
3992
  ].join(" ");
3747
- console.log(chalk18.bold(header));
3993
+ console.log(chalk19.bold(header));
3748
3994
  console.log("\u2500".repeat(header.length));
3749
3995
  for (const tool of tools) {
3750
3996
  const lastChecked = tool.updated_at ? formatRelative(new Date(tool.updated_at)) : "\u2014";
@@ -3757,7 +4003,7 @@ async function adminListToolsCommand() {
3757
4003
  ].join(" ");
3758
4004
  console.log(row);
3759
4005
  }
3760
- console.log(chalk18.grey(`
4006
+ console.log(chalk19.grey(`
3761
4007
  ${tools.length} tool(s) in registry.`));
3762
4008
  }
3763
4009
  function formatRelative(date) {
@@ -3775,7 +4021,7 @@ function formatRelative(date) {
3775
4021
 
3776
4022
  // dist/commands/admin-fetch-versions.js
3777
4023
  init_auth();
3778
- import chalk19 from "chalk";
4024
+ import chalk20 from "chalk";
3779
4025
 
3780
4026
  // dist/commands/version-sources.js
3781
4027
  var VERSION_SOURCES = {
@@ -3804,11 +4050,11 @@ async function adminFetchVersionsCommand() {
3804
4050
  const { supabase } = await getAuthenticatedClient();
3805
4051
  const { data: tools, error } = await supabase.from("tools_registry").select("*").order("display_name");
3806
4052
  if (error) {
3807
- console.error(chalk19.red(`Failed to fetch tools: ${error.message}`));
4053
+ console.error(chalk20.red(`Failed to fetch tools: ${error.message}`));
3808
4054
  process.exit(1);
3809
4055
  }
3810
4056
  if (!tools?.length) {
3811
- console.log(chalk19.yellow("No tools in the registry."));
4057
+ console.log(chalk20.yellow("No tools in the registry."));
3812
4058
  }
3813
4059
  const { data: allProjectToolings } = await supabase.from("project_toolings").select("tool_name, detection_source").is("tool_id", null);
3814
4060
  const registeredNames = new Set((tools ?? []).map((t) => t.name));
@@ -3819,7 +4065,7 @@ async function adminFetchVersionsCommand() {
3819
4065
  }
3820
4066
  }
3821
4067
  if (unregisteredNames.size > 0) {
3822
- console.log(chalk19.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
4068
+ console.log(chalk20.blue(`Auto-registering ${unregisteredNames.size} unverified package(s)...
3823
4069
  `));
3824
4070
  for (const name of unregisteredNames) {
3825
4071
  const displayName = name;
@@ -3837,10 +4083,10 @@ async function adminFetchVersionsCommand() {
3837
4083
  }
3838
4084
  }
3839
4085
  if (!tools?.length) {
3840
- console.log(chalk19.yellow("No tools to fetch."));
4086
+ console.log(chalk20.yellow("No tools to fetch."));
3841
4087
  return;
3842
4088
  }
3843
- console.log(chalk19.bold(`Fetching versions for ${tools.length} tool(s)...
4089
+ console.log(chalk20.bold(`Fetching versions for ${tools.length} tool(s)...
3844
4090
  `));
3845
4091
  const results = await Promise.all(tools.map(async (tool) => {
3846
4092
  const source = VERSION_SOURCES[tool.name] ?? { type: "npm", package: tool.name };
@@ -3879,10 +4125,10 @@ async function adminFetchVersionsCommand() {
3879
4125
  "Beta".padEnd(betaW),
3880
4126
  "Status".padEnd(statusW)
3881
4127
  ].join(" ");
3882
- console.log(chalk19.bold(header));
4128
+ console.log(chalk20.bold(header));
3883
4129
  console.log("\u2500".repeat(header.length));
3884
4130
  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;
4131
+ const statusColour = result.status === "updated" ? chalk20.green : result.status === "unchanged" ? chalk20.grey : result.status === "failed" ? chalk20.red : chalk20.yellow;
3886
4132
  const row = [
3887
4133
  result.displayName.padEnd(nameW),
3888
4134
  (result.stable ?? "\u2014").padEnd(stableW),
@@ -3895,8 +4141,8 @@ async function adminFetchVersionsCommand() {
3895
4141
  const unchanged = results.filter((r) => r.status === "unchanged").length;
3896
4142
  const failed = results.filter((r) => r.status === "failed").length;
3897
4143
  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`));
4144
+ console.log(chalk20.grey(`
4145
+ ${results.length} tool(s): `) + chalk20.green(`${updated} updated`) + ", " + chalk20.grey(`${unchanged} unchanged`) + ", " + chalk20.red(`${failed} failed`) + ", " + chalk20.yellow(`${noSource} no source`));
3900
4146
  }
3901
4147
  async function fetchVersions(source) {
3902
4148
  const controller = new AbortController();
@@ -3954,10 +4200,10 @@ async function fetchGitHubVersions(repo, signal) {
3954
4200
  init_mcp_watch();
3955
4201
 
3956
4202
  // 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";
4203
+ import { resolve as resolve8, join as join16, relative as relative4, dirname as dirname4 } from "node:path";
4204
+ import { readFile as readFile13, writeFile as writeFile5, mkdir as mkdir4 } from "node:fs/promises";
4205
+ import { existsSync as existsSync14 } from "node:fs";
4206
+ import chalk22 from "chalk";
3961
4207
  var SECRET_PATTERNS = [
3962
4208
  /_KEY$/i,
3963
4209
  /_SECRET$/i,
@@ -3973,8 +4219,8 @@ function isLikelySecret(name) {
3973
4219
  async function discoverEnvFiles(projectRoot) {
3974
4220
  const apps = [];
3975
4221
  for (const envName of [".env", ".env.local", ".env.example"]) {
3976
- const envPath = join15(projectRoot, envName);
3977
- if (existsSync13(envPath)) {
4222
+ const envPath = join16(projectRoot, envName);
4223
+ if (existsSync14(envPath)) {
3978
4224
  const vars = await extractVarNames(envPath);
3979
4225
  if (vars.length > 0) {
3980
4226
  apps.push({ name: "root", envFilePath: envName, vars });
@@ -3984,12 +4230,12 @@ async function discoverEnvFiles(projectRoot) {
3984
4230
  }
3985
4231
  const subdirs = ["web", "cli", "api", "app", "server", "packages"];
3986
4232
  for (const sub of subdirs) {
3987
- const subDir = join15(projectRoot, sub);
3988
- if (!existsSync13(subDir))
4233
+ const subDir = join16(projectRoot, sub);
4234
+ if (!existsSync14(subDir))
3989
4235
  continue;
3990
4236
  for (const envName of [".env.local", ".env", ".env.example"]) {
3991
- const envPath = join15(subDir, envName);
3992
- if (existsSync13(envPath)) {
4237
+ const envPath = join16(subDir, envName);
4238
+ if (existsSync14(envPath)) {
3993
4239
  const vars = await extractVarNames(envPath);
3994
4240
  if (vars.length > 0) {
3995
4241
  apps.push({
@@ -4005,7 +4251,7 @@ async function discoverEnvFiles(projectRoot) {
4005
4251
  return apps;
4006
4252
  }
4007
4253
  async function extractVarNames(envPath) {
4008
- const content = await readFile12(envPath, "utf-8");
4254
+ const content = await readFile13(envPath, "utf-8");
4009
4255
  const vars = [];
4010
4256
  for (const line of content.split("\n")) {
4011
4257
  const trimmed = line.trim();
@@ -4050,38 +4296,38 @@ function generateManifest(apps) {
4050
4296
  }
4051
4297
  async function initManifestCommand() {
4052
4298
  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."));
4299
+ const manifestPath = join16(projectRoot, "docs", "reference", "env-manifest.md");
4300
+ if (existsSync14(manifestPath)) {
4301
+ console.log(chalk22.yellow(`Manifest already exists: ${relative4(projectRoot, manifestPath)}`));
4302
+ console.log(chalk22.dim("Edit it directly to make changes."));
4057
4303
  return;
4058
4304
  }
4059
- console.log(chalk21.blue("Scanning for .env files...\n"));
4305
+ console.log(chalk22.blue("Scanning for .env files...\n"));
4060
4306
  const apps = await discoverEnvFiles(projectRoot);
4061
4307
  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`));
4308
+ console.log(chalk22.yellow("No .env files found. Create a manifest manually at:"));
4309
+ console.log(chalk22.cyan(` docs/reference/env-manifest.md`));
4064
4310
  return;
4065
4311
  }
4066
4312
  for (const app of apps) {
4067
- console.log(` ${chalk21.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
4313
+ console.log(` ${chalk22.green(app.name)}: ${app.envFilePath} (${app.vars.length} vars)`);
4068
4314
  }
4069
4315
  const content = generateManifest(apps);
4070
4316
  const dir = dirname4(manifestPath);
4071
- if (!existsSync13(dir)) {
4317
+ if (!existsSync14(dir)) {
4072
4318
  await mkdir4(dir, { recursive: true });
4073
4319
  }
4074
4320
  await writeFile5(manifestPath, content, "utf-8");
4075
- console.log(chalk21.green(`
4321
+ console.log(chalk22.green(`
4076
4322
  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."));
4323
+ console.log(chalk22.dim("Review and edit the file \u2014 it is your source of truth."));
4324
+ console.log(chalk22.dim("Then run `md4ai scan` to verify against your environments."));
4079
4325
  }
4080
4326
 
4081
4327
  // dist/commands/update.js
4082
4328
  init_check_update();
4083
4329
  init_config();
4084
- import chalk22 from "chalk";
4330
+ import chalk23 from "chalk";
4085
4331
  import { execFileSync as execFileSync6, spawn } from "node:child_process";
4086
4332
  async function fetchLatestVersion() {
4087
4333
  try {
@@ -4130,13 +4376,13 @@ function spawnPostUpdate() {
4130
4376
  }
4131
4377
  async function postUpdateFlow() {
4132
4378
  const { confirm: confirm4 } = await import("@inquirer/prompts");
4133
- console.log(chalk22.green(`
4379
+ console.log(chalk23.green(`
4134
4380
  md4ai v${CURRENT_VERSION} \u2014 you're on the latest version.
4135
4381
  `));
4136
4382
  const creds = await loadCredentials();
4137
4383
  const isLoggedIn = !!creds?.accessToken && Date.now() < creds.expiresAt;
4138
4384
  if (!isLoggedIn) {
4139
- console.log(chalk22.yellow(" You are not logged in.\n"));
4385
+ console.log(chalk23.yellow(" You are not logged in.\n"));
4140
4386
  const wantLogin = await confirm4({
4141
4387
  message: "Log in now?",
4142
4388
  default: true
@@ -4147,7 +4393,7 @@ async function postUpdateFlow() {
4147
4393
  await loginCommand2();
4148
4394
  console.log("");
4149
4395
  } else {
4150
- console.log(chalk22.dim("\nRun md4ai login when you're ready.\n"));
4396
+ console.log(chalk23.dim("\nRun md4ai login when you're ready.\n"));
4151
4397
  return;
4152
4398
  }
4153
4399
  }
@@ -4181,7 +4427,7 @@ async function postUpdateFlow() {
4181
4427
  const { mcpWatchCommand: mcpWatchCommand2 } = await Promise.resolve().then(() => (init_mcp_watch(), mcp_watch_exports));
4182
4428
  await mcpWatchCommand2();
4183
4429
  } else {
4184
- console.log(chalk22.dim("\nYou can start monitoring later with: md4ai mcp-watch\n"));
4430
+ console.log(chalk23.dim("\nYou can start monitoring later with: md4ai mcp-watch\n"));
4185
4431
  }
4186
4432
  }
4187
4433
  async function updateCommand(options) {
@@ -4189,19 +4435,19 @@ async function updateCommand(options) {
4189
4435
  await postUpdateFlow();
4190
4436
  return;
4191
4437
  }
4192
- console.log(chalk22.blue(`
4438
+ console.log(chalk23.blue(`
4193
4439
  Current version: v${CURRENT_VERSION}`));
4194
- console.log(chalk22.dim(" Checking for updates...\n"));
4440
+ console.log(chalk23.dim(" Checking for updates...\n"));
4195
4441
  const latest = await fetchLatestVersion();
4196
4442
  if (!latest) {
4197
- console.log(chalk22.yellow(" Could not reach the npm registry. Check your internet connection."));
4443
+ console.log(chalk23.yellow(" Could not reach the npm registry. Check your internet connection."));
4198
4444
  process.exit(1);
4199
4445
  }
4200
4446
  if (!isNewer2(latest, CURRENT_VERSION)) {
4201
4447
  await postUpdateFlow();
4202
4448
  return;
4203
4449
  }
4204
- console.log(chalk22.white(" Update available: ") + chalk22.dim(`v${CURRENT_VERSION}`) + chalk22.white(" \u2192 ") + chalk22.green.bold(`v${latest}
4450
+ console.log(chalk23.white(" Update available: ") + chalk23.dim(`v${CURRENT_VERSION}`) + chalk23.white(" \u2192 ") + chalk23.green.bold(`v${latest}
4205
4451
  `));
4206
4452
  const { confirm: confirm4 } = await import("@inquirer/prompts");
4207
4453
  const wantUpdate = await confirm4({
@@ -4209,17 +4455,17 @@ async function updateCommand(options) {
4209
4455
  default: true
4210
4456
  });
4211
4457
  if (!wantUpdate) {
4212
- console.log(chalk22.dim("\nUpdate skipped.\n"));
4458
+ console.log(chalk23.dim("\nUpdate skipped.\n"));
4213
4459
  return;
4214
4460
  }
4215
- console.log(chalk22.blue("\n Installing...\n"));
4461
+ console.log(chalk23.blue("\n Installing...\n"));
4216
4462
  const ok = runNpmInstall();
4217
4463
  if (!ok) {
4218
- console.log(chalk22.red("\n npm install failed. Try running manually:"));
4219
- console.log(chalk22.cyan(" npm install -g md4ai\n"));
4464
+ console.log(chalk23.red("\n npm install failed. Try running manually:"));
4465
+ console.log(chalk23.cyan(" npm install -g md4ai\n"));
4220
4466
  process.exit(1);
4221
4467
  }
4222
- console.log(chalk22.green("\n Updated successfully.\n"));
4468
+ console.log(chalk23.green("\n Updated successfully.\n"));
4223
4469
  spawnPostUpdate();
4224
4470
  }
4225
4471
 
@@ -4227,7 +4473,7 @@ async function updateCommand(options) {
4227
4473
  init_check_update();
4228
4474
  init_map();
4229
4475
  init_mcp_watch();
4230
- import chalk23 from "chalk";
4476
+ import chalk24 from "chalk";
4231
4477
  import { resolve as resolve9 } from "node:path";
4232
4478
  async function fetchLatestVersion2() {
4233
4479
  try {
@@ -4259,13 +4505,13 @@ function isNewer3(a, b) {
4259
4505
  async function startCommand() {
4260
4506
  const projectRoot = resolve9(process.cwd());
4261
4507
  console.log("");
4262
- console.log(chalk23.bold.cyan(` MD4AI v${CURRENT_VERSION}`));
4263
- console.log(chalk23.dim(` ${projectRoot}`));
4508
+ console.log(chalk24.bold.cyan(` MD4AI v${CURRENT_VERSION}`));
4509
+ console.log(chalk24.dim(` ${projectRoot}`));
4264
4510
  console.log("");
4265
- console.log(chalk23.blue(" \u2460 Checking for updates..."));
4511
+ console.log(chalk24.blue(" \u2460 Checking for updates..."));
4266
4512
  const latest = await fetchLatestVersion2();
4267
4513
  if (latest && isNewer3(latest, CURRENT_VERSION)) {
4268
- console.log(chalk23.yellow(` Update available: v${CURRENT_VERSION} \u2192 v${latest}`));
4514
+ console.log(chalk24.yellow(` Update available: v${CURRENT_VERSION} \u2192 v${latest}`));
4269
4515
  if (process.stdin.isTTY) {
4270
4516
  const { confirm: confirm4 } = await import("@inquirer/prompts");
4271
4517
  const wantUpdate = await confirm4({
@@ -4274,61 +4520,114 @@ async function startCommand() {
4274
4520
  });
4275
4521
  if (wantUpdate) {
4276
4522
  const { execFileSync: execFileSync7 } = await import("node:child_process");
4277
- console.log(chalk23.blue("\n Installing...\n"));
4523
+ console.log(chalk24.blue("\n Installing...\n"));
4278
4524
  try {
4279
4525
  execFileSync7("npm", ["install", "-g", "md4ai"], {
4280
4526
  stdio: "inherit",
4281
4527
  timeout: 6e4
4282
4528
  });
4283
- console.log(chalk23.green(" Updated successfully."));
4284
- console.log(chalk23.dim(" Restarting with the new version...\n"));
4529
+ console.log(chalk24.green(" Updated successfully."));
4530
+ console.log(chalk24.dim(" Restarting with the new version...\n"));
4285
4531
  const { spawn: spawn2 } = await import("node:child_process");
4286
4532
  const child = spawn2("md4ai", ["start"], { stdio: "inherit", shell: false });
4287
4533
  child.on("exit", (code) => process.exit(code ?? 0));
4288
4534
  return;
4289
4535
  } catch {
4290
- console.log(chalk23.yellow(" Update failed \u2014 continuing with current version.\n"));
4536
+ console.log(chalk24.yellow(" Update failed \u2014 continuing with current version.\n"));
4291
4537
  }
4292
4538
  } else {
4293
4539
  console.log("");
4294
4540
  }
4295
4541
  } else {
4296
- console.log(chalk23.dim(" Run md4ai update to install it.\n"));
4542
+ console.log(chalk24.dim(" Run md4ai update to install it.\n"));
4297
4543
  }
4298
4544
  } else if (latest) {
4299
- console.log(chalk23.green(" You're on the latest version.\n"));
4545
+ console.log(chalk24.green(" You're on the latest version.\n"));
4300
4546
  } else {
4301
- console.log(chalk23.dim(" Could not reach npm registry \u2014 skipping.\n"));
4547
+ console.log(chalk24.dim(" Could not reach npm registry \u2014 skipping.\n"));
4302
4548
  }
4303
- console.log(chalk23.blue(" \u2461 Scanning project files..."));
4549
+ console.log(chalk24.blue(" \u2461 Scanning project files..."));
4304
4550
  console.log("");
4305
4551
  await mapCommand(projectRoot, {});
4306
4552
  if (!process.stdin.isTTY) {
4307
- console.log(chalk23.dim("\n Non-interactive mode \u2014 skipping MCP monitor."));
4553
+ console.log(chalk24.dim("\n Non-interactive mode \u2014 skipping MCP monitor."));
4308
4554
  return;
4309
4555
  }
4310
4556
  console.log("");
4311
- console.log(chalk23.blue(" \u2462 Starting MCP monitor..."));
4557
+ console.log(chalk24.blue(" \u2462 Starting MCP monitor..."));
4312
4558
  console.log("");
4313
4559
  await mcpWatchCommand();
4314
4560
  }
4315
4561
 
4316
4562
  // dist/commands/config.js
4317
4563
  init_config();
4318
- import chalk24 from "chalk";
4319
- var ALLOWED_KEYS = ["vercel-token"];
4564
+ import chalk25 from "chalk";
4565
+ var ALLOWED_KEYS = ["vercel-token", "doppler-token"];
4320
4566
  var KEY_MAP = {
4321
- "vercel-token": "vercelToken"
4567
+ "vercel-token": "vercelToken",
4568
+ "doppler-token": "dopplerToken"
4322
4569
  };
4323
4570
  async function configSetCommand(key, value) {
4324
4571
  if (!ALLOWED_KEYS.includes(key)) {
4325
- console.error(chalk24.red(`Unknown config key: ${key}`));
4572
+ console.error(chalk25.red(`Unknown config key: ${key}`));
4326
4573
  console.log(` Allowed keys: ${ALLOWED_KEYS.join(", ")}`);
4327
4574
  process.exit(1);
4328
4575
  }
4329
4576
  const credKey = KEY_MAP[key];
4330
4577
  await mergeCredentials({ [credKey]: value });
4331
- console.log(chalk24.green(`Saved ${key}.`));
4578
+ console.log(chalk25.green(`Saved ${key}.`));
4579
+ }
4580
+
4581
+ // dist/commands/doppler.js
4582
+ init_config();
4583
+ init_api();
4584
+ import chalk26 from "chalk";
4585
+ import { input as input7 } from "@inquirer/prompts";
4586
+ async function dopplerConnectCommand() {
4587
+ const token = await input7({
4588
+ message: "Enter your Doppler service account token:",
4589
+ transformer: (val) => val.replace(/./g, "*"),
4590
+ validate: (val) => val.length > 0 || "Token is required"
4591
+ });
4592
+ console.log(chalk26.dim(" Validating token..."));
4593
+ const { valid, projectCount } = await validateDopplerToken(token);
4594
+ if (!valid) {
4595
+ console.error(chalk26.red("Token is invalid or has expired."));
4596
+ console.log(chalk26.yellow("Generate a service account token at https://dashboard.doppler.com"));
4597
+ process.exit(1);
4598
+ }
4599
+ await mergeCredentials({ dopplerToken: token });
4600
+ console.log(chalk26.green(`Token validated \u2014 ${projectCount} project${projectCount !== 1 ? "s" : ""} accessible.`));
4601
+ console.log(chalk26.green("Saved to ~/.md4ai/credentials.json"));
4602
+ console.log(chalk26.dim("Doppler will be checked automatically on your next scan."));
4603
+ }
4604
+ async function dopplerDisconnectCommand() {
4605
+ const creds = await loadCredentials();
4606
+ if (!creds?.dopplerToken) {
4607
+ console.log(chalk26.yellow("No Doppler token configured."));
4608
+ return;
4609
+ }
4610
+ const { dopplerToken: _, ...rest } = creds;
4611
+ const { writeFile: writeFile6, chmod: chmod2 } = await import("node:fs/promises");
4612
+ const { join: join17 } = await import("node:path");
4613
+ const { homedir: homedir10 } = await import("node:os");
4614
+ const credPath = join17(homedir10(), ".md4ai", "credentials.json");
4615
+ await writeFile6(credPath, JSON.stringify(rest, null, 2), "utf-8");
4616
+ await chmod2(credPath, 384);
4617
+ console.log(chalk26.green("Doppler token removed."));
4618
+ }
4619
+ async function dopplerSetProjectCommand(slug) {
4620
+ const state = await loadState();
4621
+ const folderId = state.lastFolderId;
4622
+ if (!folderId) {
4623
+ console.error(chalk26.red('No project linked yet. Run "md4ai scan" first to link a project.'));
4624
+ process.exit(1);
4625
+ }
4626
+ const dopplerProjects = state.dopplerProjects ?? {};
4627
+ dopplerProjects[folderId] = slug;
4628
+ await saveState({ dopplerProjects });
4629
+ console.log(chalk26.green(`Doppler project "${slug}" linked to folder ${folderId.slice(0, 8)}...`));
4630
+ console.log(chalk26.dim("This override will be used instead of doppler.yaml."));
4332
4631
  }
4333
4632
 
4334
4633
  // dist/index.js
@@ -4357,6 +4656,10 @@ program.command("mcp-watch").description("Monitor MCP server status on this devi
4357
4656
  program.command("init-manifest").description("Scaffold a starter env-manifest.md from detected .env files").action(initManifestCommand);
4358
4657
  var config = program.command("config").description("Manage CLI configuration");
4359
4658
  config.command("set <key> <value>").description("Set a configuration value (e.g. vercel-token)").action(configSetCommand);
4659
+ var doppler = program.command("doppler").description("Manage Doppler integration");
4660
+ doppler.command("connect").description("Configure Doppler token for secrets scanning").action(dopplerConnectCommand);
4661
+ doppler.command("disconnect").description("Remove stored Doppler token").action(dopplerDisconnectCommand);
4662
+ doppler.command("set-project <slug>").description("Override Doppler project slug for the current linked project").action(dopplerSetProjectCommand);
4360
4663
  var admin = program.command("admin").description("Admin commands for managing the tools registry");
4361
4664
  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
4665
  admin.command("list-tools").description("List all tools in the master registry").action(adminListToolsCommand);