@xcanwin/manyoyo 4.1.10 → 4.2.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 +3 -3
- package/bin/manyoyo.js +13 -3
- package/config.example.json +1 -1
- package/lib/agent-resume.js +14 -1
- package/lib/image-build.js +167 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
```bash
|
|
47
47
|
npm install -g @xcanwin/manyoyo # 安装
|
|
48
48
|
podman pull ubuntu:24.04 # 仅 Podman 需要
|
|
49
|
-
manyoyo --ib --iv 1.
|
|
49
|
+
manyoyo --ib --iv 1.8.0-common # 构建镜像
|
|
50
50
|
manyoyo --init-config all # 从本机 Agent 配置迁移到 ~/.manyoyo
|
|
51
51
|
manyoyo -r claude # 使用 manyoyo.json 的 runs.claude 启动
|
|
52
52
|
```
|
|
@@ -97,10 +97,10 @@ npm install -g @xcanwin/manyoyo
|
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
99
|
# 构建 common 版本(推荐)
|
|
100
|
-
manyoyo --ib --iv 1.
|
|
100
|
+
manyoyo --ib --iv 1.8.0-common
|
|
101
101
|
|
|
102
102
|
# 构建 full 版本
|
|
103
|
-
manyoyo --ib --iv 1.
|
|
103
|
+
manyoyo --ib --iv 1.8.0-full
|
|
104
104
|
|
|
105
105
|
# 构建自定义版本
|
|
106
106
|
manyoyo --ib --iba TOOL=go,codex,java,gemini
|
package/bin/manyoyo.js
CHANGED
|
@@ -13,7 +13,7 @@ const { startWebServer } = require('../lib/web/server');
|
|
|
13
13
|
const { buildContainerRunArgs, buildContainerRunCommand } = require('../lib/container-run');
|
|
14
14
|
const { initAgentConfigs } = require('../lib/init-config');
|
|
15
15
|
const { buildImage } = require('../lib/image-build');
|
|
16
|
-
const { resolveAgentResumeArg } = require('../lib/agent-resume');
|
|
16
|
+
const { resolveAgentResumeArg, buildAgentResumeCommand } = require('../lib/agent-resume');
|
|
17
17
|
const { version: BIN_VERSION, imageVersion: IMAGE_VERSION_DEFAULT } = require('../package.json');
|
|
18
18
|
const IMAGE_VERSION_BASE = String(IMAGE_VERSION_DEFAULT || '1.0.0').split('-')[0];
|
|
19
19
|
const IMAGE_VERSION_HELP_EXAMPLE = IMAGE_VERSION_DEFAULT || `${IMAGE_VERSION_BASE}-common`;
|
|
@@ -1210,8 +1210,12 @@ async function handlePostExit(runtime, defaultCommand) {
|
|
|
1210
1210
|
|
|
1211
1211
|
getHelloTip(runtime.containerName, defaultCommand, runtime.execCommand);
|
|
1212
1212
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1213
|
+
const resumeCommand = buildAgentResumeCommand(defaultCommand);
|
|
1214
|
+
const hasResumeAction = Boolean(resumeCommand);
|
|
1215
|
+
const menuResume = hasResumeAction ? ', r=恢复首次命令会话' : '';
|
|
1216
|
+
const quietResume = hasResumeAction ? ' r' : '';
|
|
1217
|
+
let tipAskKeep = `❔ 会话已结束。是否保留此后台容器 ${runtime.containerName}? [ y=默认保留, n=删除, 1=首次命令进入${menuResume}, x=执行命令, i=交互式SHELL ]: `;
|
|
1218
|
+
if (runtime.quiet.askkeep || runtime.quiet.full) tipAskKeep = `保留容器吗? [y n 1${quietResume} x i] `;
|
|
1215
1219
|
const reply = await askQuestion(tipAskKeep);
|
|
1216
1220
|
|
|
1217
1221
|
const firstChar = reply.trim().toLowerCase()[0];
|
|
@@ -1225,6 +1229,12 @@ async function handlePostExit(runtime, defaultCommand) {
|
|
|
1225
1229
|
runtime.execCommandSuffix = "";
|
|
1226
1230
|
runtime.execCommand = defaultCommand;
|
|
1227
1231
|
return true;
|
|
1232
|
+
} else if (firstChar === 'r' && hasResumeAction) {
|
|
1233
|
+
if (!(runtime.quiet.full)) console.log(`${GREEN}✅ 离开当前连接,恢复首次命令会话。${NC}`);
|
|
1234
|
+
runtime.execCommandPrefix = "";
|
|
1235
|
+
runtime.execCommandSuffix = "";
|
|
1236
|
+
runtime.execCommand = resumeCommand;
|
|
1237
|
+
return true;
|
|
1228
1238
|
} else if (firstChar === 'x') {
|
|
1229
1239
|
const command = await askQuestion('❔ 输入要执行的命令: ');
|
|
1230
1240
|
if (!(runtime.quiet.cmd || runtime.quiet.full)) console.log(`${GREEN}✅ 离开当前连接,执行命令。${NC}`);
|
package/config.example.json
CHANGED
package/lib/agent-resume.js
CHANGED
|
@@ -67,6 +67,19 @@ function resolveAgentResumeArg(commandText) {
|
|
|
67
67
|
return AGENT_RESUME_ARG_MAP[program] || '';
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function buildAgentResumeCommand(commandText) {
|
|
71
|
+
const baseCommand = String(commandText || '').trim();
|
|
72
|
+
if (!baseCommand) {
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
const resumeArg = resolveAgentResumeArg(baseCommand);
|
|
76
|
+
if (!resumeArg) {
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
return `${baseCommand} ${resumeArg}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
70
82
|
module.exports = {
|
|
71
|
-
resolveAgentResumeArg
|
|
83
|
+
resolveAgentResumeArg,
|
|
84
|
+
buildAgentResumeCommand
|
|
72
85
|
};
|
package/lib/image-build.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const crypto = require('crypto');
|
|
6
|
+
const { spawn } = require('child_process');
|
|
6
7
|
|
|
7
8
|
function getFileSha256(filePath) {
|
|
8
9
|
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
@@ -233,6 +234,135 @@ function resolveToolFromBuildArgs(args) {
|
|
|
233
234
|
return '';
|
|
234
235
|
}
|
|
235
236
|
|
|
237
|
+
function extractBuildArgValues(args) {
|
|
238
|
+
const values = [];
|
|
239
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
240
|
+
const current = args[i];
|
|
241
|
+
if (current === '--build-arg') {
|
|
242
|
+
const next = args[i + 1];
|
|
243
|
+
if (typeof next === 'string' && next.length > 0) {
|
|
244
|
+
values.push(next);
|
|
245
|
+
i += 1;
|
|
246
|
+
}
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (typeof current === 'string' && current.startsWith('--build-arg=')) {
|
|
250
|
+
values.push(current.slice('--build-arg='.length));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return values;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function isBuildCapabilityError(error) {
|
|
257
|
+
const combined = [
|
|
258
|
+
error && error.message ? error.message : '',
|
|
259
|
+
error && error.stderr ? String(error.stderr) : '',
|
|
260
|
+
error && error.stdout ? String(error.stdout) : ''
|
|
261
|
+
].join('\n').toLowerCase();
|
|
262
|
+
|
|
263
|
+
const patterns = [
|
|
264
|
+
/unknown flag/,
|
|
265
|
+
/unknown shorthand flag/,
|
|
266
|
+
/unknown option/,
|
|
267
|
+
/unrecognized option/,
|
|
268
|
+
/unknown instruction:\s*"?((case|if|then|fi|for|while|do|done|esac))"?/,
|
|
269
|
+
/buildx .* not available/,
|
|
270
|
+
/buildx .* not found/,
|
|
271
|
+
/buildx .* not enabled/,
|
|
272
|
+
/'buildx' is not a docker command/,
|
|
273
|
+
/the --load option requires buildx/,
|
|
274
|
+
/unsupported.*--load/,
|
|
275
|
+
/does not support.*--load/,
|
|
276
|
+
/driver .* not supported/,
|
|
277
|
+
/no such plugin.*buildx/
|
|
278
|
+
];
|
|
279
|
+
return patterns.some(pattern => pattern.test(combined));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function buildBuildkitRunArgs(ctx, dockerfilePath, fullImageTag, imageBuildArgs) {
|
|
283
|
+
const dockerfileRelativePath = path.relative(ctx.rootDir, dockerfilePath).split(path.sep).join('/');
|
|
284
|
+
const buildArgs = extractBuildArgValues(imageBuildArgs);
|
|
285
|
+
const args = [
|
|
286
|
+
'run', '--rm', '--privileged',
|
|
287
|
+
'--network', `host`,
|
|
288
|
+
'--volume', `${ctx.rootDir}:/workspace`,
|
|
289
|
+
'--entrypoint', 'buildctl-daemonless.sh',
|
|
290
|
+
'docker.io/moby/buildkit:latest',
|
|
291
|
+
'build',
|
|
292
|
+
'--frontend', 'dockerfile.v0',
|
|
293
|
+
'--local', 'context=/workspace',
|
|
294
|
+
'--local', 'dockerfile=/workspace',
|
|
295
|
+
'--opt', `filename=${dockerfileRelativePath}`,
|
|
296
|
+
'--opt', `build-arg:HTTPS_PROXY=$HTTPS_PROXY`,
|
|
297
|
+
'--opt', `build-arg:HTTPS_PROXY=$HTTPS_PROXY`,
|
|
298
|
+
'--opt', `build-arg:ALL_PROXY=$ALL_PROXY`,
|
|
299
|
+
'--opt', `build-arg:NO_PROXY=$NO_PROXY`
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
for (const value of buildArgs) {
|
|
303
|
+
args.push('--opt', `build-arg:${value}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
args.push('--output', `type=docker,name=${fullImageTag},dest=-`);
|
|
307
|
+
return args;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function runCmdPipeline(leftCmd, leftArgs, rightCmd, rightArgs, options = {}) {
|
|
311
|
+
const stdio = options.stdio || 'inherit';
|
|
312
|
+
|
|
313
|
+
return new Promise((resolve, reject) => {
|
|
314
|
+
let settled = false;
|
|
315
|
+
const left = spawn(leftCmd, leftArgs, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
316
|
+
const right = spawn(rightCmd, rightArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
317
|
+
right.stdin.on('error', () => {});
|
|
318
|
+
left.stdout.pipe(right.stdin);
|
|
319
|
+
|
|
320
|
+
if (stdio === 'inherit') {
|
|
321
|
+
left.stderr.pipe(process.stderr);
|
|
322
|
+
right.stdout.pipe(process.stdout);
|
|
323
|
+
right.stderr.pipe(process.stderr);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let leftExited = false;
|
|
327
|
+
let rightExited = false;
|
|
328
|
+
let leftCode = 1;
|
|
329
|
+
let rightCode = 1;
|
|
330
|
+
|
|
331
|
+
const finish = (error) => {
|
|
332
|
+
if (settled) return;
|
|
333
|
+
settled = true;
|
|
334
|
+
if (error) {
|
|
335
|
+
try { left.kill(); } catch (e) {}
|
|
336
|
+
try { right.kill(); } catch (e) {}
|
|
337
|
+
reject(error);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (leftCode === 0 && rightCode === 0) {
|
|
341
|
+
resolve();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
reject(new Error(`Command pipeline failed: ${leftCmd}(${leftCode}) | ${rightCmd}(${rightCode})`));
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const onClose = () => {
|
|
348
|
+
if (leftExited && rightExited) finish();
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
left.on('error', finish);
|
|
352
|
+
right.on('error', finish);
|
|
353
|
+
left.on('close', (code) => {
|
|
354
|
+
leftExited = true;
|
|
355
|
+
leftCode = typeof code === 'number' ? code : 1;
|
|
356
|
+
onClose();
|
|
357
|
+
});
|
|
358
|
+
right.on('close', (code) => {
|
|
359
|
+
rightExited = true;
|
|
360
|
+
rightCode = typeof code === 'number' ? code : 1;
|
|
361
|
+
onClose();
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
236
366
|
async function buildImage(options = {}) {
|
|
237
367
|
const ctx = {
|
|
238
368
|
imageBuildArgs: Array.isArray(options.imageBuildArgs) ? [...options.imageBuildArgs] : [],
|
|
@@ -247,6 +377,7 @@ async function buildImage(options = {}) {
|
|
|
247
377
|
rootDir: options.rootDir || process.cwd(),
|
|
248
378
|
loadConfig: options.loadConfig || (() => ({})),
|
|
249
379
|
runCmd: options.runCmd,
|
|
380
|
+
runCmdPipeline: options.runCmdPipeline || runCmdPipeline,
|
|
250
381
|
askQuestion: options.askQuestion || (async () => ''),
|
|
251
382
|
pruneDanglingImages: options.pruneDanglingImages || (() => {}),
|
|
252
383
|
cacheTtlDays: options.cacheTtlDays || 2,
|
|
@@ -297,6 +428,14 @@ async function buildImage(options = {}) {
|
|
|
297
428
|
'--progress=plain',
|
|
298
429
|
'--no-cache'
|
|
299
430
|
];
|
|
431
|
+
const buildkitRunArgs = buildBuildkitRunArgs(ctx, dockerfilePath, fullImageTag, imageBuildArgs);
|
|
432
|
+
|
|
433
|
+
function logBuildSuccess() {
|
|
434
|
+
ctx.log(`\n${GREEN}✅ 镜像构建成功: ${fullImageTag}${NC}`);
|
|
435
|
+
ctx.log(`${BLUE}使用镜像:${NC}`);
|
|
436
|
+
ctx.log(` ${ctx.manyoyoName} -n test --in ${ctx.imageName} --iv ${version}-${imageTool} -y c`);
|
|
437
|
+
ctx.pruneDanglingImages();
|
|
438
|
+
}
|
|
300
439
|
|
|
301
440
|
ctx.log(`${BLUE}准备执行命令:${NC}`);
|
|
302
441
|
ctx.log(`${ctx.dockerCmd} ${buildArgs.map(quoteShellArg).join(' ')}\n`);
|
|
@@ -308,10 +447,34 @@ async function buildImage(options = {}) {
|
|
|
308
447
|
|
|
309
448
|
try {
|
|
310
449
|
ctx.runCmd(ctx.dockerCmd, buildArgs, { stdio: 'inherit' });
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
450
|
+
logBuildSuccess();
|
|
451
|
+
return;
|
|
452
|
+
} catch (e) {
|
|
453
|
+
const stderrText = e && e.stderr ? String(e.stderr).trim() : '';
|
|
454
|
+
const stdoutText = e && e.stdout ? String(e.stdout).trim() : '';
|
|
455
|
+
const hasDiagnostics = Boolean(stderrText || stdoutText);
|
|
456
|
+
const capabilityError = isBuildCapabilityError(e);
|
|
457
|
+
|
|
458
|
+
if (!capabilityError && hasDiagnostics) {
|
|
459
|
+
ctx.error(`${RED}错误: 镜像构建失败${NC}`);
|
|
460
|
+
ctx.exit(1);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (!capabilityError && !hasDiagnostics) {
|
|
464
|
+
ctx.log(`${YELLOW}⚠️ 未捕获到构建器错误详情,尝试回退到 BuildKit...${NC}`);
|
|
465
|
+
}
|
|
466
|
+
ctx.log(`${YELLOW}⚠️ 直接 build 失败,回退到 BuildKit...${NC}`);
|
|
467
|
+
if (e && e.message) {
|
|
468
|
+
ctx.log(`${YELLOW}原因: ${e.message}${NC}`);
|
|
469
|
+
}
|
|
470
|
+
ctx.log('');
|
|
471
|
+
ctx.log(`${BLUE}回退命令:${NC}`);
|
|
472
|
+
ctx.log(`${ctx.dockerCmd} ${buildkitRunArgs.map(quoteShellArg).join(' ')} | ${ctx.dockerCmd} load\n`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
try {
|
|
476
|
+
await ctx.runCmdPipeline(ctx.dockerCmd, buildkitRunArgs, ctx.dockerCmd, ['load'], { stdio: 'inherit' });
|
|
477
|
+
logBuildSuccess();
|
|
315
478
|
} catch (e) {
|
|
316
479
|
ctx.error(`${RED}错误: 镜像构建失败${NC}`);
|
|
317
480
|
ctx.exit(1);
|