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.
- package/dist/index.bundled.js +570 -267
- package/package.json +2 -2
package/dist/index.bundled.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
1597
|
-
import { existsSync as
|
|
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 :
|
|
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 =
|
|
1659
|
-
if (
|
|
1896
|
+
const claudeDir = join12(projectRoot, ".claude");
|
|
1897
|
+
if (existsSync7(claudeDir)) {
|
|
1660
1898
|
await walkDir(claudeDir, projectRoot, files);
|
|
1661
1899
|
}
|
|
1662
|
-
if (
|
|
1900
|
+
if (existsSync7(join12(projectRoot, "CLAUDE.md"))) {
|
|
1663
1901
|
files.push("CLAUDE.md");
|
|
1664
1902
|
}
|
|
1665
|
-
if (
|
|
1903
|
+
if (existsSync7(join12(projectRoot, "skills.md"))) {
|
|
1666
1904
|
files.push("skills.md");
|
|
1667
1905
|
}
|
|
1668
|
-
const plansDir =
|
|
1669
|
-
if (
|
|
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 =
|
|
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 (
|
|
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:
|
|
1705
|
-
const { existsSync:
|
|
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 =
|
|
1716
|
-
if (!
|
|
1953
|
+
const fullPath = join12(projectRoot, relPath);
|
|
1954
|
+
if (!existsSync15(fullPath))
|
|
1717
1955
|
continue;
|
|
1718
1956
|
try {
|
|
1719
|
-
const content = await
|
|
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 =
|
|
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
|
|
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(
|
|
2206
|
+
console.error(chalk11.yellow(`Health results warning: ${error.message}`));
|
|
1968
2207
|
} else {
|
|
1969
|
-
console.log(
|
|
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
|
|
2041
|
-
import
|
|
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 (!
|
|
2047
|
-
console.error(
|
|
2285
|
+
if (!existsSync8(projectRoot)) {
|
|
2286
|
+
console.error(chalk12.red(`Path not found: ${projectRoot}`));
|
|
2048
2287
|
process.exit(1);
|
|
2049
2288
|
}
|
|
2050
|
-
console.log(
|
|
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(
|
|
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(
|
|
2307
|
+
console.log(chalk12.red(` ${br.from} -> ${br.to}`));
|
|
2068
2308
|
}
|
|
2069
2309
|
if (result.brokenRefs.length > 5) {
|
|
2070
|
-
console.log(
|
|
2310
|
+
console.log(chalk12.red(` ... and ${result.brokenRefs.length - 5} more`));
|
|
2071
2311
|
}
|
|
2072
2312
|
}
|
|
2073
2313
|
const outputDir = resolve4(projectRoot, "output");
|
|
2074
|
-
if (!
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2352
|
+
console.log(chalk12.green(` Deleted: ${file.file_path}`));
|
|
2113
2353
|
} catch (err) {
|
|
2114
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2164
|
-
console.log(
|
|
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(
|
|
2422
|
+
console.log(chalk12.green(` Uploaded ${configFiles.length} config file(s).`));
|
|
2180
2423
|
}
|
|
2181
2424
|
}
|
|
2182
2425
|
} else {
|
|
2183
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
2453
|
+
console.error(chalk12.red(`Failed to create project: ${createErr?.message}`));
|
|
2211
2454
|
return;
|
|
2212
2455
|
}
|
|
2213
2456
|
folderId = newFolder.id;
|
|
2214
|
-
console.log(
|
|
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(
|
|
2265
|
-
console.log(
|
|
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(
|
|
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
|
|
2296
|
-
import
|
|
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("..") &&
|
|
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(
|
|
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(
|
|
2555
|
+
console.error(chalk15.red(` Skipping invalid path: ${device.path}`));
|
|
2312
2556
|
continue;
|
|
2313
2557
|
}
|
|
2314
|
-
console.log(
|
|
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(
|
|
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(
|
|
2577
|
+
console.log(chalk15.green(` Done: ${device.device_name}`));
|
|
2334
2578
|
} catch (err) {
|
|
2335
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2596
|
+
console.error(chalk15.red(`Invalid project path: ${device.path}`));
|
|
2353
2597
|
process.exit(1);
|
|
2354
2598
|
}
|
|
2355
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
|
2388
|
-
import { join as
|
|
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
|
|
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 (!
|
|
2638
|
+
if (!existsSync13(path))
|
|
2395
2639
|
return null;
|
|
2396
|
-
const raw = await
|
|
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(
|
|
2702
|
+
const userConfig = await readJsonSafe(join15(home, ".claude.json"));
|
|
2459
2703
|
entries.push(...parseServers(userConfig, "global"));
|
|
2460
|
-
const globalMcp = await readJsonSafe(
|
|
2704
|
+
const globalMcp = await readJsonSafe(join15(home, ".claude", "mcp.json"));
|
|
2461
2705
|
entries.push(...parseServers(globalMcp, "global"));
|
|
2462
|
-
const cwdMcp = await readJsonSafe(
|
|
2706
|
+
const cwdMcp = await readJsonSafe(join15(process.cwd(), ".mcp.json"));
|
|
2463
2707
|
entries.push(...parseServers(cwdMcp, "project"));
|
|
2464
|
-
const pluginsBase =
|
|
2465
|
-
if (
|
|
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 =
|
|
2472
|
-
if (!
|
|
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(
|
|
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 =
|
|
2486
|
-
if (
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
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(
|
|
2806
|
-
MCP Monitor v${CURRENT_VERSION} \u2014 ${deviceName}`) +
|
|
2807
|
-
console.log(
|
|
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(
|
|
2813
|
-
console.log(
|
|
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(
|
|
2816
|
-
console.log(
|
|
2817
|
-
console.log(
|
|
2818
|
-
console.log(
|
|
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(
|
|
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 =
|
|
2843
|
-
console.log(` ${
|
|
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(
|
|
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(
|
|
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(` ${
|
|
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(
|
|
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" ?
|
|
2863
|
-
const source =
|
|
2864
|
-
const detail = 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(
|
|
2871
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
3157
|
+
console.log(chalk21.dim(" Previous watcher stopped.\n"));
|
|
2914
3158
|
}
|
|
2915
3159
|
process.stdout.write(`\x1B]0;MCP mon\x07`);
|
|
2916
|
-
console.log(
|
|
3160
|
+
console.log(chalk21.blue(`Starting MCP monitor for ${deviceName}...`));
|
|
2917
3161
|
console.log("");
|
|
2918
|
-
console.log(
|
|
2919
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3007
|
-
console.log(
|
|
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(
|
|
3010
|
-
console.log(
|
|
3011
|
-
console.log(
|
|
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(
|
|
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
|
|
3363
|
-
import { existsSync as
|
|
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
|
|
3609
|
+
import chalk13 from "chalk";
|
|
3366
3610
|
async function simulateCommand(prompt) {
|
|
3367
3611
|
const projectRoot = process.cwd();
|
|
3368
|
-
console.log(
|
|
3612
|
+
console.log(chalk13.blue(`Simulating prompt: "${prompt}"
|
|
3369
3613
|
`));
|
|
3370
|
-
console.log(
|
|
3371
|
-
const globalClaude =
|
|
3372
|
-
if (
|
|
3373
|
-
console.log(
|
|
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 =
|
|
3377
|
-
if (
|
|
3378
|
-
console.log(
|
|
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
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
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 (
|
|
3632
|
+
if (existsSync9(sf)) {
|
|
3389
3633
|
const display = sf.startsWith(homedir8()) ? sf.replace(homedir8(), "~") : sf.replace(projectRoot + "/", "");
|
|
3390
|
-
console.log(
|
|
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 =
|
|
3397
|
-
if (
|
|
3398
|
-
console.log(
|
|
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(
|
|
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
|
|
3406
|
-
import { readFile as
|
|
3407
|
-
import { existsSync as
|
|
3408
|
-
import
|
|
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 =
|
|
3412
|
-
if (!
|
|
3413
|
-
console.error(
|
|
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
|
|
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(
|
|
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 =
|
|
3668
|
+
const outputPath = join14(projectRoot, "output", `print-${Date.now()}.html`);
|
|
3425
3669
|
await writeFile3(outputPath, printHtml, "utf-8");
|
|
3426
|
-
console.log(
|
|
3670
|
+
console.log(chalk14.green(`Print-ready wall sheet: ${outputPath}`));
|
|
3427
3671
|
}
|
|
3428
3672
|
function escapeHtml2(text) {
|
|
3429
3673
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -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
|
|
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(
|
|
3501
|
-
console.error(
|
|
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(
|
|
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(
|
|
3772
|
+
console.error(chalk16.red(`Failed to link: ${pathErr.message}`));
|
|
3529
3773
|
process.exit(1);
|
|
3530
3774
|
}
|
|
3531
3775
|
}
|
|
3532
|
-
console.log(
|
|
3533
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
3579
|
-
console.log(
|
|
3824
|
+
console.log(chalk16.green("\nDone! Project linked and scanned."));
|
|
3825
|
+
console.log(chalk16.cyan(`
|
|
3580
3826
|
${projectUrl}
|
|
3581
3827
|
`));
|
|
3582
|
-
console.log(
|
|
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
|
|
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
|
|
3589
|
-
import
|
|
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 (!
|
|
3594
|
-
console.error(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
3871
|
+
console.log(chalk17.yellow("No Claude config files found in bundle."));
|
|
3626
3872
|
return;
|
|
3627
3873
|
}
|
|
3628
|
-
console.log(
|
|
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(
|
|
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(
|
|
3894
|
+
console.error(chalk17.red(` Blocked path traversal: ${file.filePath}`));
|
|
3649
3895
|
continue;
|
|
3650
3896
|
}
|
|
3651
3897
|
const dir = dirname3(fullPath);
|
|
3652
|
-
if (!
|
|
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(
|
|
3902
|
+
console.log(chalk17.green(` \u2713 ${file.filePath}`));
|
|
3657
3903
|
}
|
|
3658
|
-
console.log(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
3941
|
+
console.error(chalk18.red(`Failed to update: ${error.message}`));
|
|
3696
3942
|
process.exit(1);
|
|
3697
3943
|
}
|
|
3698
|
-
console.log(
|
|
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(
|
|
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(
|
|
3961
|
+
console.error(chalk18.red(`Failed to create: ${error.message}`));
|
|
3716
3962
|
process.exit(1);
|
|
3717
3963
|
}
|
|
3718
|
-
console.log(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
4086
|
+
console.log(chalk20.yellow("No tools to fetch."));
|
|
3841
4087
|
return;
|
|
3842
4088
|
}
|
|
3843
|
-
console.log(
|
|
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(
|
|
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" ?
|
|
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(
|
|
3899
|
-
${results.length} tool(s): `) +
|
|
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
|
|
3958
|
-
import { readFile as
|
|
3959
|
-
import { existsSync as
|
|
3960
|
-
import
|
|
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 =
|
|
3977
|
-
if (
|
|
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 =
|
|
3988
|
-
if (!
|
|
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 =
|
|
3992
|
-
if (
|
|
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
|
|
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 =
|
|
4054
|
-
if (
|
|
4055
|
-
console.log(
|
|
4056
|
-
console.log(
|
|
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(
|
|
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(
|
|
4063
|
-
console.log(
|
|
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(` ${
|
|
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 (!
|
|
4317
|
+
if (!existsSync14(dir)) {
|
|
4072
4318
|
await mkdir4(dir, { recursive: true });
|
|
4073
4319
|
}
|
|
4074
4320
|
await writeFile5(manifestPath, content, "utf-8");
|
|
4075
|
-
console.log(
|
|
4321
|
+
console.log(chalk22.green(`
|
|
4076
4322
|
Manifest created: ${relative4(projectRoot, manifestPath)}`));
|
|
4077
|
-
console.log(
|
|
4078
|
-
console.log(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
4438
|
+
console.log(chalk23.blue(`
|
|
4193
4439
|
Current version: v${CURRENT_VERSION}`));
|
|
4194
|
-
console.log(
|
|
4440
|
+
console.log(chalk23.dim(" Checking for updates...\n"));
|
|
4195
4441
|
const latest = await fetchLatestVersion();
|
|
4196
4442
|
if (!latest) {
|
|
4197
|
-
console.log(
|
|
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(
|
|
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(
|
|
4458
|
+
console.log(chalk23.dim("\nUpdate skipped.\n"));
|
|
4213
4459
|
return;
|
|
4214
4460
|
}
|
|
4215
|
-
console.log(
|
|
4461
|
+
console.log(chalk23.blue("\n Installing...\n"));
|
|
4216
4462
|
const ok = runNpmInstall();
|
|
4217
4463
|
if (!ok) {
|
|
4218
|
-
console.log(
|
|
4219
|
-
console.log(
|
|
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(
|
|
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
|
|
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(
|
|
4263
|
-
console.log(
|
|
4508
|
+
console.log(chalk24.bold.cyan(` MD4AI v${CURRENT_VERSION}`));
|
|
4509
|
+
console.log(chalk24.dim(` ${projectRoot}`));
|
|
4264
4510
|
console.log("");
|
|
4265
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
4284
|
-
console.log(
|
|
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(
|
|
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(
|
|
4542
|
+
console.log(chalk24.dim(" Run md4ai update to install it.\n"));
|
|
4297
4543
|
}
|
|
4298
4544
|
} else if (latest) {
|
|
4299
|
-
console.log(
|
|
4545
|
+
console.log(chalk24.green(" You're on the latest version.\n"));
|
|
4300
4546
|
} else {
|
|
4301
|
-
console.log(
|
|
4547
|
+
console.log(chalk24.dim(" Could not reach npm registry \u2014 skipping.\n"));
|
|
4302
4548
|
}
|
|
4303
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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);
|