md4ai 0.7.6 → 0.7.8

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 +106 -19
  2. package/package.json +1 -1
@@ -1232,7 +1232,7 @@ var CURRENT_VERSION;
1232
1232
  var init_check_update = __esm({
1233
1233
  "dist/check-update.js"() {
1234
1234
  "use strict";
1235
- CURRENT_VERSION = true ? "0.7.6" : "0.0.0-dev";
1235
+ CURRENT_VERSION = true ? "0.7.8" : "0.0.0-dev";
1236
1236
  }
1237
1237
  });
1238
1238
 
@@ -1563,15 +1563,24 @@ function findProcessesForConfig(config, processes) {
1563
1563
  return [];
1564
1564
  const packageName = config.args ? extractPackageName(config.args) : null;
1565
1565
  const matches = [];
1566
- for (const proc of processes) {
1567
- let matched = false;
1568
- if (packageName && proc.args.includes(packageName)) {
1569
- matched = true;
1570
- } else if (config.command === "node" && config.args?.[0]) {
1571
- if (proc.args.includes(config.args[0])) {
1572
- matched = true;
1573
- }
1566
+ const searchTerms = [];
1567
+ if (packageName)
1568
+ searchTerms.push(packageName);
1569
+ if (config.command === "node" && config.args?.[0]) {
1570
+ searchTerms.push(config.args[0]);
1571
+ }
1572
+ if (config.name) {
1573
+ searchTerms.push(`mcp-server-${config.name}`);
1574
+ searchTerms.push(`${config.name}-mcp`);
1575
+ }
1576
+ if ((config.command === "uvx" || config.command === "pipx") && config.args) {
1577
+ for (const arg of config.args) {
1578
+ if (!arg.startsWith("-") && arg !== "run")
1579
+ searchTerms.push(arg);
1574
1580
  }
1581
+ }
1582
+ for (const proc of processes) {
1583
+ const matched = searchTerms.some((term) => proc.args.includes(term));
1575
1584
  if (matched) {
1576
1585
  matches.push({
1577
1586
  pid: proc.pid,
@@ -1582,7 +1591,15 @@ function findProcessesForConfig(config, processes) {
1582
1591
  });
1583
1592
  }
1584
1593
  }
1585
- return matches;
1594
+ const byTty = /* @__PURE__ */ new Map();
1595
+ for (const m of matches) {
1596
+ const key = m.tty || `pid-${m.pid}`;
1597
+ const existing = byTty.get(key);
1598
+ if (!existing || m.memoryMb > existing.memoryMb) {
1599
+ byTty.set(key, m);
1600
+ }
1601
+ }
1602
+ return Array.from(byTty.values());
1586
1603
  }
1587
1604
  function getProcessTable() {
1588
1605
  try {
@@ -1623,16 +1640,37 @@ function detectTty() {
1623
1640
  }
1624
1641
  function checkEnvVars(config) {
1625
1642
  const required = config.env ? Object.keys(config.env) : [];
1626
- const missing = required.filter((key) => !process.env[key]);
1643
+ const missing = required.filter((key) => {
1644
+ const configValue = config.env?.[key];
1645
+ const hasConfigValue = configValue && !configValue.startsWith("${");
1646
+ return !hasConfigValue && !process.env[key];
1647
+ });
1627
1648
  return { required, missing };
1628
1649
  }
1629
- function buildRows(configs) {
1650
+ async function checkHttpServer(url) {
1651
+ try {
1652
+ const controller = new AbortController();
1653
+ const timeout = setTimeout(() => controller.abort(), 3e3);
1654
+ const res = await fetch(url, {
1655
+ method: "HEAD",
1656
+ signal: controller.signal
1657
+ });
1658
+ clearTimeout(timeout);
1659
+ return "reachable";
1660
+ } catch {
1661
+ return "unreachable";
1662
+ }
1663
+ }
1664
+ function buildRows(configs, httpResults) {
1630
1665
  const processes = getProcessTable();
1631
1666
  const rows = [];
1632
1667
  for (const config of configs) {
1633
1668
  const { required, missing } = checkEnvVars(config);
1634
1669
  const packageName = config.args ? extractPackageName(config.args) : null;
1635
1670
  if (config.type === "http") {
1671
+ const reachability = httpResults.get(config.name) ?? "unknown";
1672
+ const status = reachability === "reachable" ? "running" : "stopped";
1673
+ const detail = reachability === "reachable" ? "HTTP \u2014 remote service reachable" : reachability === "unreachable" ? "HTTP \u2014 remote service unreachable" : "HTTP \u2014 could not verify";
1636
1674
  rows.push({
1637
1675
  server_name: config.name,
1638
1676
  config_source: config.source,
@@ -1640,14 +1678,14 @@ function buildRows(configs) {
1640
1678
  command: null,
1641
1679
  package_name: null,
1642
1680
  http_url: config.url ?? null,
1643
- status: "stopped",
1681
+ status,
1644
1682
  pid: null,
1645
1683
  session_tty: null,
1646
1684
  uptime_seconds: null,
1647
1685
  memory_mb: null,
1648
1686
  env_vars_required: required.length ? required : null,
1649
1687
  env_vars_missing: missing.length ? missing : null,
1650
- error_detail: "HTTP server \u2014 cannot verify from CLI"
1688
+ error_detail: detail
1651
1689
  });
1652
1690
  continue;
1653
1691
  }
@@ -1717,11 +1755,12 @@ function printTable(rows, deviceName) {
1717
1755
  MCP Monitor \u2014 ${deviceName}`));
1718
1756
  console.log(chalk18.dim(` ${(/* @__PURE__ */ new Date()).toLocaleTimeString()} \xB7 refreshes every 30s \xB7 Ctrl+C to stop
1719
1757
  `));
1720
- const running = rows.filter((r) => r.status === "running");
1758
+ const runningLocal = rows.filter((r) => r.status === "running" && r.server_type !== "http");
1759
+ const runningHttp = rows.filter((r) => r.status === "running" && r.server_type === "http");
1721
1760
  const stopped = rows.filter((r) => r.status === "stopped");
1722
1761
  const errored = rows.filter((r) => r.status === "error");
1723
1762
  const byTty = /* @__PURE__ */ new Map();
1724
- for (const r of running) {
1763
+ for (const r of runningLocal) {
1725
1764
  const key = r.session_tty ?? "unknown";
1726
1765
  const list = byTty.get(key) ?? [];
1727
1766
  list.push(r);
@@ -1738,6 +1777,13 @@ function printTable(rows, deviceName) {
1738
1777
  console.log("");
1739
1778
  }
1740
1779
  }
1780
+ if (runningHttp.length > 0) {
1781
+ console.log(chalk18.blue(` Remote Services (${runningHttp.length})`) + chalk18.dim(" \u2014 HTTP endpoints reachable"));
1782
+ for (const s of runningHttp) {
1783
+ console.log(` ${chalk18.blue("\u25CF")} ${s.server_name.padEnd(20)} ${chalk18.dim((s.http_url ?? "").padEnd(30))}`);
1784
+ }
1785
+ console.log("");
1786
+ }
1741
1787
  if (stopped.length > 0 || errored.length > 0) {
1742
1788
  const notRunning = [...stopped, ...errored];
1743
1789
  console.log(chalk18.yellow(` Not Running (${notRunning.length})`));
@@ -1753,6 +1799,14 @@ function printTable(rows, deviceName) {
1753
1799
  console.log(chalk18.dim(" Configure servers in ~/.claude/mcp.json or .mcp.json\n"));
1754
1800
  }
1755
1801
  }
1802
+ function timeAgo(dateStr) {
1803
+ const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1e3);
1804
+ if (diff < 60)
1805
+ return `${diff}s ago`;
1806
+ if (diff < 3600)
1807
+ return `${Math.floor(diff / 60)}m ago`;
1808
+ return `${Math.floor(diff / 3600)}h ago`;
1809
+ }
1756
1810
  function formatUptime(seconds) {
1757
1811
  if (seconds < 60)
1758
1812
  return `${seconds}s`;
@@ -1768,6 +1822,35 @@ async function mcpWatchCommand() {
1768
1822
  const deviceName = detectDeviceName();
1769
1823
  const myPid = process.pid;
1770
1824
  const myTty = detectTty();
1825
+ const staleThreshold = new Date(Date.now() - 12e4).toISOString();
1826
+ await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).lt("last_heartbeat", staleThreshold);
1827
+ const { data: existingWatchers } = await supabase.from("mcp_watchers").select("pid, tty, cli_version, started_at").eq("device_id", deviceId);
1828
+ if (existingWatchers && existingWatchers.length > 0) {
1829
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
1830
+ console.log("");
1831
+ console.log(chalk18.yellow(" Another watcher is already running on this device:"));
1832
+ for (const w of existingWatchers) {
1833
+ console.log(chalk18.dim(` PID ${w.pid} \xB7 ${w.tty ?? "unknown tty"} \xB7 v${w.cli_version} \xB7 started ${timeAgo(w.started_at)}`));
1834
+ }
1835
+ console.log("");
1836
+ const takeOver = await confirm2({
1837
+ message: "Stop the existing watcher and start a new one?",
1838
+ default: true
1839
+ });
1840
+ if (!takeOver) {
1841
+ console.log(chalk18.dim("\nKeeping existing watcher. Exiting.\n"));
1842
+ return;
1843
+ }
1844
+ for (const w of existingWatchers) {
1845
+ try {
1846
+ process.kill(w.pid, "SIGTERM");
1847
+ } catch {
1848
+ }
1849
+ }
1850
+ await supabase.from("mcp_watchers").delete().eq("device_id", deviceId);
1851
+ await new Promise((r) => setTimeout(r, 1e3));
1852
+ console.log(chalk18.dim(" Previous watcher stopped.\n"));
1853
+ }
1771
1854
  process.stdout.write(`\x1B]0;${deviceName} MCP Monitor\x07`);
1772
1855
  console.log(chalk18.blue(`Starting MCP monitor for ${deviceName}...`));
1773
1856
  console.log("");
@@ -1784,11 +1867,15 @@ async function mcpWatchCommand() {
1784
1867
  started_at: (/* @__PURE__ */ new Date()).toISOString(),
1785
1868
  last_heartbeat: (/* @__PURE__ */ new Date()).toISOString()
1786
1869
  }, { onConflict: "device_id,pid" });
1787
- const staleThreshold = new Date(Date.now() - 12e4).toISOString();
1788
- await supabase.from("mcp_watchers").delete().eq("device_id", deviceId).lt("last_heartbeat", staleThreshold);
1789
1870
  async function cycle() {
1790
1871
  const configs = await readAllMcpConfigs();
1791
- const rows = buildRows(configs);
1872
+ const httpConfigs = configs.filter((c) => c.type === "http" && c.url);
1873
+ const httpResults = /* @__PURE__ */ new Map();
1874
+ await Promise.all(httpConfigs.map(async (c) => {
1875
+ const result = await checkHttpServer(c.url);
1876
+ httpResults.set(c.name, result);
1877
+ }));
1878
+ const rows = buildRows(configs, httpResults);
1792
1879
  const now = (/* @__PURE__ */ new Date()).toISOString();
1793
1880
  await supabase.from("mcp_server_status").delete().eq("device_id", deviceId);
1794
1881
  if (rows.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md4ai",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "description": "CLI for MD4AI — scan Claude projects and sync to your dashboard",
5
5
  "type": "module",
6
6
  "bin": {