@xcanwin/manyoyo 5.8.5 → 5.8.6

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/README.md CHANGED
@@ -128,6 +128,7 @@ manyoyo serve 127.0.0.1:3000
128
128
  manyoyo serve 127.0.0.1:3000 -U admin -P 123456
129
129
  manyoyo serve 127.0.0.1:3000 -U admin -P 123456 -d
130
130
  manyoyo serve 127.0.0.1:3000 -d # 未设置密码时会打印本次随机密码
131
+ manyoyo serve 127.0.0.1:3000 --stop # 停止指定后台服务
131
132
 
132
133
  # 查看配置与命令拼装
133
134
  manyoyo config show
package/bin/manyoyo.js CHANGED
@@ -1216,6 +1216,7 @@ Notes:
1216
1216
  const serveCommand = program.command('serve [listen]').description('启动网页交互服务 (默认 127.0.0.1:3000)');
1217
1217
  applyRunStyleOptions(serveCommand, { includeRmOnExit: false, includeWebAuthOptions: true });
1218
1218
  serveCommand.option('-d, --detach', '后台启动网页服务并立即返回');
1219
+ serveCommand.option('--stop', '停止后台网页服务;必须显式传入 listen');
1219
1220
  serveCommand.action((listen, options) => {
1220
1221
  selectAction('serve', {
1221
1222
  ...options,
@@ -1317,8 +1318,12 @@ Notes:
1317
1318
  const isShowConfigMode = selectedAction === 'config-show';
1318
1319
  const isShowCommandMode = selectedAction === 'config-command';
1319
1320
  const isServerMode = options.server !== undefined;
1321
+ const isServerStopMode = Boolean(selectedAction === 'serve' && options.stop);
1320
1322
 
1321
1323
  const noDockerActions = new Set(['init', 'update', 'install', 'config-show', 'plugin']);
1324
+ if (isServerStopMode) {
1325
+ noDockerActions.add('serve');
1326
+ }
1322
1327
  if (!noDockerActions.has(selectedAction)) {
1323
1328
  ensureDocker();
1324
1329
  }
@@ -1498,7 +1503,7 @@ Notes:
1498
1503
  SERVER_AUTH_PASS_AUTO = false;
1499
1504
  }
1500
1505
 
1501
- if (isServerMode) {
1506
+ if (isServerMode && !isServerStopMode) {
1502
1507
  ensureWebServerAuthCredentials();
1503
1508
  }
1504
1509
 
@@ -1560,7 +1565,9 @@ Notes:
1560
1565
  isRemoveMode,
1561
1566
  isShowCommandMode,
1562
1567
  isServerMode,
1568
+ isServerStop: isServerStopMode,
1563
1569
  isServerDetach: Boolean(selectedAction === 'serve' && options.detach),
1570
+ isServerListenSpecified: Boolean(isServerMode && options.server !== true),
1564
1571
  isPluginMode: false
1565
1572
  };
1566
1573
  }
@@ -1588,7 +1595,9 @@ function createRuntimeContext(modeState = {}) {
1588
1595
  showCommand: Boolean(modeState.isShowCommandMode),
1589
1596
  rmOnExit: RM_ON_EXIT,
1590
1597
  serverMode: Boolean(modeState.isServerMode),
1598
+ serverStop: Boolean(modeState.isServerStop),
1591
1599
  serverDetach: Boolean(modeState.isServerDetach),
1600
+ serverListenSpecified: Boolean(modeState.isServerListenSpecified),
1592
1601
  serverHost: SERVER_HOST,
1593
1602
  serverPort: SERVER_PORT,
1594
1603
  serverAuthUser: SERVER_AUTH_USER,
@@ -1657,10 +1666,138 @@ function buildDetachedServeEnv(runtime) {
1657
1666
  return env;
1658
1667
  }
1659
1668
 
1669
+ function formatServeListenHost(host) {
1670
+ const text = String(host || '').trim() || '127.0.0.1';
1671
+ if (text.includes(':') && !text.startsWith('[')) {
1672
+ return `[${text}]`;
1673
+ }
1674
+ return text;
1675
+ }
1676
+
1677
+ function buildServeListenLabel(host, port) {
1678
+ return `${formatServeListenHost(host)}:${port}`;
1679
+ }
1680
+
1681
+ function buildServePidFile(host, port, homeDir = os.homedir()) {
1682
+ const dir = path.join(homeDir, '.manyoyo', 'run', 'serve');
1683
+ const listen = buildServeListenLabel(host, port);
1684
+ const safeName = listen.replace(/[^A-Za-z0-9_.-]+/g, '_');
1685
+ return {
1686
+ dir,
1687
+ listen,
1688
+ path: path.join(dir, `${safeName}.pid`)
1689
+ };
1690
+ }
1691
+
1692
+ function removeServePidFile(filePath) {
1693
+ if (!filePath) return;
1694
+ try {
1695
+ fs.rmSync(filePath, { force: true });
1696
+ } catch (e) {
1697
+ // ignore cleanup failures
1698
+ }
1699
+ }
1700
+
1701
+ function isProcessRunning(pid) {
1702
+ if (!Number.isInteger(pid) || pid <= 0) {
1703
+ return false;
1704
+ }
1705
+ try {
1706
+ process.kill(pid, 0);
1707
+ return true;
1708
+ } catch (e) {
1709
+ return e && e.code !== 'ESRCH';
1710
+ }
1711
+ }
1712
+
1713
+ function readServePidFile(filePath) {
1714
+ try {
1715
+ const text = fs.readFileSync(filePath, 'utf-8').trim();
1716
+ if (!/^\d+$/.test(text)) {
1717
+ return 0;
1718
+ }
1719
+ return Number(text);
1720
+ } catch (e) {
1721
+ return 0;
1722
+ }
1723
+ }
1724
+
1725
+ function getServePidTarget(host, port, homeDir = os.homedir()) {
1726
+ const pidFile = buildServePidFile(host, port, homeDir);
1727
+ const pid = readServePidFile(pidFile.path);
1728
+ if (!Number.isInteger(pid) || pid <= 0 || !isProcessRunning(pid)) {
1729
+ removeServePidFile(pidFile.path);
1730
+ return null;
1731
+ }
1732
+ return {
1733
+ pid,
1734
+ listen: pidFile.listen,
1735
+ path: pidFile.path
1736
+ };
1737
+ }
1738
+
1739
+ function installServePidCleanup(pidFilePath, logger) {
1740
+ if (!pidFilePath || global.__manyoyoServePidCleanupInstalled) {
1741
+ return;
1742
+ }
1743
+ global.__manyoyoServePidCleanupInstalled = true;
1744
+ process.on('exit', () => {
1745
+ removeServePidFile(pidFilePath);
1746
+ if (logger && typeof logger.info === 'function') {
1747
+ logger.info('serve pid file removed', { pidFilePath });
1748
+ }
1749
+ });
1750
+ }
1751
+
1752
+ function writeServePidFile(runtime, serverHandle) {
1753
+ const pidFile = buildServePidFile(serverHandle.host, serverHandle.port);
1754
+ fs.mkdirSync(pidFile.dir, { recursive: true });
1755
+ fs.writeFileSync(pidFile.path, `${process.pid}\n`);
1756
+ installServePidCleanup(pidFile.path, runtime && runtime.logger);
1757
+ return pidFile.path;
1758
+ }
1759
+
1760
+ async function stopServeProcess(runtime) {
1761
+ if (!runtime || !runtime.serverListenSpecified) {
1762
+ throw new Error('serve --stop 必须显式传入 listen,例如 manyoyo serve 127.0.0.1:3000 --stop');
1763
+ }
1764
+ const target = getServePidTarget(runtime.serverHost, runtime.serverPort);
1765
+ if (!target) {
1766
+ const label = buildServeListenLabel(runtime.serverHost, runtime.serverPort);
1767
+ console.log(`${YELLOW}⚠️ 未发现运行中的 serve 实例: ${label}${NC}`);
1768
+ return;
1769
+ }
1770
+ try {
1771
+ process.kill(target.pid, 'SIGTERM');
1772
+ } catch (e) {
1773
+ if (!e || e.code !== 'ESRCH') {
1774
+ throw e;
1775
+ }
1776
+ }
1777
+ await sleep(200);
1778
+ if (isProcessRunning(target.pid)) {
1779
+ try {
1780
+ process.kill(target.pid, 'SIGKILL');
1781
+ } catch (e) {
1782
+ if (!e || e.code !== 'ESRCH') {
1783
+ throw e;
1784
+ }
1785
+ }
1786
+ }
1787
+ removeServePidFile(target.path);
1788
+ console.log(`${GREEN}✅ 已停止 serve: ${target.listen} (pid: ${target.pid})${NC}`);
1789
+ }
1790
+
1660
1791
  function relaunchServeDetached(runtime) {
1661
1792
  const serveLog = buildManyoyoLogPath('serve');
1662
1793
  fs.mkdirSync(serveLog.dir, { recursive: true });
1663
1794
 
1795
+ const existing = getServePidTarget(runtime.serverHost, runtime.serverPort);
1796
+ if (existing) {
1797
+ console.log(`${YELLOW}⚠️ serve 已在后台运行: ${existing.listen} (pid: ${existing.pid})${NC}`);
1798
+ return;
1799
+ }
1800
+
1664
1801
  const child = spawn(process.argv[0], buildDetachedServeArgv(process.argv.slice(1)), {
1665
1802
  detached: true,
1666
1803
  stdio: 'ignore',
@@ -1954,7 +2091,7 @@ async function runWebServerMode(runtime) {
1954
2091
  runtime.serverAuthPassAuto = SERVER_AUTH_PASS_AUTO;
1955
2092
  }
1956
2093
 
1957
- await startWebServer({
2094
+ const serverHandle = await startWebServer({
1958
2095
  serverHost: runtime.serverHost,
1959
2096
  serverPort: runtime.serverPort,
1960
2097
  authUser: runtime.serverAuthUser,
@@ -1993,6 +2130,8 @@ async function runWebServerMode(runtime) {
1993
2130
  },
1994
2131
  logger: runtime.logger
1995
2132
  });
2133
+ writeServePidFile(runtime, serverHandle);
2134
+ return serverHandle;
1996
2135
  }
1997
2136
 
1998
2137
  async function main() {
@@ -2015,6 +2154,10 @@ async function main() {
2015
2154
 
2016
2155
  // 2. Start web server mode
2017
2156
  if (runtime.serverMode) {
2157
+ if (runtime.serverStop) {
2158
+ await stopServeProcess(runtime);
2159
+ return;
2160
+ }
2018
2161
  if (runtime.serverDetach) {
2019
2162
  relaunchServeDetached(runtime);
2020
2163
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.8.5",
3
+ "version": "5.8.6",
4
4
  "imageVersion": "1.9.0-common",
5
5
  "playwrightCliVersion": "0.1.1",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",