rrce-workflow 0.2.29 → 0.2.31

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.js +302 -165
  2. package/package.json +7 -1
package/dist/index.js CHANGED
@@ -721,6 +721,17 @@ var init_types = __esm({
721
721
  });
722
722
 
723
723
  // src/mcp/config.ts
724
+ var config_exports = {};
725
+ __export(config_exports, {
726
+ ensureMCPGlobalPath: () => ensureMCPGlobalPath,
727
+ getMCPConfigPath: () => getMCPConfigPath,
728
+ getProjectPermissions: () => getProjectPermissions,
729
+ isProjectExposed: () => isProjectExposed,
730
+ loadMCPConfig: () => loadMCPConfig,
731
+ removeProjectConfig: () => removeProjectConfig,
732
+ saveMCPConfig: () => saveMCPConfig,
733
+ setProjectConfig: () => setProjectConfig
734
+ });
724
735
  import * as fs7 from "fs";
725
736
  import * as path8 from "path";
726
737
  function getMCPConfigPath() {
@@ -907,6 +918,10 @@ function setProjectConfig(config, name, expose, permissions) {
907
918
  }
908
919
  return config;
909
920
  }
921
+ function removeProjectConfig(config, name) {
922
+ config.projects = config.projects.filter((p) => p.name !== name);
923
+ return config;
924
+ }
910
925
  function isProjectExposed(config, name) {
911
926
  const project = config.projects.find((p) => p.name === name);
912
927
  if (project) {
@@ -1160,7 +1175,7 @@ import {
1160
1175
  ListPromptsRequestSchema,
1161
1176
  GetPromptRequestSchema
1162
1177
  } from "@modelcontextprotocol/sdk/types.js";
1163
- async function startMCPServer() {
1178
+ async function startMCPServer(options = {}) {
1164
1179
  try {
1165
1180
  logger.info("Starting MCP Server...");
1166
1181
  process.on("uncaughtException", (error) => {
@@ -1182,8 +1197,12 @@ async function startMCPServer() {
1182
1197
  registerResourceHandlers(mcpServer);
1183
1198
  registerToolHandlers(mcpServer);
1184
1199
  registerPromptHandlers(mcpServer);
1185
- const transport = new StdioServerTransport();
1186
- await mcpServer.connect(transport);
1200
+ if (!options.interactive) {
1201
+ const transport = new StdioServerTransport();
1202
+ await mcpServer.connect(transport);
1203
+ } else {
1204
+ logger.info("Running in interactive mode (Stdio transport detached)");
1205
+ }
1187
1206
  serverState = { running: true, port: config.server.port, pid: process.pid };
1188
1207
  const exposed = getExposedProjects().map((p) => p.name).join(", ");
1189
1208
  logger.info(`RRCE MCP Hub started (pid: ${process.pid})`, { exposedProjects: exposed });
@@ -1683,6 +1702,226 @@ var init_install = __esm({
1683
1702
  }
1684
1703
  });
1685
1704
 
1705
+ // src/mcp/ui/Header.tsx
1706
+ import "react";
1707
+ import { Box, Text } from "ink";
1708
+ import { jsx } from "react/jsx-runtime";
1709
+ var Header;
1710
+ var init_Header = __esm({
1711
+ "src/mcp/ui/Header.tsx"() {
1712
+ "use strict";
1713
+ Header = () => /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingBottom: 1, children: /* @__PURE__ */ jsx(Box, { borderStyle: "double", borderColor: "cyan", paddingX: 2, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: " RRCE MCP Hub " }) }) });
1714
+ }
1715
+ });
1716
+
1717
+ // src/mcp/ui/StatusBoard.tsx
1718
+ import "react";
1719
+ import { Box as Box2, Text as Text2 } from "ink";
1720
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1721
+ var StatusBoard;
1722
+ var init_StatusBoard = __esm({
1723
+ "src/mcp/ui/StatusBoard.tsx"() {
1724
+ "use strict";
1725
+ StatusBoard = ({ exposedLabel, port, pid }) => {
1726
+ return /* @__PURE__ */ jsx2(Box2, { borderStyle: "single", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsxs(Text2, { children: [
1727
+ "\u{1F4CB} ",
1728
+ /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: exposedLabel }),
1729
+ " ",
1730
+ "\u2502",
1731
+ " Port: ",
1732
+ /* @__PURE__ */ jsx2(Text2, { color: "green", children: port }),
1733
+ " ",
1734
+ "\u2502",
1735
+ " PID: ",
1736
+ /* @__PURE__ */ jsx2(Text2, { color: "green", children: pid })
1737
+ ] }) });
1738
+ };
1739
+ }
1740
+ });
1741
+
1742
+ // src/mcp/ui/LogViewer.tsx
1743
+ import "react";
1744
+ import { Box as Box3, Text as Text3 } from "ink";
1745
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1746
+ var LogViewer;
1747
+ var init_LogViewer = __esm({
1748
+ "src/mcp/ui/LogViewer.tsx"() {
1749
+ "use strict";
1750
+ LogViewer = ({ logs, height }) => {
1751
+ const visibleLogs = logs.slice(-height);
1752
+ const emptyLines = Math.max(0, height - visibleLogs.length);
1753
+ const padding = Array(emptyLines).fill("");
1754
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", borderStyle: "round", borderColor: "dim", paddingX: 1, height: height + 2, flexGrow: 1, children: [
1755
+ padding.map((_, i) => /* @__PURE__ */ jsx3(Text3, { children: " " }, `empty-${i}`)),
1756
+ visibleLogs.map((log, i) => /* @__PURE__ */ jsx3(Text3, { wrap: "truncate-end", children: log }, `log-${i}`))
1757
+ ] });
1758
+ };
1759
+ }
1760
+ });
1761
+
1762
+ // src/mcp/ui/CommandBar.tsx
1763
+ import "react";
1764
+ import { Box as Box4, Text as Text4 } from "ink";
1765
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1766
+ var CommandBar;
1767
+ var init_CommandBar = __esm({
1768
+ "src/mcp/ui/CommandBar.tsx"() {
1769
+ "use strict";
1770
+ CommandBar = () => {
1771
+ return /* @__PURE__ */ jsx4(Box4, { borderStyle: "single", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsxs3(Text4, { children: [
1772
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: "q" }),
1773
+ ":Quit ",
1774
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: "p" }),
1775
+ ":Projects ",
1776
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: "i" }),
1777
+ ":Install ",
1778
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: "r" }),
1779
+ ":Reload ",
1780
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: "c" }),
1781
+ ":Clear ",
1782
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: "?" }),
1783
+ ":Help"
1784
+ ] }) });
1785
+ };
1786
+ }
1787
+ });
1788
+
1789
+ // src/mcp/ui/Dashboard.tsx
1790
+ import "react";
1791
+ import { Box as Box5 } from "ink";
1792
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1793
+ var Dashboard;
1794
+ var init_Dashboard = __esm({
1795
+ "src/mcp/ui/Dashboard.tsx"() {
1796
+ "use strict";
1797
+ init_Header();
1798
+ init_StatusBoard();
1799
+ init_LogViewer();
1800
+ init_CommandBar();
1801
+ Dashboard = ({ logs, exposedLabel, port, pid, logHeight }) => {
1802
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", padding: 0, children: [
1803
+ /* @__PURE__ */ jsx5(Header, {}),
1804
+ /* @__PURE__ */ jsx5(LogViewer, { logs, height: logHeight }),
1805
+ /* @__PURE__ */ jsx5(StatusBoard, { exposedLabel, port, pid }),
1806
+ /* @__PURE__ */ jsx5(CommandBar, {})
1807
+ ] });
1808
+ };
1809
+ }
1810
+ });
1811
+
1812
+ // src/mcp/ui/App.tsx
1813
+ var App_exports = {};
1814
+ __export(App_exports, {
1815
+ App: () => App
1816
+ });
1817
+ import { useState, useEffect as useEffect2 } from "react";
1818
+ import { useInput, useApp } from "ink";
1819
+ import fs11 from "fs";
1820
+ import { jsx as jsx6 } from "react/jsx-runtime";
1821
+ var App;
1822
+ var init_App = __esm({
1823
+ "src/mcp/ui/App.tsx"() {
1824
+ "use strict";
1825
+ init_Dashboard();
1826
+ init_config();
1827
+ init_detection();
1828
+ init_logger();
1829
+ init_server();
1830
+ App = ({ onExit, onConfigure, onInstall, initialPort }) => {
1831
+ const { exit } = useApp();
1832
+ const [logs, setLogs] = useState([]);
1833
+ const [serverInfo, setServerInfo] = useState({
1834
+ port: initialPort,
1835
+ pid: process.pid,
1836
+ running: false
1837
+ });
1838
+ const config = loadMCPConfig();
1839
+ const projects = scanForProjects();
1840
+ const exposedProjects = projects.filter((p) => {
1841
+ const cfg = config.projects.find((c) => c.name === p.name);
1842
+ return cfg?.expose ?? config.defaults.includeNew;
1843
+ });
1844
+ const exposedNames = exposedProjects.map((p) => p.name).slice(0, 5);
1845
+ const exposedLabel = exposedNames.length > 0 ? exposedNames.join(", ") + (exposedProjects.length > 5 ? ` (+${exposedProjects.length - 5} more)` : "") : "(none)";
1846
+ useEffect2(() => {
1847
+ const start = async () => {
1848
+ const status = getMCPServerStatus();
1849
+ if (!status.running) {
1850
+ try {
1851
+ const res = await startMCPServer({ interactive: true });
1852
+ setServerInfo((prev) => ({ ...prev, running: true, port: res.port, pid: res.pid }));
1853
+ } catch (e) {
1854
+ setLogs((prev) => [...prev, `Error starting server: ${e}`]);
1855
+ }
1856
+ } else {
1857
+ setServerInfo((prev) => ({ ...prev, running: true, port: status.port || initialPort, pid: status.pid || process.pid }));
1858
+ }
1859
+ };
1860
+ start();
1861
+ return () => {
1862
+ };
1863
+ }, []);
1864
+ useEffect2(() => {
1865
+ const logPath = getLogFilePath();
1866
+ let lastSize = 0;
1867
+ if (fs11.existsSync(logPath)) {
1868
+ const stats = fs11.statSync(logPath);
1869
+ lastSize = stats.size;
1870
+ }
1871
+ const interval = setInterval(() => {
1872
+ if (fs11.existsSync(logPath)) {
1873
+ const stats = fs11.statSync(logPath);
1874
+ if (stats.size > lastSize) {
1875
+ const buffer = Buffer.alloc(stats.size - lastSize);
1876
+ const fd = fs11.openSync(logPath, "r");
1877
+ fs11.readSync(fd, buffer, 0, buffer.length, lastSize);
1878
+ fs11.closeSync(fd);
1879
+ const newContent = buffer.toString("utf-8");
1880
+ const newLines = newContent.split("\n").filter((l) => l.trim());
1881
+ setLogs((prev) => {
1882
+ const next = [...prev, ...newLines];
1883
+ return next.slice(-50);
1884
+ });
1885
+ lastSize = stats.size;
1886
+ }
1887
+ }
1888
+ }, 500);
1889
+ return () => clearInterval(interval);
1890
+ }, []);
1891
+ useInput((input, key) => {
1892
+ if (input === "q" || key.ctrl && input === "c") {
1893
+ stopMCPServer();
1894
+ onExit();
1895
+ }
1896
+ if (input === "p") {
1897
+ onConfigure();
1898
+ }
1899
+ if (input === "i") {
1900
+ onInstall();
1901
+ }
1902
+ if (input === "c") {
1903
+ setLogs([]);
1904
+ }
1905
+ if (input === "r") {
1906
+ setLogs((prev) => [...prev, "[INFO] Config reload requested..."]);
1907
+ }
1908
+ });
1909
+ const termHeight = process.stdout.rows || 24;
1910
+ const logHeight = Math.max(5, termHeight - 12);
1911
+ return /* @__PURE__ */ jsx6(
1912
+ Dashboard,
1913
+ {
1914
+ logs,
1915
+ exposedLabel,
1916
+ port: serverInfo.port,
1917
+ pid: serverInfo.pid,
1918
+ logHeight
1919
+ }
1920
+ );
1921
+ };
1922
+ }
1923
+ });
1924
+
1686
1925
  // src/mcp/index.ts
1687
1926
  var mcp_exports = {};
1688
1927
  __export(mcp_exports, {
@@ -1694,9 +1933,13 @@ async function runMCP(subcommand2) {
1694
1933
  if (subcommand2) {
1695
1934
  switch (subcommand2) {
1696
1935
  case "start":
1697
- await startMCPServer();
1698
- await new Promise(() => {
1699
- });
1936
+ if (process.stdout.isTTY) {
1937
+ await handleStartServer();
1938
+ } else {
1939
+ await startMCPServer();
1940
+ await new Promise(() => {
1941
+ });
1942
+ }
1700
1943
  return;
1701
1944
  case "stop":
1702
1945
  await handleStopServer();
@@ -1848,9 +2091,11 @@ async function handleInstallWizard(workspacePath) {
1848
2091
  }
1849
2092
  }
1850
2093
  async function handleStartServer() {
1851
- const fs16 = await import("fs");
1852
- const { getLogFilePath: getLogFilePath2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
1853
- const config = loadMCPConfig();
2094
+ const React7 = await import("react");
2095
+ const { render } = await import("ink");
2096
+ const { App: App2 } = await Promise.resolve().then(() => (init_App(), App_exports));
2097
+ const { loadMCPConfig: loadMCPConfig2, saveMCPConfig: saveMCPConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2098
+ const config = loadMCPConfig2();
1854
2099
  const projects = scanForProjects();
1855
2100
  const exposedProjects = projects.filter((p) => {
1856
2101
  const cfg = config.projects.find((c) => c.name === p.name);
@@ -1867,7 +2112,7 @@ async function handleStartServer() {
1867
2112
  }
1868
2113
  }
1869
2114
  const status = getMCPServerStatus();
1870
- let newPort = config.server.port;
2115
+ let initialPort = config.server.port;
1871
2116
  if (!status.running) {
1872
2117
  const portInput = await text({
1873
2118
  message: "Select port for MCP Server",
@@ -1878,153 +2123,45 @@ async function handleStartServer() {
1878
2123
  }
1879
2124
  });
1880
2125
  if (isCancel2(portInput)) return;
1881
- newPort = parseInt(portInput, 10);
2126
+ const newPort = parseInt(portInput, 10);
1882
2127
  if (newPort !== config.server.port) {
1883
2128
  config.server.port = newPort;
1884
- saveMCPConfig(config);
2129
+ saveMCPConfig2(config);
2130
+ initialPort = newPort;
1885
2131
  }
1886
- } else {
1887
- newPort = status.port || newPort;
1888
- note2(`Server is already running on port ${newPort}`, "Info");
1889
2132
  }
1890
2133
  console.clear();
1891
- const logPath = getLogFilePath2();
1892
- const exposedNames = exposedProjects.map((p) => p.name).slice(0, 5);
1893
- const exposedLabel = exposedNames.length > 0 ? exposedNames.join(", ") + (exposedProjects.length > 5 ? ` (+${exposedProjects.length - 5} more)` : "") : pc3.dim("(none)");
1894
- const render = (logs = []) => {
1895
- console.clear();
1896
- console.log(pc3.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
1897
- console.log(pc3.cyan("\u2551") + pc3.bold(pc3.white(" RRCE MCP Hub Running ")) + pc3.cyan("\u2551"));
1898
- console.log(pc3.cyan("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
1899
- const logLines = logs.slice(-10);
1900
- const emptyLines = 10 - logLines.length;
1901
- for (let i = 0; i < emptyLines; i++) {
1902
- console.log(pc3.cyan("\u2551") + " ".repeat(63) + pc3.cyan("\u2551"));
1903
- }
1904
- for (const line of logLines) {
1905
- const cleanLine = line.replace(/\u001b\[\d+m/g, "");
1906
- const truncated = cleanLine.substring(0, 61).padEnd(61);
1907
- console.log(pc3.cyan("\u2551") + " " + pc3.dim(truncated) + " " + pc3.cyan("\u2551"));
1908
- }
1909
- console.log(pc3.cyan("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
1910
- const infoLine = ` \u{1F4CB} ${exposedLabel} \u2502 Port: ${newPort} \u2502 PID: ${process.pid || "?"}`.substring(0, 61).padEnd(61);
1911
- console.log(pc3.cyan("\u2551") + pc3.yellow(infoLine) + " " + pc3.cyan("\u2551"));
1912
- console.log(pc3.cyan("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
1913
- const cmdLine = ` q:Quit p:Projects i:Install r:Reload c:Clear ?:Help`;
1914
- console.log(pc3.cyan("\u2551") + pc3.dim(cmdLine.padEnd(63)) + pc3.cyan("\u2551"));
1915
- console.log(pc3.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
1916
- };
1917
- let logBuffer = [];
1918
- render(logBuffer);
1919
- try {
1920
- if (!status.running) {
1921
- await startMCPServer();
1922
- }
1923
- let lastSize = 0;
1924
- if (fs16.existsSync(logPath)) {
1925
- const stats = fs16.statSync(logPath);
1926
- lastSize = stats.size;
1927
- }
1928
- let isRunning = true;
1929
- let interval;
1930
- if (process.stdin.setRawMode) {
1931
- process.stdin.setRawMode(true);
1932
- process.stdin.resume();
1933
- process.stdin.setEncoding("utf8");
1934
- }
1935
- return new Promise((resolve) => {
1936
- const cleanup = (shouldStopServer) => {
1937
- isRunning = false;
1938
- clearInterval(interval);
1939
- if (process.stdin.setRawMode) {
1940
- process.stdin.setRawMode(false);
1941
- }
1942
- process.stdin.removeListener("data", onKey);
1943
- process.stdin.pause();
1944
- if (shouldStopServer) {
1945
- stopMCPServer();
1946
- }
1947
- console.log("");
1948
- };
1949
- const onKey = async (key) => {
1950
- if (key === "" || key.toLowerCase() === "q") {
1951
- cleanup(true);
1952
- resolve();
1953
- return;
1954
- }
1955
- if (key.toLowerCase() === "p") {
1956
- cleanup(false);
1957
- console.clear();
1958
- await handleConfigure();
1959
- await handleStartServer();
1960
- resolve();
1961
- return;
1962
- }
1963
- if (key.toLowerCase() === "i") {
1964
- cleanup(false);
1965
- console.clear();
1966
- await handleInstallWizard(detectWorkspaceRoot());
1967
- await handleStartServer();
1968
- resolve();
1969
- return;
1970
- }
1971
- if (key.toLowerCase() === "r") {
1972
- logBuffer.push("[INFO] Reloading configuration...");
1973
- render(logBuffer);
1974
- return;
1975
- }
1976
- if (key.toLowerCase() === "c") {
1977
- logBuffer = [];
1978
- render(logBuffer);
1979
- return;
1980
- }
1981
- if (key === "?") {
1982
- logBuffer.push("\u2500".repeat(40));
1983
- logBuffer.push("Commands:");
1984
- logBuffer.push(" q - Stop server and return to menu");
1985
- logBuffer.push(" p - Reconfigure exposed projects");
1986
- logBuffer.push(" i - Install to additional IDEs");
1987
- logBuffer.push(" r - Reload configuration");
1988
- logBuffer.push(" c - Clear log display");
1989
- logBuffer.push(" ? - Show this help");
1990
- logBuffer.push("\u2500".repeat(40));
1991
- render(logBuffer);
1992
- return;
1993
- }
1994
- };
1995
- process.stdin.on("data", onKey);
1996
- interval = setInterval(() => {
1997
- if (!isRunning) return;
1998
- if (fs16.existsSync(logPath)) {
1999
- const stats = fs16.statSync(logPath);
2000
- if (stats.size > lastSize) {
2001
- const buffer = Buffer.alloc(stats.size - lastSize);
2002
- const fd = fs16.openSync(logPath, "r");
2003
- fs16.readSync(fd, buffer, 0, buffer.length, lastSize);
2004
- fs16.closeSync(fd);
2005
- const newContent = buffer.toString("utf-8");
2006
- const newLines = newContent.split("\n").filter((l) => l.trim());
2007
- logBuffer.push(...newLines);
2008
- if (logBuffer.length > 100) {
2009
- logBuffer = logBuffer.slice(-100);
2010
- }
2011
- lastSize = stats.size;
2012
- render(logBuffer);
2013
- }
2014
- }
2015
- }, 500);
2016
- });
2017
- } catch (error) {
2018
- if (process.stdin.setRawMode) {
2019
- process.stdin.setRawMode(false);
2134
+ let keepRunning = true;
2135
+ while (keepRunning) {
2136
+ let nextAction = "exit";
2137
+ const app = render(React7.createElement(App2, {
2138
+ initialPort,
2139
+ onExit: () => {
2140
+ nextAction = "exit";
2141
+ },
2142
+ onConfigure: () => {
2143
+ nextAction = "configure";
2144
+ },
2145
+ onInstall: () => {
2146
+ nextAction = "install";
2147
+ }
2148
+ }));
2149
+ await app.waitUntilExit();
2150
+ if (nextAction === "exit") {
2151
+ keepRunning = false;
2152
+ } else if (nextAction === "configure") {
2153
+ console.clear();
2154
+ await handleConfigure();
2155
+ } else if (nextAction === "install") {
2156
+ console.clear();
2157
+ const workspacePath = detectWorkspaceRoot();
2158
+ await handleInstallWizard(workspacePath);
2020
2159
  }
2021
- console.error(pc3.red("\nFailed to start/monitor server:"));
2022
- console.error(error);
2023
2160
  }
2024
2161
  }
2025
2162
  async function handleConfigureGlobalPath() {
2026
2163
  const { resolveGlobalPath: resolveGlobalPath2 } = await Promise.resolve().then(() => (init_tui_utils(), tui_utils_exports));
2027
- const fs16 = await import("fs");
2164
+ const fs17 = await import("fs");
2028
2165
  const path16 = await import("path");
2029
2166
  note2(
2030
2167
  `MCP Hub requires a ${pc3.bold("global storage path")} to store its configuration
@@ -2039,8 +2176,8 @@ locally in each project. MCP needs a central location.`,
2039
2176
  return false;
2040
2177
  }
2041
2178
  try {
2042
- if (!fs16.existsSync(resolvedPath)) {
2043
- fs16.mkdirSync(resolvedPath, { recursive: true });
2179
+ if (!fs17.existsSync(resolvedPath)) {
2180
+ fs17.mkdirSync(resolvedPath, { recursive: true });
2044
2181
  }
2045
2182
  const config = loadMCPConfig();
2046
2183
  saveMCPConfig(config);
@@ -2220,7 +2357,7 @@ var init_mcp = __esm({
2220
2357
  // src/commands/wizard/setup-flow.ts
2221
2358
  import { group, select as select3, multiselect as multiselect2, confirm as confirm2, spinner as spinner2, note as note3, outro as outro2, cancel as cancel2, isCancel as isCancel3 } from "@clack/prompts";
2222
2359
  import pc4 from "picocolors";
2223
- import * as fs11 from "fs";
2360
+ import * as fs12 from "fs";
2224
2361
  import * as path12 from "path";
2225
2362
  async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
2226
2363
  const s = spinner2();
@@ -2395,7 +2532,7 @@ linked_projects:
2395
2532
  `;
2396
2533
  });
2397
2534
  }
2398
- fs11.writeFileSync(workspaceConfigPath, configContent);
2535
+ fs12.writeFileSync(workspaceConfigPath, configContent);
2399
2536
  if (config.addToGitignore) {
2400
2537
  updateGitignore(workspacePath, config.storageMode, config.tools);
2401
2538
  }
@@ -2435,8 +2572,8 @@ function updateGitignore(workspacePath, storageMode, tools) {
2435
2572
  }
2436
2573
  try {
2437
2574
  let content = "";
2438
- if (fs11.existsSync(gitignorePath)) {
2439
- content = fs11.readFileSync(gitignorePath, "utf-8");
2575
+ if (fs12.existsSync(gitignorePath)) {
2576
+ content = fs12.readFileSync(gitignorePath, "utf-8");
2440
2577
  }
2441
2578
  const lines = content.split("\n").map((line) => line.trim());
2442
2579
  const newEntries = [];
@@ -2461,7 +2598,7 @@ function updateGitignore(workspacePath, storageMode, tools) {
2461
2598
  newContent += "\n# rrce-workflow generated folders (uncomment to ignore)\n";
2462
2599
  }
2463
2600
  newContent += newEntries.map((e) => `# ${e}`).join("\n") + "\n";
2464
- fs11.writeFileSync(gitignorePath, newContent);
2601
+ fs12.writeFileSync(gitignorePath, newContent);
2465
2602
  return true;
2466
2603
  } catch {
2467
2604
  return false;
@@ -2482,7 +2619,7 @@ var init_setup_flow = __esm({
2482
2619
  // src/commands/wizard/link-flow.ts
2483
2620
  import { multiselect as multiselect3, spinner as spinner3, note as note4, outro as outro3, cancel as cancel3, isCancel as isCancel4 } from "@clack/prompts";
2484
2621
  import pc5 from "picocolors";
2485
- import * as fs12 from "fs";
2622
+ import * as fs13 from "fs";
2486
2623
  async function runLinkProjectsFlow(workspacePath, workspaceName) {
2487
2624
  const projects = scanForProjects({
2488
2625
  excludeWorkspace: workspaceName,
@@ -2520,7 +2657,7 @@ async function runLinkProjectsFlow(workspacePath, workspaceName) {
2520
2657
  const s = spinner3();
2521
2658
  s.start("Linking projects");
2522
2659
  const configFilePath = getConfigPath(workspacePath);
2523
- let configContent = fs12.readFileSync(configFilePath, "utf-8");
2660
+ let configContent = fs13.readFileSync(configFilePath, "utf-8");
2524
2661
  if (configContent.includes("linked_projects:")) {
2525
2662
  const lines = configContent.split("\n");
2526
2663
  const linkedIndex = lines.findIndex((l) => l.trim() === "linked_projects:");
@@ -2547,7 +2684,7 @@ linked_projects:
2547
2684
  `;
2548
2685
  });
2549
2686
  }
2550
- fs12.writeFileSync(configFilePath, configContent);
2687
+ fs13.writeFileSync(configFilePath, configContent);
2551
2688
  generateVSCodeWorkspace(workspacePath, workspaceName, selectedProjects, customGlobalPath);
2552
2689
  s.stop("Projects linked");
2553
2690
  const workspaceFile = `${workspaceName}.code-workspace`;
@@ -2572,7 +2709,7 @@ var init_link_flow = __esm({
2572
2709
  // src/commands/wizard/sync-flow.ts
2573
2710
  import { confirm as confirm3, spinner as spinner4, note as note5, outro as outro4, cancel as cancel4, isCancel as isCancel5 } from "@clack/prompts";
2574
2711
  import pc6 from "picocolors";
2575
- import * as fs13 from "fs";
2712
+ import * as fs14 from "fs";
2576
2713
  import * as path13 from "path";
2577
2714
  async function runSyncToGlobalFlow(workspacePath, workspaceName) {
2578
2715
  const localPath = getLocalWorkspacePath(workspacePath);
@@ -2580,7 +2717,7 @@ async function runSyncToGlobalFlow(workspacePath, workspaceName) {
2580
2717
  const globalPath = path13.join(customGlobalPath, "workspaces", workspaceName);
2581
2718
  const subdirs = ["knowledge", "prompts", "templates", "tasks", "refs"];
2582
2719
  const existingDirs = subdirs.filter(
2583
- (dir) => fs13.existsSync(path13.join(localPath, dir))
2720
+ (dir) => fs14.existsSync(path13.join(localPath, dir))
2584
2721
  );
2585
2722
  if (existingDirs.length === 0) {
2586
2723
  outro4(pc6.yellow("No data found in workspace storage to sync."));
@@ -2639,7 +2776,7 @@ var init_sync_flow = __esm({
2639
2776
  // src/commands/wizard/update-flow.ts
2640
2777
  import { confirm as confirm4, spinner as spinner5, note as note6, outro as outro5, cancel as cancel5, isCancel as isCancel6 } from "@clack/prompts";
2641
2778
  import pc7 from "picocolors";
2642
- import * as fs14 from "fs";
2779
+ import * as fs15 from "fs";
2643
2780
  import * as path14 from "path";
2644
2781
  async function runUpdateFlow(workspacePath, workspaceName, currentStorageMode) {
2645
2782
  const s = spinner5();
@@ -2673,7 +2810,7 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
2673
2810
  copyDirToAllStoragePaths(path14.join(agentCoreDir, "templates"), "templates", [dataPath]);
2674
2811
  }
2675
2812
  const configFilePath = getConfigPath(workspacePath);
2676
- const configContent = fs14.readFileSync(configFilePath, "utf-8");
2813
+ const configContent = fs15.readFileSync(configFilePath, "utf-8");
2677
2814
  if (configContent.includes("copilot: true")) {
2678
2815
  const copilotPath = getAgentPromptPath(workspacePath, "copilot");
2679
2816
  ensureDir(copilotPath);
@@ -2728,7 +2865,7 @@ __export(wizard_exports, {
2728
2865
  });
2729
2866
  import { intro as intro2, select as select4, spinner as spinner6, note as note7, outro as outro6, isCancel as isCancel7 } from "@clack/prompts";
2730
2867
  import pc8 from "picocolors";
2731
- import * as fs15 from "fs";
2868
+ import * as fs16 from "fs";
2732
2869
  async function runWizard() {
2733
2870
  intro2(pc8.cyan(pc8.inverse(" RRCE-Workflow Setup ")));
2734
2871
  const s = spinner6();
@@ -2748,18 +2885,18 @@ Workspace: ${pc8.bold(workspaceName)}`,
2748
2885
  workspacePath
2749
2886
  });
2750
2887
  const configFilePath = getConfigPath(workspacePath);
2751
- const isAlreadyConfigured = fs15.existsSync(configFilePath);
2888
+ const isAlreadyConfigured = fs16.existsSync(configFilePath);
2752
2889
  let currentStorageMode = null;
2753
2890
  if (isAlreadyConfigured) {
2754
2891
  try {
2755
- const configContent = fs15.readFileSync(configFilePath, "utf-8");
2892
+ const configContent = fs16.readFileSync(configFilePath, "utf-8");
2756
2893
  const modeMatch = configContent.match(/mode:\s*(global|workspace)/);
2757
2894
  currentStorageMode = modeMatch?.[1] ?? null;
2758
2895
  } catch {
2759
2896
  }
2760
2897
  }
2761
2898
  const localDataPath = getLocalWorkspacePath(workspacePath);
2762
- const hasLocalData = fs15.existsSync(localDataPath);
2899
+ const hasLocalData = fs16.existsSync(localDataPath);
2763
2900
  if (isAlreadyConfigured) {
2764
2901
  const menuOptions = [];
2765
2902
  menuOptions.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rrce-workflow",
3
- "version": "0.2.29",
3
+ "version": "0.2.31",
4
4
  "description": "RRCE-Workflow TUI - Agentic code workflow generator for AI-assisted development",
5
5
  "author": "RRCE Team",
6
6
  "license": "MIT",
@@ -50,11 +50,17 @@
50
50
  "@clack/prompts": "^0.11.0",
51
51
  "@modelcontextprotocol/sdk": "^1.25.1",
52
52
  "gray-matter": "^4.0.3",
53
+ "ink": "^6.6.0",
54
+ "ink-link": "^5.0.0",
55
+ "ink-spinner": "^5.0.0",
56
+ "ink-text-input": "^6.0.0",
53
57
  "picocolors": "^1.1.1",
58
+ "react": "^19.2.3",
54
59
  "zod": "^4.2.1"
55
60
  },
56
61
  "devDependencies": {
57
62
  "@types/node": "^25.0.3",
63
+ "@types/react": "^19.2.7",
58
64
  "esbuild": "^0.27.2",
59
65
  "tsx": "^4.21.0",
60
66
  "typescript": "^5.9.3"