@xcanwin/manyoyo 4.1.10 → 4.2.0
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 +128 -1
- 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,104 @@ 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 buildPodmanBuildkitRunArgs(ctx, dockerfilePath, fullImageTag, imageBuildArgs) {
|
|
257
|
+
const dockerfileRelativePath = path.relative(ctx.rootDir, dockerfilePath).split(path.sep).join('/');
|
|
258
|
+
const buildArgs = extractBuildArgValues(imageBuildArgs);
|
|
259
|
+
const args = [
|
|
260
|
+
'run', '--rm', '--privileged',
|
|
261
|
+
'--volume', `${ctx.rootDir}:/workspace`,
|
|
262
|
+
'--entrypoint', 'buildctl-daemonless.sh',
|
|
263
|
+
'moby/buildkit:latest',
|
|
264
|
+
'build',
|
|
265
|
+
'--frontend', 'dockerfile.v0',
|
|
266
|
+
'--local', 'context=/workspace',
|
|
267
|
+
'--local', 'dockerfile=/workspace',
|
|
268
|
+
'--opt', `filename=${dockerfileRelativePath}`
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
for (const value of buildArgs) {
|
|
272
|
+
args.push('--opt', `build-arg:${value}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
args.push('--output', `type=docker,name=${fullImageTag},dest=-`);
|
|
276
|
+
return args;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function runCmdPipeline(leftCmd, leftArgs, rightCmd, rightArgs, options = {}) {
|
|
280
|
+
const stdio = options.stdio || 'inherit';
|
|
281
|
+
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
let settled = false;
|
|
284
|
+
const left = spawn(leftCmd, leftArgs, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
285
|
+
const right = spawn(rightCmd, rightArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
286
|
+
right.stdin.on('error', () => {});
|
|
287
|
+
left.stdout.pipe(right.stdin);
|
|
288
|
+
|
|
289
|
+
if (stdio === 'inherit') {
|
|
290
|
+
left.stderr.pipe(process.stderr);
|
|
291
|
+
right.stdout.pipe(process.stdout);
|
|
292
|
+
right.stderr.pipe(process.stderr);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let leftExited = false;
|
|
296
|
+
let rightExited = false;
|
|
297
|
+
let leftCode = 1;
|
|
298
|
+
let rightCode = 1;
|
|
299
|
+
|
|
300
|
+
const finish = (error) => {
|
|
301
|
+
if (settled) return;
|
|
302
|
+
settled = true;
|
|
303
|
+
if (error) {
|
|
304
|
+
try { left.kill(); } catch (e) {}
|
|
305
|
+
try { right.kill(); } catch (e) {}
|
|
306
|
+
reject(error);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (leftCode === 0 && rightCode === 0) {
|
|
310
|
+
resolve();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
reject(new Error(`Command pipeline failed: ${leftCmd}(${leftCode}) | ${rightCmd}(${rightCode})`));
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const onClose = () => {
|
|
317
|
+
if (leftExited && rightExited) finish();
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
left.on('error', finish);
|
|
321
|
+
right.on('error', finish);
|
|
322
|
+
left.on('close', (code) => {
|
|
323
|
+
leftExited = true;
|
|
324
|
+
leftCode = typeof code === 'number' ? code : 1;
|
|
325
|
+
onClose();
|
|
326
|
+
});
|
|
327
|
+
right.on('close', (code) => {
|
|
328
|
+
rightExited = true;
|
|
329
|
+
rightCode = typeof code === 'number' ? code : 1;
|
|
330
|
+
onClose();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
236
335
|
async function buildImage(options = {}) {
|
|
237
336
|
const ctx = {
|
|
238
337
|
imageBuildArgs: Array.isArray(options.imageBuildArgs) ? [...options.imageBuildArgs] : [],
|
|
@@ -247,6 +346,7 @@ async function buildImage(options = {}) {
|
|
|
247
346
|
rootDir: options.rootDir || process.cwd(),
|
|
248
347
|
loadConfig: options.loadConfig || (() => ({})),
|
|
249
348
|
runCmd: options.runCmd,
|
|
349
|
+
runCmdPipeline: options.runCmdPipeline || runCmdPipeline,
|
|
250
350
|
askQuestion: options.askQuestion || (async () => ''),
|
|
251
351
|
pruneDanglingImages: options.pruneDanglingImages || (() => {}),
|
|
252
352
|
cacheTtlDays: options.cacheTtlDays || 2,
|
|
@@ -299,13 +399,40 @@ async function buildImage(options = {}) {
|
|
|
299
399
|
];
|
|
300
400
|
|
|
301
401
|
ctx.log(`${BLUE}准备执行命令:${NC}`);
|
|
302
|
-
ctx.
|
|
402
|
+
const usePodmanBuildkit = ctx.dockerCmd === 'podman';
|
|
403
|
+
const buildkitRunArgs = usePodmanBuildkit
|
|
404
|
+
? buildPodmanBuildkitRunArgs(ctx, dockerfilePath, fullImageTag, imageBuildArgs)
|
|
405
|
+
: [];
|
|
406
|
+
if (usePodmanBuildkit) {
|
|
407
|
+
ctx.log(`${ctx.dockerCmd} ${buildkitRunArgs.map(quoteShellArg).join(' ')} | ${ctx.dockerCmd} load\n`);
|
|
408
|
+
} else {
|
|
409
|
+
ctx.log(`${ctx.dockerCmd} ${buildArgs.map(quoteShellArg).join(' ')}\n`);
|
|
410
|
+
}
|
|
303
411
|
|
|
304
412
|
if (!ctx.yesMode) {
|
|
305
413
|
await ctx.askQuestion('❔ 是否继续构建? [ 直接回车=继续, ctrl+c=取消 ]: ');
|
|
306
414
|
ctx.log('');
|
|
307
415
|
}
|
|
308
416
|
|
|
417
|
+
if (usePodmanBuildkit) {
|
|
418
|
+
try {
|
|
419
|
+
await ctx.runCmdPipeline(ctx.dockerCmd, buildkitRunArgs, ctx.dockerCmd, ['load'], { stdio: 'inherit' });
|
|
420
|
+
ctx.log(`\n${GREEN}✅ 镜像构建成功: ${fullImageTag}${NC}`);
|
|
421
|
+
ctx.log(`${BLUE}使用镜像:${NC}`);
|
|
422
|
+
ctx.log(` ${ctx.manyoyoName} -n test --in ${ctx.imageName} --iv ${version}-${imageTool} -y c`);
|
|
423
|
+
ctx.pruneDanglingImages();
|
|
424
|
+
return;
|
|
425
|
+
} catch (e) {
|
|
426
|
+
ctx.log(`${YELLOW}⚠️ BuildKit 构建失败,回退到 podman build...${NC}`);
|
|
427
|
+
if (e && e.message) {
|
|
428
|
+
ctx.log(`${YELLOW}原因: ${e.message}${NC}`);
|
|
429
|
+
}
|
|
430
|
+
ctx.log('');
|
|
431
|
+
ctx.log(`${BLUE}回退命令:${NC}`);
|
|
432
|
+
ctx.log(`${ctx.dockerCmd} ${buildArgs.map(quoteShellArg).join(' ')}\n`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
309
436
|
try {
|
|
310
437
|
ctx.runCmd(ctx.dockerCmd, buildArgs, { stdio: 'inherit' });
|
|
311
438
|
ctx.log(`\n${GREEN}✅ 镜像构建成功: ${fullImageTag}${NC}`);
|