@xcanwin/manyoyo 5.9.11 → 5.10.4
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 +11 -0
- package/bin/manyoyo.js +33 -2
- package/docker/res/update-agents.sh +45 -0
- package/lib/image-build.js +164 -0
- package/lib/runtime-resolver.js +25 -1
- 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 往往需要:
|
|
@@ -145,6 +153,9 @@ manyoyo build --iv 1.9.0-common
|
|
|
145
153
|
# full 版本
|
|
146
154
|
manyoyo build --iv 1.9.0-full
|
|
147
155
|
|
|
156
|
+
# 仅更新已有镜像内 Agent CLI 到 latest,不重建 Dockerfile
|
|
157
|
+
manyoyo build --iv 1.9.0-full --update-agents --yes
|
|
158
|
+
|
|
148
159
|
# 自定义工具集
|
|
149
160
|
manyoyo build --iba TOOL=go,codex,java,gemini
|
|
150
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>)')
|
|
@@ -1119,6 +1138,7 @@ async function setupCommander() {
|
|
|
1119
1138
|
示例:
|
|
1120
1139
|
${MANYOYO_NAME} update 更新 MANYOYO 到最新版本
|
|
1121
1140
|
${MANYOYO_NAME} build --iv ${IMAGE_VERSION_HELP_EXAMPLE} --yes 构建镜像
|
|
1141
|
+
${MANYOYO_NAME} build --update-agents --yes 仅更新已有镜像内已存在的 Agent CLI
|
|
1122
1142
|
${MANYOYO_NAME} init all 从本机 Agent 配置初始化 ~/.manyoyo
|
|
1123
1143
|
${MANYOYO_NAME} run -r claude 使用 manyoyo.json 的 runs.claude 快速启动
|
|
1124
1144
|
${MANYOYO_NAME} run -r codex --ss "resume --last" 使用命令后缀
|
|
@@ -1156,6 +1176,7 @@ Notes:
|
|
|
1156
1176
|
.option('-r, --run <name>', '加载运行配置 (从 ~/.manyoyo/manyoyo.json 的 runs.<name> 读取)')
|
|
1157
1177
|
.option('--in, --image-name <name>', '指定镜像名称')
|
|
1158
1178
|
.option('--iv, --image-ver <version>', '指定镜像版本 (格式: x.y.z-后缀,如 1.7.4-common)')
|
|
1179
|
+
.option('--update-agents', '仅更新已有镜像内 Agent CLI 到 latest (Claude/Codex/Gemini/OpenCode)')
|
|
1159
1180
|
.option('--yes', '所有提示自动确认 (用于CI/脚本)');
|
|
1160
1181
|
appendArrayOption(buildCommand, '--iba, --image-build-arg <arg>', '构建镜像时传参给dockerfile (可多次使用)');
|
|
1161
1182
|
buildCommand.action(options => selectAction('build', options));
|
|
@@ -1260,6 +1281,7 @@ Notes:
|
|
|
1260
1281
|
|
|
1261
1282
|
// Pre-handle -x/--shell-full: treat all following args as a single command
|
|
1262
1283
|
normalizeShellFullArgv(process.argv);
|
|
1284
|
+
normalizeWorktreeArgv(process.argv);
|
|
1263
1285
|
|
|
1264
1286
|
// Parse arguments
|
|
1265
1287
|
program.allowUnknownOption(false);
|
|
@@ -1357,7 +1379,8 @@ Notes:
|
|
|
1357
1379
|
normalizeCliEnvMap,
|
|
1358
1380
|
mergeArrayConfig,
|
|
1359
1381
|
normalizeVolume,
|
|
1360
|
-
parseServerListen
|
|
1382
|
+
parseServerListen,
|
|
1383
|
+
resolveWorktreeSupport
|
|
1361
1384
|
});
|
|
1362
1385
|
|
|
1363
1386
|
HOST_PATH = resolvedRuntime.hostPath;
|
|
@@ -1432,6 +1455,10 @@ Notes:
|
|
|
1432
1455
|
volumes: volumeList,
|
|
1433
1456
|
ports: portList,
|
|
1434
1457
|
imageBuildArgs: buildArgList,
|
|
1458
|
+
worktrees: resolvedRuntime.worktrees,
|
|
1459
|
+
worktreesRoot: resolvedRuntime.worktreesRoot,
|
|
1460
|
+
worktreeRepoRoot: resolvedRuntime.worktreeRepoRoot,
|
|
1461
|
+
worktreeMainRepoRoot: resolvedRuntime.worktreeMainRepoRoot,
|
|
1435
1462
|
containerMode: contModeValue || "",
|
|
1436
1463
|
shellPrefix: EXEC_COMMAND_PREFIX.trim(),
|
|
1437
1464
|
shell: EXEC_COMMAND || "",
|
|
@@ -1482,6 +1509,7 @@ Notes:
|
|
|
1482
1509
|
isServerRestart: isServerRestartMode,
|
|
1483
1510
|
isServerDetach: Boolean(selectedAction === 'serve' && options.detach),
|
|
1484
1511
|
isServerListenSpecified: Boolean(isServerMode && options.server !== true),
|
|
1512
|
+
updateAgents: Boolean(options.updateAgents),
|
|
1485
1513
|
isPluginMode: false
|
|
1486
1514
|
};
|
|
1487
1515
|
}
|
|
@@ -2107,6 +2135,7 @@ async function main() {
|
|
|
2107
2135
|
parseImageVersionTag,
|
|
2108
2136
|
manyoyoName: MANYOYO_NAME,
|
|
2109
2137
|
yesMode: Boolean(modeState.yesMode),
|
|
2138
|
+
updateAgents: Boolean(modeState.updateAgents),
|
|
2110
2139
|
dockerCmd: DOCKER_CMD,
|
|
2111
2140
|
rootDir: path.join(__dirname, '..'),
|
|
2112
2141
|
loadConfig,
|
|
@@ -2115,7 +2144,9 @@ async function main() {
|
|
|
2115
2144
|
pruneDanglingImages,
|
|
2116
2145
|
colors: { RED, GREEN, YELLOW, BLUE, CYAN, NC }
|
|
2117
2146
|
});
|
|
2118
|
-
|
|
2147
|
+
if (!modeState.updateAgents) {
|
|
2148
|
+
syncBuiltImageVersionToGlobalConfig(runtime.imageVersion);
|
|
2149
|
+
}
|
|
2119
2150
|
process.exit(0);
|
|
2120
2151
|
}
|
|
2121
2152
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
TARGETS="${MANYOYO_AGENT_UPDATE_TARGETS:-claude=@anthropic-ai/claude-code@latest codex=@openai/codex@latest}"
|
|
5
|
+
|
|
6
|
+
read -r -a agent_targets <<< "$TARGETS"
|
|
7
|
+
|
|
8
|
+
print_agent_versions() {
|
|
9
|
+
for target in "${agent_targets[@]}"; do
|
|
10
|
+
agent="${target%%=*}"
|
|
11
|
+
if command -v "$agent" >/dev/null 2>&1; then
|
|
12
|
+
printf "%s: " "$agent"
|
|
13
|
+
"$agent" --version || echo "version check failed"
|
|
14
|
+
else
|
|
15
|
+
printf "%s: skipped (command not found)\n" "$agent"
|
|
16
|
+
fi
|
|
17
|
+
done
|
|
18
|
+
printf "npm: "
|
|
19
|
+
npm --version
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
update_packages=()
|
|
23
|
+
for target in "${agent_targets[@]}"; do
|
|
24
|
+
agent="${target%%=*}"
|
|
25
|
+
package_name="${target#*=}"
|
|
26
|
+
if [ -n "$agent" ] && [ -n "$package_name" ] && [ "$agent" != "$package_name" ] && command -v "$agent" >/dev/null 2>&1; then
|
|
27
|
+
update_packages+=("$package_name")
|
|
28
|
+
fi
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
echo "[manyoyo] Agent CLI versions before update:"
|
|
32
|
+
print_agent_versions
|
|
33
|
+
|
|
34
|
+
if [ "${#update_packages[@]}" -gt 0 ]; then
|
|
35
|
+
npm_config_update_notifier=false npm install -g npm@latest "${update_packages[@]}"
|
|
36
|
+
else
|
|
37
|
+
echo "[manyoyo] No existing Agent CLI found; skip npm install."
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo "[manyoyo] Agent CLI versions after update:"
|
|
41
|
+
print_agent_versions
|
|
42
|
+
|
|
43
|
+
npm_config_update_notifier=false npm cache clean --force --loglevel=error
|
|
44
|
+
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
|
|
45
|
+
rm -f /var/log/dpkg.log /var/log/bootstrap.log /var/lib/dpkg/status-old /var/cache/debconf/templates.dat-old
|
package/lib/image-build.js
CHANGED
|
@@ -396,6 +396,162 @@ function runCmdPipeline(leftCmd, leftArgs, rightCmd, rightArgs, options = {}) {
|
|
|
396
396
|
});
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
const AGENT_UPDATE_TARGETS = [
|
|
400
|
+
{ name: 'claude', packageName: '@anthropic-ai/claude-code@latest', base: true },
|
|
401
|
+
{ name: 'codex', packageName: '@openai/codex@latest', base: true },
|
|
402
|
+
{ name: 'gemini', packageName: '@google/gemini-cli@latest' },
|
|
403
|
+
{ name: 'opencode', packageName: 'opencode-ai@latest' }
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
function resolveAgentUpdateTargets(imageTool) {
|
|
407
|
+
const tools = new Set(String(imageTool || 'common').split(',').map(value => value.trim()).filter(Boolean));
|
|
408
|
+
const hasFull = tools.has('full');
|
|
409
|
+
return AGENT_UPDATE_TARGETS.filter(target => target.base || hasFull || tools.has(target.name));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function formatAgentUpdateTargets(targets) {
|
|
413
|
+
return targets.map(target => `${target.name}=${target.packageName}`).join(' ');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function createAgentUpdateContainerName() {
|
|
417
|
+
return `manyoyo-update-agents-${crypto.randomBytes(6).toString('hex')}`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const MANYOYO_DEFAULT_CMD = ['supervisord', '-n', '-c', '/etc/supervisor/supervisord.conf'];
|
|
421
|
+
|
|
422
|
+
function parseImageConfig(rawConfig) {
|
|
423
|
+
if (!rawConfig || typeof rawConfig !== 'string') return {};
|
|
424
|
+
try {
|
|
425
|
+
const parsed = JSON.parse(rawConfig);
|
|
426
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
427
|
+
} catch (e) {
|
|
428
|
+
return {};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function isAgentUpdateCommand(cmd) {
|
|
433
|
+
const text = Array.isArray(cmd) ? cmd.join(' ') : String(cmd || '');
|
|
434
|
+
return AGENT_UPDATE_TARGETS.some(target => text.includes(target.packageName));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function buildImageImportChangeArgs(config) {
|
|
438
|
+
const changes = [];
|
|
439
|
+
const envList = Array.isArray(config.Env) ? config.Env : [];
|
|
440
|
+
envList.forEach(env => {
|
|
441
|
+
if (typeof env === 'string' && env.trim()) {
|
|
442
|
+
changes.push('--change', `ENV ${env}`);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
if (config.Entrypoint) {
|
|
447
|
+
changes.push('--change', `ENTRYPOINT ${JSON.stringify(config.Entrypoint)}`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const cmd = isAgentUpdateCommand(config.Cmd) ? MANYOYO_DEFAULT_CMD : (config.Cmd || MANYOYO_DEFAULT_CMD);
|
|
451
|
+
changes.push('--change', `CMD ${JSON.stringify(cmd)}`);
|
|
452
|
+
|
|
453
|
+
const workingDir = typeof config.WorkingDir === 'string' && config.WorkingDir.trim()
|
|
454
|
+
? config.WorkingDir
|
|
455
|
+
: '/tmp';
|
|
456
|
+
changes.push('--change', `WORKDIR ${workingDir}`);
|
|
457
|
+
|
|
458
|
+
if (typeof config.User === 'string' && config.User.trim()) {
|
|
459
|
+
changes.push('--change', `USER ${config.User}`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const exposedPorts = config.ExposedPorts && typeof config.ExposedPorts === 'object'
|
|
463
|
+
? Object.keys(config.ExposedPorts)
|
|
464
|
+
: [];
|
|
465
|
+
exposedPorts.forEach(port => {
|
|
466
|
+
if (port) changes.push('--change', `EXPOSE ${port}`);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const labels = config.Labels && typeof config.Labels === 'object' ? config.Labels : {};
|
|
470
|
+
Object.entries(labels).forEach(([key, value]) => {
|
|
471
|
+
changes.push('--change', `LABEL ${key}=${value}`);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
return changes;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async function updateAgentsInExistingImage(ctx, fullImageTag) {
|
|
478
|
+
const { RED, GREEN, YELLOW, BLUE, CYAN, NC } = ctx.colors;
|
|
479
|
+
const containerName = ctx.agentUpdateContainerName || createAgentUpdateContainerName();
|
|
480
|
+
let updateTargets = resolveAgentUpdateTargets(ctx.imageTool);
|
|
481
|
+
const updateScriptPath = path.join(ctx.rootDir, 'docker', 'res', 'update-agents.sh');
|
|
482
|
+
const cleanupArgs = ['rm', '-f', containerName];
|
|
483
|
+
let imageConfig = {};
|
|
484
|
+
|
|
485
|
+
ctx.log(`${CYAN}🔄 正在更新已有镜像内 Agent CLI: ${YELLOW}${fullImageTag}${NC}`);
|
|
486
|
+
|
|
487
|
+
if (!fs.existsSync(updateScriptPath)) {
|
|
488
|
+
ctx.error(`${RED}错误: 找不到 Agent CLI 更新脚本: ${updateScriptPath}${NC}`);
|
|
489
|
+
ctx.exit(1);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
imageConfig = parseImageConfig(ctx.runCmd(ctx.dockerCmd, [
|
|
495
|
+
'image',
|
|
496
|
+
'inspect',
|
|
497
|
+
fullImageTag,
|
|
498
|
+
'--format',
|
|
499
|
+
'{{json .Config}}'
|
|
500
|
+
], { stdio: 'pipe' }));
|
|
501
|
+
} catch (e) {
|
|
502
|
+
ctx.error(`${RED}错误: 找不到本地镜像 ${fullImageTag}${NC}`);
|
|
503
|
+
ctx.error(`${YELLOW}请先执行 ${ctx.manyoyoName} build --iv ${fullImageTag.split(':').pop()} --yes 构建镜像。${NC}`);
|
|
504
|
+
ctx.exit(1);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
ctx.log(`${BLUE}更新范围: 已存在的 ${updateTargets.map(target => target.name).join('/')} CLI -> latest${NC}\n`);
|
|
509
|
+
|
|
510
|
+
const runArgs = [
|
|
511
|
+
'run',
|
|
512
|
+
'--name', containerName,
|
|
513
|
+
'--network', 'host',
|
|
514
|
+
'--volume', `${updateScriptPath}:/usr/local/bin/manyoyo-update-agents.sh:ro`,
|
|
515
|
+
'--env', `MANYOYO_AGENT_UPDATE_TARGETS=${formatAgentUpdateTargets(updateTargets)}`,
|
|
516
|
+
fullImageTag,
|
|
517
|
+
'/bin/bash',
|
|
518
|
+
'/usr/local/bin/manyoyo-update-agents.sh'
|
|
519
|
+
];
|
|
520
|
+
|
|
521
|
+
const importArgs = [
|
|
522
|
+
'import',
|
|
523
|
+
...buildImageImportChangeArgs(imageConfig),
|
|
524
|
+
'-',
|
|
525
|
+
fullImageTag
|
|
526
|
+
];
|
|
527
|
+
|
|
528
|
+
ctx.log(`${BLUE}准备执行命令:${NC}`);
|
|
529
|
+
ctx.log(`${ctx.dockerCmd} ${runArgs.map(quoteShellArg).join(' ')}`);
|
|
530
|
+
ctx.log(`${ctx.dockerCmd} export ${quoteShellArg(containerName)} | ${ctx.dockerCmd} ${importArgs.map(quoteShellArg).join(' ')}`);
|
|
531
|
+
ctx.log(`${ctx.dockerCmd} ${cleanupArgs.map(quoteShellArg).join(' ')}\n`);
|
|
532
|
+
|
|
533
|
+
if (!ctx.yesMode) {
|
|
534
|
+
await ctx.askQuestion('❔ 是否继续更新并覆盖当前镜像 tag? [ 直接回车=继续, ctrl+c=取消 ]: ');
|
|
535
|
+
ctx.log('');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const updateStartTime = Date.now();
|
|
539
|
+
try {
|
|
540
|
+
ctx.runCmd(ctx.dockerCmd, runArgs, { stdio: 'inherit' });
|
|
541
|
+
await ctx.runCmdPipeline(ctx.dockerCmd, ['export', containerName], ctx.dockerCmd, importArgs, { stdio: 'inherit' });
|
|
542
|
+
const updateDuration = ((Date.now() - updateStartTime) / 1000).toFixed(1);
|
|
543
|
+
ctx.log(`\n${GREEN}✅ Agent CLI 更新成功: ${fullImageTag}${NC}`);
|
|
544
|
+
ctx.log(`${GREEN}⏱️ 更新耗时: ${updateDuration} 秒${NC}`);
|
|
545
|
+
} catch (e) {
|
|
546
|
+
ctx.error(`${RED}错误: Agent CLI 更新失败${NC}`);
|
|
547
|
+
ctx.exit(1);
|
|
548
|
+
} finally {
|
|
549
|
+
try {
|
|
550
|
+
ctx.runCmd(ctx.dockerCmd, cleanupArgs, { stdio: 'inherit', ignoreError: true });
|
|
551
|
+
} catch (e) {}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
399
555
|
async function buildImage(options = {}) {
|
|
400
556
|
const ctx = {
|
|
401
557
|
imageBuildArgs: Array.isArray(options.imageBuildArgs) ? [...options.imageBuildArgs] : [],
|
|
@@ -406,6 +562,8 @@ async function buildImage(options = {}) {
|
|
|
406
562
|
parseImageVersionTag: options.parseImageVersionTag,
|
|
407
563
|
manyoyoName: options.manyoyoName || 'manyoyo',
|
|
408
564
|
yesMode: Boolean(options.yesMode),
|
|
565
|
+
updateAgents: Boolean(options.updateAgents),
|
|
566
|
+
agentUpdateContainerName: options.agentUpdateContainerName || '',
|
|
409
567
|
dockerCmd: options.dockerCmd || 'docker',
|
|
410
568
|
rootDir: options.rootDir || process.cwd(),
|
|
411
569
|
loadConfig: options.loadConfig || (() => ({})),
|
|
@@ -438,8 +596,14 @@ async function buildImage(options = {}) {
|
|
|
438
596
|
} else {
|
|
439
597
|
imageTool = toolFromArgs;
|
|
440
598
|
}
|
|
599
|
+
ctx.imageTool = imageTool;
|
|
441
600
|
|
|
442
601
|
const fullImageTag = `${ctx.imageName}:${version}-${imageTool}`;
|
|
602
|
+
if (ctx.updateAgents) {
|
|
603
|
+
await updateAgentsInExistingImage(ctx, fullImageTag);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
443
607
|
ctx.log(`${CYAN}🔨 正在构建镜像: ${YELLOW}${fullImageTag}${NC}`);
|
|
444
608
|
ctx.log(`${BLUE}构建组件类型: ${imageTool}${NC}\n`);
|
|
445
609
|
|
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,
|
package/lib/worktrees.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
function defaultRunGitCommand(targetPath, args) {
|
|
8
|
+
const result = spawnSync('git', ['-C', targetPath, ...args], {
|
|
9
|
+
encoding: 'utf-8'
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
if (result.status !== 0) {
|
|
13
|
+
const stderr = String(result.stderr || '').trim();
|
|
14
|
+
throw new Error(stderr || `git ${args.join(' ')} 执行失败`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return String(result.stdout || '').trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeAbsolutePath(targetPath) {
|
|
21
|
+
return path.resolve(String(targetPath || '').trim());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isDescendantPath(parentPath, targetPath) {
|
|
25
|
+
const relativePath = path.relative(parentPath, targetPath);
|
|
26
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function detectGitWorktreeContext(targetPath, deps = {}) {
|
|
30
|
+
const fsApi = deps.fs || fs;
|
|
31
|
+
const pathApi = deps.path || path;
|
|
32
|
+
const runGitCommand = deps.runGitCommand || defaultRunGitCommand;
|
|
33
|
+
const absoluteTargetPath = normalizeAbsolutePath(targetPath);
|
|
34
|
+
|
|
35
|
+
if (!fsApi.existsSync(absoluteTargetPath)) {
|
|
36
|
+
throw new Error(`启用 --worktrees 时宿主机路径不存在: ${absoluteTargetPath}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const stats = fsApi.statSync(absoluteTargetPath);
|
|
40
|
+
if (!stats.isDirectory()) {
|
|
41
|
+
throw new Error(`启用 --worktrees 时宿主机路径必须为目录: ${absoluteTargetPath}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let repoRoot;
|
|
45
|
+
let commonDirRaw;
|
|
46
|
+
try {
|
|
47
|
+
repoRoot = pathApi.resolve(runGitCommand(absoluteTargetPath, ['rev-parse', '--show-toplevel']));
|
|
48
|
+
commonDirRaw = runGitCommand(absoluteTargetPath, ['rev-parse', '--git-common-dir']);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
throw new Error(`启用 --worktrees 失败: ${absoluteTargetPath} 不在 Git 仓库内`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const commonDir = pathApi.isAbsolute(commonDirRaw)
|
|
54
|
+
? pathApi.normalize(commonDirRaw)
|
|
55
|
+
: pathApi.resolve(repoRoot, commonDirRaw);
|
|
56
|
+
const mainRepoRoot = pathApi.dirname(commonDir);
|
|
57
|
+
const projectName = pathApi.basename(mainRepoRoot);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
targetPath: absoluteTargetPath,
|
|
61
|
+
repoRoot,
|
|
62
|
+
mainRepoRoot,
|
|
63
|
+
isWorktree: repoRoot !== mainRepoRoot,
|
|
64
|
+
projectName,
|
|
65
|
+
defaultWorktreesRoot: pathApi.join(pathApi.dirname(mainRepoRoot), 'worktrees', projectName)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function shouldAddSamePathMount(hostPath, containerPath, targetPath) {
|
|
70
|
+
if (hostPath !== containerPath) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return !isDescendantPath(hostPath, targetPath);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveWorktreeSupport(options = {}, deps = {}) {
|
|
77
|
+
const fsApi = deps.fs || fs;
|
|
78
|
+
const pathApi = deps.path || path;
|
|
79
|
+
const enabled = options.enabled === true || Boolean(options.worktreesRoot);
|
|
80
|
+
|
|
81
|
+
if (!enabled) {
|
|
82
|
+
return {
|
|
83
|
+
enabled: false,
|
|
84
|
+
worktreesRoot: null,
|
|
85
|
+
worktreeRepoRoot: null,
|
|
86
|
+
worktreeMainRepoRoot: null,
|
|
87
|
+
extraVolumes: []
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const hostPath = normalizeAbsolutePath(options.hostPath);
|
|
92
|
+
const containerPath = String(options.containerPath || hostPath).trim() || hostPath;
|
|
93
|
+
const detected = detectGitWorktreeContext(hostPath, deps);
|
|
94
|
+
let worktreesRoot = options.worktreesRoot;
|
|
95
|
+
|
|
96
|
+
if (worktreesRoot !== undefined && worktreesRoot !== null && String(worktreesRoot).trim() !== '') {
|
|
97
|
+
if (!pathApi.isAbsolute(worktreesRoot)) {
|
|
98
|
+
throw new Error(`--worktrees-root 仅支持绝对路径: ${worktreesRoot}`);
|
|
99
|
+
}
|
|
100
|
+
worktreesRoot = pathApi.resolve(worktreesRoot);
|
|
101
|
+
} else {
|
|
102
|
+
worktreesRoot = detected.defaultWorktreesRoot;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fsApi.mkdirSync(worktreesRoot, { recursive: true });
|
|
106
|
+
|
|
107
|
+
const existingVolumes = new Set((options.volumes || []).map(item => String(item)));
|
|
108
|
+
const extraVolumes = [];
|
|
109
|
+
[detected.mainRepoRoot, worktreesRoot].forEach(targetPath => {
|
|
110
|
+
if (!shouldAddSamePathMount(hostPath, containerPath, targetPath)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const volume = `${targetPath}:${targetPath}`;
|
|
114
|
+
if (existingVolumes.has(volume) || extraVolumes.includes(volume)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
extraVolumes.push(volume);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
enabled: true,
|
|
122
|
+
worktreesRoot,
|
|
123
|
+
worktreeRepoRoot: detected.repoRoot,
|
|
124
|
+
worktreeMainRepoRoot: detected.mainRepoRoot,
|
|
125
|
+
extraVolumes
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
detectGitWorktreeContext,
|
|
131
|
+
resolveWorktreeSupport
|
|
132
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcanwin/manyoyo",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.10.4",
|
|
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",
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"esbuild": "^0.25.12",
|
|
88
88
|
"glob": "^13.0.6",
|
|
89
89
|
"minimatch": "^10.2.2",
|
|
90
|
+
"postcss": "^8.5.10",
|
|
90
91
|
"test-exclude": "^8.0.0",
|
|
91
92
|
"vite": "^6.4.2"
|
|
92
93
|
},
|