@xcanwin/manyoyo 5.9.3 → 5.10.3
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 +12 -0
- package/bin/manyoyo.js +51 -7
- package/lib/image-build.js +160 -0
- package/lib/runtime-resolver.js +25 -1
- package/lib/web/frontend/app.css +121 -55
- package/lib/web/frontend/app.html +12 -6
- package/lib/web/frontend/app.js +214 -34
- package/lib/web/frontend/codemirror-entry.js +13 -0
- package/lib/web/frontend/codemirror.bundle.js +13 -0
- package/lib/web/frontend/file-browser.js +220 -29
- package/lib/web/server.js +179 -10
- package/lib/worktrees.js +132 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
+
## 生态仓库
|
|
25
|
+
|
|
26
|
+
| 仓库 | 定位 |
|
|
27
|
+
|------|------|
|
|
28
|
+
| [manyoyo](https://github.com/xcanwin/manyoyo) | CLI + 容器运行时 + Web 服务 |
|
|
29
|
+
| [manyoyo-app](https://github.com/xcanwin/manyoyo-app) | Flutter 原生 UI 客户端(macOS / Windows / iOS / Android) |
|
|
30
|
+
| [manyoyo-studio](https://github.com/xcanwin/manyoyo-studio) | Electron 桌面端 + Capacitor 移动端 |
|
|
31
|
+
|
|
24
32
|
## 为什么是 MANYOYO
|
|
25
33
|
|
|
26
34
|
AI Agent CLI 往往需要:
|
|
@@ -129,6 +137,7 @@ manyoyo serve 127.0.0.1:3000 -U admin -P 123456
|
|
|
129
137
|
manyoyo serve 127.0.0.1:3000 -U admin -P 123456 -d
|
|
130
138
|
manyoyo serve 127.0.0.1:3000 -d # 未设置密码时会打印本次随机密码
|
|
131
139
|
manyoyo serve 127.0.0.1:3000 --stop # 停止指定后台服务
|
|
140
|
+
manyoyo serve 127.0.0.1:3000 -U admin -P 123456 -d --restart # 重启指定后台服务
|
|
132
141
|
|
|
133
142
|
# 查看配置与命令拼装
|
|
134
143
|
manyoyo config show
|
|
@@ -144,6 +153,9 @@ manyoyo build --iv 1.9.0-common
|
|
|
144
153
|
# full 版本
|
|
145
154
|
manyoyo build --iv 1.9.0-full
|
|
146
155
|
|
|
156
|
+
# 仅更新已有镜像内 Agent CLI 到 latest,不重建 Dockerfile
|
|
157
|
+
manyoyo build --iv 1.9.0-full --update-agents --yes
|
|
158
|
+
|
|
147
159
|
# 自定义工具集
|
|
148
160
|
manyoyo build --iba TOOL=go,codex,java,gemini
|
|
149
161
|
```
|
package/bin/manyoyo.js
CHANGED
|
@@ -17,6 +17,7 @@ const { resolveAgentResumeArg, buildAgentResumeCommand } = require('../lib/agent
|
|
|
17
17
|
const { runPluginCommand, createPlugin } = require('../lib/plugin');
|
|
18
18
|
const { buildManyoyoLogPath } = require('../lib/log-path');
|
|
19
19
|
const { resolveRuntimeConfig } = require('../lib/runtime-resolver');
|
|
20
|
+
const { resolveWorktreeSupport } = require('../lib/worktrees');
|
|
20
21
|
const {
|
|
21
22
|
parseEnvEntry: parseEnvEntryOrThrow,
|
|
22
23
|
normalizeVolume
|
|
@@ -938,6 +939,22 @@ function normalizeShellFullArgv(argv) {
|
|
|
938
939
|
}
|
|
939
940
|
}
|
|
940
941
|
|
|
942
|
+
function normalizeWorktreeArgv(argv) {
|
|
943
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
944
|
+
if (argv[i] === '--wt') {
|
|
945
|
+
argv[i] = '--worktrees';
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
if (argv[i] === '--wtr') {
|
|
949
|
+
argv[i] = '--worktrees-root';
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
if (typeof argv[i] === 'string' && argv[i].startsWith('--wtr=')) {
|
|
953
|
+
argv[i] = `--worktrees-root=${argv[i].slice('--wtr='.length)}`;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
941
958
|
function appendArrayOption(command, flags, description) {
|
|
942
959
|
return command.option(
|
|
943
960
|
flags,
|
|
@@ -983,6 +1000,8 @@ function applyRunStyleOptions(command, options = {}) {
|
|
|
983
1000
|
appendArrayOption(command, '-p, --port <port>', '设置端口映射 XXX:YYY (可多次使用)');
|
|
984
1001
|
|
|
985
1002
|
command
|
|
1003
|
+
.option('--worktrees', '启用 Git worktrees 根目录自动挂载 (别名: --wt)')
|
|
1004
|
+
.option('--worktrees-root <path>', '指定项目级 Git worktrees 根目录 (仅支持绝对路径; 隐式启用 --worktrees; 别名: --wtr)')
|
|
986
1005
|
.option('--sp, --shell-prefix <command>', '主命令前缀 (常用于临时环境变量)')
|
|
987
1006
|
.option('-s, --shell <command>', '主命令')
|
|
988
1007
|
.option('--ss, --shell-suffix <command>', '主命令后缀 (追加到 -s 之后,等价于 -- <args>)')
|
|
@@ -1128,6 +1147,7 @@ async function setupCommander() {
|
|
|
1128
1147
|
${MANYOYO_NAME} serve 127.0.0.1:3000 启动本机网页服务
|
|
1129
1148
|
${MANYOYO_NAME} serve 127.0.0.1:3000 -d 后台启动;未设密码时会打印本次随机密码
|
|
1130
1149
|
${MANYOYO_NAME} serve 0.0.0.0:3000 -U admin -P 123 -d 后台启动并监听全部网卡
|
|
1150
|
+
${MANYOYO_NAME} serve 0.0.0.0:3000 -U admin -P 123 -d --restart 重启指定后台网页服务
|
|
1131
1151
|
${MANYOYO_NAME} playwright up mcp-host-headless 启动 playwright MCP 宿主场景(默认/推荐)
|
|
1132
1152
|
${MANYOYO_NAME} playwright up cli-host-headless 启动 playwright CLI 宿主场景(供容器内 playwright-cli 附着)
|
|
1133
1153
|
${MANYOYO_NAME} run -n test -q tip -q cmd 多次使用静默选项
|
|
@@ -1155,6 +1175,7 @@ Notes:
|
|
|
1155
1175
|
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
|
|
1156
1176
|
.option('--in, --image-name <name>', '指定镜像名称')
|
|
1157
1177
|
.option('--iv, --image-ver <version>', '指定镜像版本 (格式: x.y.z-后缀,如 1.7.4-common)')
|
|
1178
|
+
.option('--update-agents', '仅更新已有镜像内 Agent CLI 到 latest (Claude/Codex/Gemini/OpenCode)')
|
|
1158
1179
|
.option('--yes', '所有提示自动确认 (用于CI/脚本)');
|
|
1159
1180
|
appendArrayOption(buildCommand, '--iba, --image-build-arg <arg>', '构建镜像时传参给dockerfile (可多次使用)');
|
|
1160
1181
|
buildCommand.action(options => selectAction('build', options));
|
|
@@ -1176,6 +1197,7 @@ Notes:
|
|
|
1176
1197
|
applyRunStyleOptions(serveCommand, { includeRmOnExit: false, includeWebAuthOptions: true });
|
|
1177
1198
|
serveCommand.option('-d, --detach', '后台启动网页服务并立即返回');
|
|
1178
1199
|
serveCommand.option('--stop', '停止后台网页服务;必须显式传入 listen');
|
|
1200
|
+
serveCommand.option('--restart', '重启后台网页服务;必须显式传入 listen');
|
|
1179
1201
|
serveCommand.action((listen, options) => {
|
|
1180
1202
|
selectAction('serve', {
|
|
1181
1203
|
...options,
|
|
@@ -1258,6 +1280,7 @@ Notes:
|
|
|
1258
1280
|
|
|
1259
1281
|
// Pre-handle -x/--shell-full: treat all following args as a single command
|
|
1260
1282
|
normalizeShellFullArgv(process.argv);
|
|
1283
|
+
normalizeWorktreeArgv(process.argv);
|
|
1261
1284
|
|
|
1262
1285
|
// Parse arguments
|
|
1263
1286
|
program.allowUnknownOption(false);
|
|
@@ -1278,6 +1301,11 @@ Notes:
|
|
|
1278
1301
|
const isShowCommandMode = selectedAction === 'config-command';
|
|
1279
1302
|
const isServerMode = options.server !== undefined;
|
|
1280
1303
|
const isServerStopMode = Boolean(selectedAction === 'serve' && options.stop);
|
|
1304
|
+
const isServerRestartMode = Boolean(selectedAction === 'serve' && options.restart);
|
|
1305
|
+
|
|
1306
|
+
if (isServerStopMode && isServerRestartMode) {
|
|
1307
|
+
throw new Error('serve --stop 与 --restart 不能同时使用');
|
|
1308
|
+
}
|
|
1281
1309
|
|
|
1282
1310
|
const noDockerActions = new Set(['init', 'update', 'install', 'config-show', 'plugin']);
|
|
1283
1311
|
if (isServerStopMode) {
|
|
@@ -1350,7 +1378,8 @@ Notes:
|
|
|
1350
1378
|
normalizeCliEnvMap,
|
|
1351
1379
|
mergeArrayConfig,
|
|
1352
1380
|
normalizeVolume,
|
|
1353
|
-
parseServerListen
|
|
1381
|
+
parseServerListen,
|
|
1382
|
+
resolveWorktreeSupport
|
|
1354
1383
|
});
|
|
1355
1384
|
|
|
1356
1385
|
HOST_PATH = resolvedRuntime.hostPath;
|
|
@@ -1425,6 +1454,10 @@ Notes:
|
|
|
1425
1454
|
volumes: volumeList,
|
|
1426
1455
|
ports: portList,
|
|
1427
1456
|
imageBuildArgs: buildArgList,
|
|
1457
|
+
worktrees: resolvedRuntime.worktrees,
|
|
1458
|
+
worktreesRoot: resolvedRuntime.worktreesRoot,
|
|
1459
|
+
worktreeRepoRoot: resolvedRuntime.worktreeRepoRoot,
|
|
1460
|
+
worktreeMainRepoRoot: resolvedRuntime.worktreeMainRepoRoot,
|
|
1428
1461
|
containerMode: contModeValue || "",
|
|
1429
1462
|
shellPrefix: EXEC_COMMAND_PREFIX.trim(),
|
|
1430
1463
|
shell: EXEC_COMMAND || "",
|
|
@@ -1472,8 +1505,10 @@ Notes:
|
|
|
1472
1505
|
isShowCommandMode,
|
|
1473
1506
|
isServerMode,
|
|
1474
1507
|
isServerStop: isServerStopMode,
|
|
1508
|
+
isServerRestart: isServerRestartMode,
|
|
1475
1509
|
isServerDetach: Boolean(selectedAction === 'serve' && options.detach),
|
|
1476
1510
|
isServerListenSpecified: Boolean(isServerMode && options.server !== true),
|
|
1511
|
+
updateAgents: Boolean(options.updateAgents),
|
|
1477
1512
|
isPluginMode: false
|
|
1478
1513
|
};
|
|
1479
1514
|
}
|
|
@@ -1502,6 +1537,7 @@ function createRuntimeContext(modeState = {}) {
|
|
|
1502
1537
|
rmOnExit: RM_ON_EXIT,
|
|
1503
1538
|
serverMode: Boolean(modeState.isServerMode),
|
|
1504
1539
|
serverStop: Boolean(modeState.isServerStop),
|
|
1540
|
+
serverRestart: Boolean(modeState.isServerRestart),
|
|
1505
1541
|
serverDetach: Boolean(modeState.isServerDetach),
|
|
1506
1542
|
serverListenSpecified: Boolean(modeState.isServerListenSpecified),
|
|
1507
1543
|
serverHost: SERVER_HOST,
|
|
@@ -1553,7 +1589,7 @@ function buildDetachedServeArgv(argv) {
|
|
|
1553
1589
|
const result = [];
|
|
1554
1590
|
for (let i = 0; i < argv.length; i++) {
|
|
1555
1591
|
const arg = String(argv[i] || '');
|
|
1556
|
-
if (arg === '-d' || arg === '--detach') {
|
|
1592
|
+
if (arg === '-d' || arg === '--detach' || arg === '--restart') {
|
|
1557
1593
|
continue;
|
|
1558
1594
|
}
|
|
1559
1595
|
result.push(arg);
|
|
@@ -1663,15 +1699,16 @@ function writeServePidFile(runtime, serverHandle) {
|
|
|
1663
1699
|
return pidFile.path;
|
|
1664
1700
|
}
|
|
1665
1701
|
|
|
1666
|
-
async function stopServeProcess(runtime) {
|
|
1702
|
+
async function stopServeProcess(runtime, options = {}) {
|
|
1703
|
+
const commandName = options.commandName || '--stop';
|
|
1667
1704
|
if (!runtime || !runtime.serverListenSpecified) {
|
|
1668
|
-
throw new Error(
|
|
1705
|
+
throw new Error(`serve ${commandName} 必须显式传入 listen,例如 manyoyo serve 127.0.0.1:3000 ${commandName}`);
|
|
1669
1706
|
}
|
|
1670
1707
|
const target = getServePidTarget(runtime.serverHost, runtime.serverPort);
|
|
1671
1708
|
if (!target) {
|
|
1672
1709
|
const label = buildServeListenLabel(runtime.serverHost, runtime.serverPort);
|
|
1673
1710
|
console.log(`${YELLOW}⚠️ 未发现运行中的 serve 实例: ${label}${NC}`);
|
|
1674
|
-
return;
|
|
1711
|
+
return false;
|
|
1675
1712
|
}
|
|
1676
1713
|
try {
|
|
1677
1714
|
process.kill(target.pid, 'SIGTERM');
|
|
@@ -1692,6 +1729,7 @@ async function stopServeProcess(runtime) {
|
|
|
1692
1729
|
}
|
|
1693
1730
|
removeServePidFile(target.path);
|
|
1694
1731
|
console.log(`${GREEN}✅ 已停止 serve: ${target.listen} (pid: ${target.pid})${NC}`);
|
|
1732
|
+
return true;
|
|
1695
1733
|
}
|
|
1696
1734
|
|
|
1697
1735
|
function relaunchServeDetached(runtime) {
|
|
@@ -1711,7 +1749,7 @@ function relaunchServeDetached(runtime) {
|
|
|
1711
1749
|
});
|
|
1712
1750
|
child.unref();
|
|
1713
1751
|
|
|
1714
|
-
console.log(`${GREEN}✅
|
|
1752
|
+
console.log(`${GREEN}✅ MANYOYO Web 服务已在后台启动: http://${buildServeListenLabel(runtime.serverHost, runtime.serverPort)}${NC}`);
|
|
1715
1753
|
console.log(`PID: ${child.pid}`);
|
|
1716
1754
|
console.log(`日志: ${serveLog.path}`);
|
|
1717
1755
|
console.log(`登录用户名: ${runtime.serverAuthUser}`);
|
|
@@ -2064,6 +2102,9 @@ async function main() {
|
|
|
2064
2102
|
await stopServeProcess(runtime);
|
|
2065
2103
|
return;
|
|
2066
2104
|
}
|
|
2105
|
+
if (runtime.serverRestart) {
|
|
2106
|
+
await stopServeProcess(runtime, { commandName: '--restart' });
|
|
2107
|
+
}
|
|
2067
2108
|
if (runtime.serverDetach) {
|
|
2068
2109
|
relaunchServeDetached(runtime);
|
|
2069
2110
|
return;
|
|
@@ -2093,6 +2134,7 @@ async function main() {
|
|
|
2093
2134
|
parseImageVersionTag,
|
|
2094
2135
|
manyoyoName: MANYOYO_NAME,
|
|
2095
2136
|
yesMode: Boolean(modeState.yesMode),
|
|
2137
|
+
updateAgents: Boolean(modeState.updateAgents),
|
|
2096
2138
|
dockerCmd: DOCKER_CMD,
|
|
2097
2139
|
rootDir: path.join(__dirname, '..'),
|
|
2098
2140
|
loadConfig,
|
|
@@ -2101,7 +2143,9 @@ async function main() {
|
|
|
2101
2143
|
pruneDanglingImages,
|
|
2102
2144
|
colors: { RED, GREEN, YELLOW, BLUE, CYAN, NC }
|
|
2103
2145
|
});
|
|
2104
|
-
|
|
2146
|
+
if (!modeState.updateAgents) {
|
|
2147
|
+
syncBuiltImageVersionToGlobalConfig(runtime.imageVersion);
|
|
2148
|
+
}
|
|
2105
2149
|
process.exit(0);
|
|
2106
2150
|
}
|
|
2107
2151
|
|
package/lib/image-build.js
CHANGED
|
@@ -396,6 +396,159 @@ function runCmdPipeline(leftCmd, leftArgs, rightCmd, rightArgs, options = {}) {
|
|
|
396
396
|
});
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
const AGENT_UPDATE_PACKAGES = [
|
|
400
|
+
'npm@latest',
|
|
401
|
+
'@anthropic-ai/claude-code@latest',
|
|
402
|
+
'@openai/codex@latest',
|
|
403
|
+
'@google/gemini-cli@latest',
|
|
404
|
+
'opencode-ai@latest'
|
|
405
|
+
];
|
|
406
|
+
const AGENT_VERSION_CHECK_COMMAND = [
|
|
407
|
+
'printf "claude: " && claude --version',
|
|
408
|
+
'printf "codex: " && codex --version',
|
|
409
|
+
'printf "gemini: " && gemini --version',
|
|
410
|
+
'printf "opencode: " && opencode --version',
|
|
411
|
+
'printf "npm: " && npm --version'
|
|
412
|
+
].join(' && ');
|
|
413
|
+
|
|
414
|
+
function createAgentUpdateContainerName() {
|
|
415
|
+
return `manyoyo-update-agents-${crypto.randomBytes(6).toString('hex')}`;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const MANYOYO_DEFAULT_CMD = ['supervisord', '-n', '-c', '/etc/supervisor/supervisord.conf'];
|
|
419
|
+
|
|
420
|
+
function parseImageConfig(rawConfig) {
|
|
421
|
+
if (!rawConfig || typeof rawConfig !== 'string') return {};
|
|
422
|
+
try {
|
|
423
|
+
const parsed = JSON.parse(rawConfig);
|
|
424
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
425
|
+
} catch (e) {
|
|
426
|
+
return {};
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function isAgentUpdateCommand(cmd) {
|
|
431
|
+
const text = Array.isArray(cmd) ? cmd.join(' ') : String(cmd || '');
|
|
432
|
+
return AGENT_UPDATE_PACKAGES.some(pkg => text.includes(pkg));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function buildImageImportChangeArgs(config) {
|
|
436
|
+
const changes = [];
|
|
437
|
+
const envList = Array.isArray(config.Env) ? config.Env : [];
|
|
438
|
+
envList.forEach(env => {
|
|
439
|
+
if (typeof env === 'string' && env.trim()) {
|
|
440
|
+
changes.push('--change', `ENV ${env}`);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
if (config.Entrypoint) {
|
|
445
|
+
changes.push('--change', `ENTRYPOINT ${JSON.stringify(config.Entrypoint)}`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const cmd = isAgentUpdateCommand(config.Cmd) ? MANYOYO_DEFAULT_CMD : (config.Cmd || MANYOYO_DEFAULT_CMD);
|
|
449
|
+
changes.push('--change', `CMD ${JSON.stringify(cmd)}`);
|
|
450
|
+
|
|
451
|
+
const workingDir = typeof config.WorkingDir === 'string' && config.WorkingDir.trim()
|
|
452
|
+
? config.WorkingDir
|
|
453
|
+
: '/tmp';
|
|
454
|
+
changes.push('--change', `WORKDIR ${workingDir}`);
|
|
455
|
+
|
|
456
|
+
if (typeof config.User === 'string' && config.User.trim()) {
|
|
457
|
+
changes.push('--change', `USER ${config.User}`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const exposedPorts = config.ExposedPorts && typeof config.ExposedPorts === 'object'
|
|
461
|
+
? Object.keys(config.ExposedPorts)
|
|
462
|
+
: [];
|
|
463
|
+
exposedPorts.forEach(port => {
|
|
464
|
+
if (port) changes.push('--change', `EXPOSE ${port}`);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const labels = config.Labels && typeof config.Labels === 'object' ? config.Labels : {};
|
|
468
|
+
Object.entries(labels).forEach(([key, value]) => {
|
|
469
|
+
changes.push('--change', `LABEL ${key}=${value}`);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
return changes;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function updateAgentsInExistingImage(ctx, fullImageTag) {
|
|
476
|
+
const { RED, GREEN, YELLOW, BLUE, CYAN, NC } = ctx.colors;
|
|
477
|
+
const containerName = ctx.agentUpdateContainerName || createAgentUpdateContainerName();
|
|
478
|
+
const updateScript = [
|
|
479
|
+
'echo "[manyoyo] Agent CLI versions before update:"',
|
|
480
|
+
AGENT_VERSION_CHECK_COMMAND,
|
|
481
|
+
`npm_config_update_notifier=false npm install -g ${AGENT_UPDATE_PACKAGES.map(quoteShellArg).join(' ')}`,
|
|
482
|
+
'echo "[manyoyo] Agent CLI versions after update:"',
|
|
483
|
+
AGENT_VERSION_CHECK_COMMAND,
|
|
484
|
+
'npm_config_update_notifier=false npm cache clean --force --loglevel=error',
|
|
485
|
+
'rm -rf /tmp/* /var/tmp/* /var/log/apt /var/log/*.log /var/lib/apt/lists/* ~/.npm ~/.cache/node-gyp ~/.claude/plugins/cache ~/go/pkg/mod/cache',
|
|
486
|
+
'rm -f /var/log/dpkg.log /var/log/bootstrap.log /var/lib/dpkg/status-old /var/cache/debconf/templates.dat-old'
|
|
487
|
+
].join(' && ');
|
|
488
|
+
const runArgs = [
|
|
489
|
+
'run',
|
|
490
|
+
'--name', containerName,
|
|
491
|
+
'--network', 'host',
|
|
492
|
+
fullImageTag,
|
|
493
|
+
'/bin/bash',
|
|
494
|
+
'-lc',
|
|
495
|
+
updateScript
|
|
496
|
+
];
|
|
497
|
+
const cleanupArgs = ['rm', '-f', containerName];
|
|
498
|
+
let imageConfig = {};
|
|
499
|
+
|
|
500
|
+
ctx.log(`${CYAN}🔄 正在更新已有镜像内 Agent CLI: ${YELLOW}${fullImageTag}${NC}`);
|
|
501
|
+
ctx.log(`${BLUE}更新范围: Claude/Codex/Gemini/OpenCode CLI -> latest${NC}\n`);
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
imageConfig = parseImageConfig(ctx.runCmd(ctx.dockerCmd, [
|
|
505
|
+
'image',
|
|
506
|
+
'inspect',
|
|
507
|
+
fullImageTag,
|
|
508
|
+
'--format',
|
|
509
|
+
'{{json .Config}}'
|
|
510
|
+
], { stdio: 'pipe' }));
|
|
511
|
+
} catch (e) {
|
|
512
|
+
ctx.error(`${RED}错误: 找不到本地镜像 ${fullImageTag}${NC}`);
|
|
513
|
+
ctx.error(`${YELLOW}请先执行 ${ctx.manyoyoName} build --iv ${fullImageTag.split(':').pop()} --yes 构建镜像。${NC}`);
|
|
514
|
+
ctx.exit(1);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const importArgs = [
|
|
519
|
+
'import',
|
|
520
|
+
...buildImageImportChangeArgs(imageConfig),
|
|
521
|
+
'-',
|
|
522
|
+
fullImageTag
|
|
523
|
+
];
|
|
524
|
+
|
|
525
|
+
ctx.log(`${BLUE}准备执行命令:${NC}`);
|
|
526
|
+
ctx.log(`${ctx.dockerCmd} ${runArgs.map(quoteShellArg).join(' ')}`);
|
|
527
|
+
ctx.log(`${ctx.dockerCmd} export ${quoteShellArg(containerName)} | ${ctx.dockerCmd} ${importArgs.map(quoteShellArg).join(' ')}`);
|
|
528
|
+
ctx.log(`${ctx.dockerCmd} ${cleanupArgs.map(quoteShellArg).join(' ')}\n`);
|
|
529
|
+
|
|
530
|
+
if (!ctx.yesMode) {
|
|
531
|
+
await ctx.askQuestion('❔ 是否继续更新并覆盖当前镜像 tag? [ 直接回车=继续, ctrl+c=取消 ]: ');
|
|
532
|
+
ctx.log('');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const updateStartTime = Date.now();
|
|
536
|
+
try {
|
|
537
|
+
ctx.runCmd(ctx.dockerCmd, runArgs, { stdio: 'inherit' });
|
|
538
|
+
await ctx.runCmdPipeline(ctx.dockerCmd, ['export', containerName], ctx.dockerCmd, importArgs, { stdio: 'inherit' });
|
|
539
|
+
const updateDuration = ((Date.now() - updateStartTime) / 1000).toFixed(1);
|
|
540
|
+
ctx.log(`\n${GREEN}✅ Agent CLI 更新成功: ${fullImageTag}${NC}`);
|
|
541
|
+
ctx.log(`${GREEN}⏱️ 更新耗时: ${updateDuration} 秒${NC}`);
|
|
542
|
+
} catch (e) {
|
|
543
|
+
ctx.error(`${RED}错误: Agent CLI 更新失败${NC}`);
|
|
544
|
+
ctx.exit(1);
|
|
545
|
+
} finally {
|
|
546
|
+
try {
|
|
547
|
+
ctx.runCmd(ctx.dockerCmd, cleanupArgs, { stdio: 'inherit', ignoreError: true });
|
|
548
|
+
} catch (e) {}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
399
552
|
async function buildImage(options = {}) {
|
|
400
553
|
const ctx = {
|
|
401
554
|
imageBuildArgs: Array.isArray(options.imageBuildArgs) ? [...options.imageBuildArgs] : [],
|
|
@@ -406,6 +559,8 @@ async function buildImage(options = {}) {
|
|
|
406
559
|
parseImageVersionTag: options.parseImageVersionTag,
|
|
407
560
|
manyoyoName: options.manyoyoName || 'manyoyo',
|
|
408
561
|
yesMode: Boolean(options.yesMode),
|
|
562
|
+
updateAgents: Boolean(options.updateAgents),
|
|
563
|
+
agentUpdateContainerName: options.agentUpdateContainerName || '',
|
|
409
564
|
dockerCmd: options.dockerCmd || 'docker',
|
|
410
565
|
rootDir: options.rootDir || process.cwd(),
|
|
411
566
|
loadConfig: options.loadConfig || (() => ({})),
|
|
@@ -440,6 +595,11 @@ async function buildImage(options = {}) {
|
|
|
440
595
|
}
|
|
441
596
|
|
|
442
597
|
const fullImageTag = `${ctx.imageName}:${version}-${imageTool}`;
|
|
598
|
+
if (ctx.updateAgents) {
|
|
599
|
+
await updateAgentsInExistingImage(ctx, fullImageTag);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
443
603
|
ctx.log(`${CYAN}🔨 正在构建镜像: ${YELLOW}${fullImageTag}${NC}`);
|
|
444
604
|
ctx.log(`${BLUE}构建组件类型: ${imageTool}${NC}\n`);
|
|
445
605
|
|
package/lib/runtime-resolver.js
CHANGED
|
@@ -23,6 +23,7 @@ function resolveRuntimeConfig(options = {}) {
|
|
|
23
23
|
const mergeArrayConfig = params.mergeArrayConfig;
|
|
24
24
|
const normalizeVolume = params.normalizeVolume;
|
|
25
25
|
const parseServerListen = params.parseServerListen;
|
|
26
|
+
const resolveWorktreeSupport = params.resolveWorktreeSupport;
|
|
26
27
|
const argv = Array.isArray(params.argv) ? params.argv : [];
|
|
27
28
|
const isServerMode = params.isServerMode === true;
|
|
28
29
|
const isServerStopMode = params.isServerStopMode === true;
|
|
@@ -104,7 +105,7 @@ function resolveRuntimeConfig(options = {}) {
|
|
|
104
105
|
...normalizeCliEnvMap(cliOptions.firstEnv)
|
|
105
106
|
};
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
let volumes = mergeArrayConfig(globalConfig.volumes, runConfig.volumes, cliOptions.volume)
|
|
108
109
|
.map(volume => normalizeVolume(volume));
|
|
109
110
|
const ports = mergeArrayConfig(globalConfig.ports, runConfig.ports, cliOptions.port);
|
|
110
111
|
const imageBuildArgs = mergeArrayConfig(globalConfig.imageBuildArgs, runConfig.imageBuildArgs, cliOptions.imageBuildArg);
|
|
@@ -151,6 +152,25 @@ function resolveRuntimeConfig(options = {}) {
|
|
|
151
152
|
hostPath = defaults.hostPath;
|
|
152
153
|
}
|
|
153
154
|
|
|
155
|
+
let worktreeState = {
|
|
156
|
+
enabled: false,
|
|
157
|
+
worktreesRoot: null,
|
|
158
|
+
worktreeRepoRoot: null,
|
|
159
|
+
worktreeMainRepoRoot: null,
|
|
160
|
+
extraVolumes: []
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
if (typeof resolveWorktreeSupport === 'function') {
|
|
164
|
+
worktreeState = resolveWorktreeSupport({
|
|
165
|
+
enabled: Boolean(cliOptions.worktrees || cliOptions.worktreesRoot),
|
|
166
|
+
hostPath,
|
|
167
|
+
containerPath,
|
|
168
|
+
worktreesRoot: cliOptions.worktreesRoot,
|
|
169
|
+
volumes
|
|
170
|
+
});
|
|
171
|
+
volumes = volumes.concat(worktreeState.extraVolumes || []);
|
|
172
|
+
}
|
|
173
|
+
|
|
154
174
|
return {
|
|
155
175
|
hostPath,
|
|
156
176
|
containerName,
|
|
@@ -164,6 +184,10 @@ function resolveRuntimeConfig(options = {}) {
|
|
|
164
184
|
volumes,
|
|
165
185
|
ports,
|
|
166
186
|
imageBuildArgs,
|
|
187
|
+
worktrees: Boolean(worktreeState.enabled),
|
|
188
|
+
worktreesRoot: worktreeState.worktreesRoot,
|
|
189
|
+
worktreeRepoRoot: worktreeState.worktreeRepoRoot,
|
|
190
|
+
worktreeMainRepoRoot: worktreeState.worktreeMainRepoRoot,
|
|
167
191
|
containerMode,
|
|
168
192
|
yolo,
|
|
169
193
|
quiet,
|