@aiyiran/myclaw 1.0.209 → 1.0.210

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.
@@ -137,13 +137,25 @@
137
137
  ].join(';');
138
138
  header.innerHTML = '<span>\uD83C\uDFA8 \u5B66\u751F\u4F5C\u54C1</span>';
139
139
 
140
+ var headerRight = document.createElement('span');
141
+ headerRight.style.cssText = 'display:flex;align-items:center;gap:10px;';
142
+
143
+ var publishBtn = document.createElement('span');
144
+ publishBtn.textContent = '\u53D1\u5E03';
145
+ publishBtn.style.cssText = 'cursor:pointer;padding:2px 10px;border-radius:3px;font-size:12px;background:rgba(255,255,255,0.08);transition:background 0.15s;';
146
+ publishBtn.onmouseenter = function () { publishBtn.style.background = 'rgba(255,255,255,0.18)'; };
147
+ publishBtn.onmouseleave = function () { publishBtn.style.background = 'rgba(255,255,255,0.08)'; };
148
+ publishBtn.onclick = function () { openPublishModal(); };
149
+ headerRight.appendChild(publishBtn);
150
+
140
151
  var closeBtn = document.createElement('span');
141
152
  closeBtn.textContent = '\u2715';
142
153
  closeBtn.style.cssText = 'cursor:pointer;padding:2px 6px;border-radius:3px;font-size:14px;transition:background 0.15s;';
143
154
  closeBtn.onmouseenter = function () { closeBtn.style.background = 'rgba(255,255,255,0.1)'; };
144
155
  closeBtn.onmouseleave = function () { closeBtn.style.background = 'none'; };
145
156
  closeBtn.onclick = function () { closeArtifactsPanel(); };
146
- header.appendChild(closeBtn);
157
+ headerRight.appendChild(closeBtn);
158
+ header.appendChild(headerRight);
147
159
 
148
160
  // 内容区
149
161
  var content = document.createElement('div');
@@ -235,13 +247,32 @@
235
247
  ].join(';');
236
248
  tableHeader.innerHTML = [
237
249
  '<span style="width:30px;text-align:center;">#</span>',
238
- '<span style="flex:2;">标题</span>',
239
- '<span style="flex:1.5;">文件名</span>',
250
+ '<span style="flex:2;">文件名</span>',
251
+ '<span style="flex:1;text-align:center;">类别</span>',
240
252
  '<span style="flex:1;text-align:right;">更新时间</span>',
241
253
  ].join('');
242
254
  container.appendChild(tableHeader);
243
255
 
244
- data.assets.forEach(function (asset, idx) {
256
+ // updated_at 从新到旧排序
257
+ var sorted = data.assets.slice().sort(function (a, b) {
258
+ var ta = a.updated_at || '';
259
+ var tb = b.updated_at || '';
260
+ return tb.localeCompare(ta);
261
+ });
262
+
263
+ var topUpdatedAt = data.updated_at || '';
264
+
265
+ sorted.forEach(function (asset, idx) {
266
+ // 判断是否为最新(asset 的 updated_at 与顶层一致)
267
+ var isLatest = topUpdatedAt && asset.updated_at === topUpdatedAt;
268
+ var latestSeenKey = 'myclaw-artifacts-latest-seen-' + (asset.id || asset.path);
269
+ var isLatestSeen = localStorage.getItem(latestSeenKey);
270
+
271
+ // 判断是否有更新(created_at !== updated_at)
272
+ var isUpdated = asset.created_at && asset.updated_at && asset.created_at !== asset.updated_at;
273
+ var updateSeenKey = 'myclaw-artifacts-update-seen-' + (asset.id || asset.path);
274
+ var isUpdateSeen = localStorage.getItem(updateSeenKey);
275
+
245
276
  var row = document.createElement('div');
246
277
  row.style.cssText = [
247
278
  'display: flex',
@@ -253,31 +284,47 @@
253
284
  ].join(';');
254
285
  row.onmouseenter = function () { row.style.background = '#2d2d3f'; };
255
286
  row.onmouseleave = function () { row.style.background = 'transparent'; };
256
- row.onclick = function () { openPreviewModal(data, asset); };
287
+ row.onclick = function () {
288
+ if (isLatest && !isLatestSeen) localStorage.setItem(latestSeenKey, '1');
289
+ if (isUpdated && !isUpdateSeen) localStorage.setItem(updateSeenKey, '1');
290
+ openPreviewModal(data, asset);
291
+ };
257
292
 
258
293
  // 编号
259
294
  var num = document.createElement('span');
260
295
  num.style.cssText = 'width:30px;text-align:center;color:#888;flex-shrink:0;';
261
296
  num.textContent = String(idx + 1);
262
297
 
263
- // 标题
264
- var title = document.createElement('span');
265
- title.style.cssText = 'flex:2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
266
- title.textContent = asset.name || '未命名';
267
- title.title = asset.name || '';
268
-
269
- // 文件名
298
+ // 文件名(从 path 提取)+ 标记
270
299
  var fname = document.createElement('span');
271
- fname.style.cssText = 'flex:1.5;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#888;';
272
- // 从 path 中提取文件名
300
+ fname.style.cssText = 'flex:2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:6px;';
273
301
  var pathParts = (asset.path || '').split('/');
274
- fname.textContent = pathParts[pathParts.length - 1] || '';
275
- fname.title = asset.path || '';
302
+ var nameSpan = document.createElement('span');
303
+ nameSpan.textContent = pathParts[pathParts.length - 1] || asset.name || '未命名';
304
+ nameSpan.title = asset.path || '';
305
+ fname.appendChild(nameSpan);
306
+
307
+ // [最新] 优先于 [有更新]
308
+ if (isLatest && !isLatestSeen) {
309
+ var badge = document.createElement('span');
310
+ badge.style.cssText = 'color:#ff4444;font-size:10px;font-weight:bold;flex-shrink:0;';
311
+ badge.textContent = '[最新]';
312
+ fname.appendChild(badge);
313
+ } else if (isUpdated && !isUpdateSeen) {
314
+ var badge = document.createElement('span');
315
+ badge.style.cssText = 'color:#10b981;font-size:10px;font-weight:bold;flex-shrink:0;';
316
+ badge.textContent = '[有更新]';
317
+ fname.appendChild(badge);
318
+ }
319
+
320
+ // 类别
321
+ var type = document.createElement('span');
322
+ type.style.cssText = 'flex:1;text-align:center;color:#888;';
323
+ type.textContent = asset.type ? asset.type.toUpperCase() : '';
276
324
 
277
325
  // 更新时间
278
326
  var time = document.createElement('span');
279
327
  time.style.cssText = 'flex:1;text-align:right;color:#888;font-size:11px;flex-shrink:0;';
280
- // 优先用 asset 自身的,fallback 到 data 级别的 updated_at
281
328
  var updatedAt = asset.updated_at || data.updated_at || '';
282
329
  if (updatedAt) {
283
330
  try {
@@ -289,8 +336,8 @@
289
336
  }
290
337
 
291
338
  row.appendChild(num);
292
- row.appendChild(title);
293
339
  row.appendChild(fname);
340
+ row.appendChild(type);
294
341
  row.appendChild(time);
295
342
  container.appendChild(row);
296
343
  });
@@ -318,8 +365,8 @@
318
365
 
319
366
  var box = document.createElement('div');
320
367
  box.style.cssText = [
321
- 'width: 90vw',
322
- 'height: 85vh',
368
+ 'width: 98vw',
369
+ 'height: 96vh',
323
370
  'background: #1e1e2e',
324
371
  'border-radius: 8px',
325
372
  'overflow: hidden',
@@ -346,8 +393,8 @@
346
393
 
347
394
  var closeBtn = document.createElement('span');
348
395
  closeBtn.textContent = '\u2715';
349
- closeBtn.style.cssText = 'cursor:pointer;padding:2px 6px;border-radius:3px;font-size:14px;transition:background 0.15s;';
350
- closeBtn.onmouseenter = function () { closeBtn.style.background = 'rgba(255,255,255,0.1)'; };
396
+ closeBtn.style.cssText = 'cursor:pointer;padding:4px 10px;border-radius:4px;font-size:18px;font-weight:bold;transition:background 0.15s;';
397
+ closeBtn.onmouseenter = function () { closeBtn.style.background = 'rgba(255,255,255,0.15)'; };
351
398
  closeBtn.onmouseleave = function () { closeBtn.style.background = 'none'; };
352
399
  closeBtn.onclick = function () { closePreviewModal(); };
353
400
  header.appendChild(closeBtn);
@@ -366,11 +413,6 @@
366
413
  box.appendChild(iframe);
367
414
  overlay.appendChild(box);
368
415
 
369
- // 点击遮罩关闭
370
- overlay.onclick = function (e) {
371
- if (e.target === overlay) closePreviewModal();
372
- };
373
-
374
416
  document.body.appendChild(overlay);
375
417
  }
376
418
 
@@ -379,6 +421,182 @@
379
421
  if (modal) modal.remove();
380
422
  }
381
423
 
424
+ // ═══ 发布弹框 ═══
425
+ function openPublishModal() {
426
+ if (document.querySelector('#myclaw-artifacts-publish-modal')) return;
427
+ if (!cachedData) return;
428
+
429
+ var overlay = document.createElement('div');
430
+ overlay.id = 'myclaw-artifacts-publish-modal';
431
+ overlay.style.cssText = [
432
+ 'position: fixed',
433
+ 'top: 0',
434
+ 'left: 0',
435
+ 'width: 100vw',
436
+ 'height: 100vh',
437
+ 'background: rgba(0, 0, 0, 0.4)',
438
+ 'z-index: 99999',
439
+ 'display: flex',
440
+ 'align-items: center',
441
+ 'justify-content: center',
442
+ 'animation: myclaw-fade-in 0.15s ease',
443
+ ].join(';');
444
+
445
+ var box = document.createElement('div');
446
+ box.style.cssText = [
447
+ 'width: 500px',
448
+ 'background: #1e1e2e',
449
+ 'border-radius: 8px',
450
+ 'overflow: hidden',
451
+ 'display: flex',
452
+ 'flex-direction: column',
453
+ 'box-shadow: 0 8px 32px rgba(0,0,0,0.5)',
454
+ ].join(';');
455
+
456
+ // 标题栏
457
+ var header = document.createElement('div');
458
+ header.style.cssText = [
459
+ 'display: flex',
460
+ 'align-items: center',
461
+ 'justify-content: space-between',
462
+ 'padding: 10px 14px',
463
+ 'background: #2d2d3f',
464
+ 'color: #cdd6f4',
465
+ 'font-size: 13px',
466
+ 'font-family: monospace',
467
+ 'user-select: none',
468
+ 'flex-shrink: 0',
469
+ ].join(';');
470
+ header.innerHTML = '<span>\uD83D\uDE80 \u53D1\u5E03\u4F5C\u54C1</span>';
471
+
472
+ var closeBtn = document.createElement('span');
473
+ closeBtn.textContent = '\u2715';
474
+ closeBtn.style.cssText = 'cursor:pointer;padding:2px 6px;border-radius:3px;font-size:14px;transition:background 0.15s;';
475
+ closeBtn.onmouseenter = function () { closeBtn.style.background = 'rgba(255,255,255,0.1)'; };
476
+ closeBtn.onmouseleave = function () { closeBtn.style.background = 'none'; };
477
+ closeBtn.onclick = function () { closePublishModal(); };
478
+ header.appendChild(closeBtn);
479
+
480
+ // 表单内容
481
+ var form = document.createElement('div');
482
+ form.style.cssText = 'padding: 20px;display:flex;flex-direction:column;gap:16px;color:#cdd6f4;font-family:monospace;font-size:13px;';
483
+
484
+ // 字段 0:作品名称
485
+ var titleGroup = document.createElement('div');
486
+ titleGroup.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
487
+ var titleLabel = document.createElement('label');
488
+ titleLabel.textContent = '\u4F5C\u54C1\u540D\u79F0';
489
+ titleLabel.style.cssText = 'font-size:12px;color:#888;';
490
+ var titleInput = document.createElement('input');
491
+ titleInput.type = 'text';
492
+ titleInput.placeholder = '\u8F93\u5165\u5C55\u793A\u6807\u9898';
493
+ titleInput.value = cachedData.title || '';
494
+ titleInput.style.cssText = 'padding:8px 10px;background:#252536;border:1px solid #3d3d5c;border-radius:4px;color:#cdd6f4;font-size:13px;font-family:monospace;outline:none;';
495
+ titleInput.onfocus = function () { titleInput.style.borderColor = '#6c6caa'; };
496
+ titleInput.onblur = function () { titleInput.style.borderColor = '#3d3d5c'; };
497
+ titleGroup.appendChild(titleLabel);
498
+ titleGroup.appendChild(titleInput);
499
+ form.appendChild(titleGroup);
500
+
501
+ // 字段 1:选择封面图片
502
+ var coverGroup = document.createElement('div');
503
+ coverGroup.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
504
+ var coverLabel = document.createElement('label');
505
+ coverLabel.textContent = '\u9009\u62E9\u5C01\u9762\u56FE\u7247';
506
+ coverLabel.style.cssText = 'font-size:12px;color:#888;';
507
+ var coverRow = document.createElement('div');
508
+ coverRow.style.cssText = 'display:flex;align-items:center;gap:10px;';
509
+ var coverSelect = document.createElement('select');
510
+ coverSelect.style.cssText = 'flex:1;padding:8px 10px;background:#252536;border:1px solid #3d3d5c;border-radius:4px;color:#cdd6f4;font-size:13px;font-family:monospace;outline:none;';
511
+ coverSelect.onfocus = function () { coverSelect.style.borderColor = '#6c6caa'; };
512
+ coverSelect.onblur = function () { coverSelect.style.borderColor = '#3d3d5c'; };
513
+ var coverDefaultOpt = document.createElement('option');
514
+ coverDefaultOpt.value = '';
515
+ coverDefaultOpt.textContent = '\u4E0D\u9009\u62E9';
516
+ coverSelect.appendChild(coverDefaultOpt);
517
+ var imageAssets = (cachedData.assets || []).filter(function (a) { return a.type === 'image'; });
518
+ imageAssets.forEach(function (asset) {
519
+ var opt = document.createElement('option');
520
+ opt.value = asset.path;
521
+ opt.textContent = asset.name;
522
+ coverSelect.appendChild(opt);
523
+ });
524
+ var coverPreview = document.createElement('img');
525
+ coverPreview.style.cssText = 'width:60px;height:40px;object-fit:cover;border-radius:4px;border:1px solid #3d3d5c;display:none;background:#252536;';
526
+ coverSelect.onchange = function () {
527
+ if (coverSelect.value) {
528
+ coverPreview.src = buildPreviewUrl(cachedData, coverSelect.value);
529
+ coverPreview.style.display = 'block';
530
+ } else {
531
+ coverPreview.src = '';
532
+ coverPreview.style.display = 'none';
533
+ }
534
+ };
535
+ coverRow.appendChild(coverSelect);
536
+ coverRow.appendChild(coverPreview);
537
+ coverGroup.appendChild(coverLabel);
538
+ coverGroup.appendChild(coverRow);
539
+ form.appendChild(coverGroup);
540
+
541
+ // 字段 2:入口文件
542
+ var entryGroup = document.createElement('div');
543
+ entryGroup.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
544
+ var entryLabel = document.createElement('label');
545
+ entryLabel.textContent = '\u5165\u53E3\u6587\u4EF6';
546
+ entryLabel.style.cssText = 'font-size:12px;color:#888;';
547
+ var entrySelect = document.createElement('select');
548
+ entrySelect.style.cssText = 'padding:8px 10px;background:#252536;border:1px solid #3d3d5c;border-radius:4px;color:#cdd6f4;font-size:13px;font-family:monospace;outline:none;';
549
+ entrySelect.onfocus = function () { entrySelect.style.borderColor = '#6c6caa'; };
550
+ entrySelect.onblur = function () { entrySelect.style.borderColor = '#3d3d5c'; };
551
+ var entryDefaultOpt = document.createElement('option');
552
+ entryDefaultOpt.value = '';
553
+ entryDefaultOpt.textContent = '\u4E0D\u9009\u62E9';
554
+ entrySelect.appendChild(entryDefaultOpt);
555
+ var htmlAssets = (cachedData.assets || []).filter(function (a) { return a.type === 'html'; });
556
+ htmlAssets.forEach(function (asset) {
557
+ var opt = document.createElement('option');
558
+ opt.value = asset.path;
559
+ opt.textContent = asset.name;
560
+ entrySelect.appendChild(opt);
561
+ });
562
+ entryGroup.appendChild(entryLabel);
563
+ entryGroup.appendChild(entrySelect);
564
+ form.appendChild(entryGroup);
565
+
566
+ // 确认发布按钮
567
+ var submitBtn = document.createElement('button');
568
+ submitBtn.textContent = '\u786E\u8BA4\u53D1\u5E03';
569
+ submitBtn.style.cssText = 'margin:0 20px 20px;padding:10px;background:#4a4a7a;border:none;border-radius:4px;color:#cdd6f4;font-size:13px;font-family:monospace;cursor:pointer;transition:background 0.15s;';
570
+ submitBtn.onmouseenter = function () { submitBtn.style.background = '#5a5a9a'; };
571
+ submitBtn.onmouseleave = function () { submitBtn.style.background = '#4a4a7a'; };
572
+ submitBtn.onclick = function () {
573
+ var titleVal = titleInput.value.trim();
574
+ if (!titleVal) {
575
+ titleInput.style.borderColor = '#ff4444';
576
+ return;
577
+ }
578
+ var publishData = {
579
+ title: titleVal,
580
+ cover_path: coverSelect.value || '',
581
+ entry_path: entrySelect.value || '',
582
+ published_at: new Date().toISOString(),
583
+ };
584
+ console.log('[myclaw-artifacts-publish]', JSON.stringify(publishData));
585
+ closePublishModal();
586
+ };
587
+
588
+ box.appendChild(header);
589
+ box.appendChild(form);
590
+ box.appendChild(submitBtn);
591
+ overlay.appendChild(box);
592
+ document.body.appendChild(overlay);
593
+ }
594
+
595
+ function closePublishModal() {
596
+ var modal = document.querySelector('#myclaw-artifacts-publish-modal');
597
+ if (modal) modal.remove();
598
+ }
599
+
382
600
  // ═══ 注入样式 ═══
383
601
  function injectStyles() {
384
602
  if (document.querySelector('#myclaw-artifacts-styles')) return;
package/index.js CHANGED
@@ -396,6 +396,142 @@ function runPrepare() {
396
396
  });
397
397
  }
398
398
 
399
+ // ============================================================================
400
+ // TUI 唤起新对话上下文
401
+ // ============================================================================
402
+
403
+ // 预置的特殊 session ID 字典(按顺序使用)
404
+ const TUI_SESSION_SUFFIXES = [
405
+ 'helper', 'AAA', 'GO', '007', 'VIP', '666',
406
+ 'China', 'PRO', 'MAX', 'TOP', 'BEST',
407
+ 'GO', 'OK', 'YES', 'WIN', 'FUN'
408
+ ];
409
+
410
+ // Session 索引存储文件
411
+ const SESSION_INDEX_FILE = path.join(os.homedir(), '.openclaw', 'myclaw-session-index.json');
412
+
413
+ /**
414
+ * 获取 agent 的下一个 session 序号索引
415
+ */
416
+ function getNextSessionIndex(agentName) {
417
+ const fs = require('fs');
418
+ let indexData = {};
419
+
420
+ // 读取现有索引
421
+ if (fs.existsSync(SESSION_INDEX_FILE)) {
422
+ try {
423
+ const raw = fs.readFileSync(SESSION_INDEX_FILE, 'utf8');
424
+ indexData = JSON.parse(raw);
425
+ } catch (err) {
426
+ console.warn('[' + colors.yellow + '警告' + colors.nc + '] 读取 session 索引失败,将重置: ' + err.message);
427
+ }
428
+ }
429
+
430
+ // 获取当前 agent 的索引,不存在则从 0 开始
431
+ const currentIndex = indexData[agentName] || 0;
432
+
433
+ // 计算下一个索引(循环使用)
434
+ const nextIndex = currentIndex % TUI_SESSION_SUFFIXES.length;
435
+
436
+ // 更新索引数据
437
+ indexData[agentName] = currentIndex + 1;
438
+
439
+ // 确保目录存在
440
+ const dir = path.dirname(SESSION_INDEX_FILE);
441
+ if (!fs.existsSync(dir)) {
442
+ fs.mkdirSync(dir, { recursive: true });
443
+ }
444
+
445
+ // 写回文件
446
+ try {
447
+ fs.writeFileSync(SESSION_INDEX_FILE, JSON.stringify(indexData, null, 2) + '\n', 'utf8');
448
+ } catch (err) {
449
+ console.warn('[' + colors.yellow + '警告' + colors.nc + '] 保存 session 索引失败: ' + err.message);
450
+ }
451
+
452
+ return nextIndex;
453
+ }
454
+
455
+ /**
456
+ * 获取 agent 的下一个 session 后缀
457
+ */
458
+ function getNextSessionSuffix(agentName) {
459
+ const index = getNextSessionIndex(agentName);
460
+ return TUI_SESSION_SUFFIXES[index];
461
+ }
462
+
463
+ function runTui(interactive = false) {
464
+ const { spawn } = require('child_process');
465
+
466
+ // 获取 agent 名称
467
+ let agentName;
468
+ if (interactive) {
469
+ // 交互模式:需要询问用户
470
+ return askAgentNameForTui();
471
+ } else {
472
+ // 命令行模式:从参数获取,默认 'main'
473
+ agentName = args[1] || 'main';
474
+ doTuiCommand(agentName);
475
+ }
476
+ }
477
+
478
+ // 交互式询问 Agent 名称
479
+ function askAgentNameForTui() {
480
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
481
+ rl.question(colors.cyan + '请输入 Agent 名称: ' + colors.nc, function (answer) {
482
+ rl.close();
483
+ const agentName = (answer || '').trim();
484
+ if (!agentName) {
485
+ console.log(colors.red + 'Agent 名称不能为空。' + colors.nc);
486
+ console.log('');
487
+ askAgentNameForTui();
488
+ return;
489
+ }
490
+ doTuiCommand(agentName);
491
+ });
492
+ }
493
+
494
+ // 执行 TUI 命令
495
+ function doTuiCommand(agentName) {
496
+ const { spawn } = require('child_process');
497
+
498
+ // 获取当前 agent 的下一个 session 后缀
499
+ const sessionSuffix = getNextSessionSuffix(agentName);
500
+ const sessionKey = 'agent:' + agentName + ':' + sessionSuffix;
501
+
502
+ // 默认消息
503
+ const messageParam = '你好';
504
+
505
+ console.log('[' + colors.blue + 'TUI' + colors.nc + '] 唤起新对话上下文');
506
+ console.log(' Agent: ' + colors.green + agentName + colors.nc);
507
+ console.log(' Session: ' + colors.cyan + sessionKey + colors.nc);
508
+ console.log(' Message: ' + colors.dim + messageParam + colors.nc);
509
+ console.log('');
510
+
511
+ // 发送消息给 agent
512
+ const openclawArgs = ['tui', '--session', sessionKey, '--message', messageParam];
513
+
514
+ // 打印命令时给消息加上引号(仅用于显示)
515
+ const displayArgs = ['tui', '--session', sessionKey, '--message', '"' + messageParam + '"'];
516
+ console.log('[' + colors.dim + '执行命令: openclaw ' + displayArgs.join(' ') + colors.nc + ']');
517
+ console.log('');
518
+
519
+ // 启动进程,保存引用以便稍后杀掉
520
+ const child = spawn('openclaw', openclawArgs, {
521
+ stdio: 'ignore',
522
+ shell: true
523
+ });
524
+
525
+ console.log('[' + colors.blue + 'TUI' + colors.nc + '] 消息已发送,等待对话生成...');
526
+
527
+ // 等待 10 秒后杀掉进程
528
+ setTimeout(() => {
529
+ child.kill();
530
+ console.log('[' + colors.green + '完成' + colors.nc + '] 对话上下文已唤起');
531
+ process.exit(0);
532
+ }, 10000);
533
+ }
534
+
399
535
  // ============================================================================
400
536
  // 创建 Windows 桌面快捷启动脚本
401
537
  // ============================================================================
@@ -411,7 +547,7 @@ function runBat() {
411
547
  }
412
548
 
413
549
  const myClawDir = path.join(process.env.LOCALAPPDATA || os.tmpdir(), 'myclaw');
414
- try { fs.mkdirSync(myClawDir, { recursive: true }); } catch {}
550
+ try { fs.mkdirSync(myClawDir, { recursive: true }); } catch { }
415
551
 
416
552
  const batPath = path.join(myClawDir, 'openclaw-launcher.bat');
417
553
  const desktopPath = path.join(os.homedir(), 'Desktop');
@@ -429,9 +565,9 @@ function runBat() {
429
565
  try {
430
566
  const oldLinks = fs.readdirSync(desktopPath).filter(f => f.endsWith('.lnk') && (f.includes('OpenClaw') || f.match(/^\d+\.\d+\.\d+_OpenClaw/)));
431
567
  for (const old of oldLinks) {
432
- try { fs.unlinkSync(path.join(desktopPath, old)); } catch {}
568
+ try { fs.unlinkSync(path.join(desktopPath, old)); } catch { }
433
569
  }
434
- } catch {}
570
+ } catch { }
435
571
 
436
572
  const lnkPath = path.join(desktopPath, ver + '_OpenClaw_' + mm + '-' + dd + '_' + hh + '-' + mi + '-' + ss + '.lnk');
437
573
 
@@ -461,7 +597,7 @@ pause >nul
461
597
  // 用 PowerShell 下载图标并创建带图标的桌面快捷方式 + 刷新桌面
462
598
  const iconPath = path.join(myClawDir, 'openclaw.ico');
463
599
  const iconUrl = 'https://cdn.yiranlaoshi.com/software/myclaw/openclaw.ico';
464
-
600
+
465
601
  const psFile = path.join(myClawDir, 'create-shortcuts.ps1');
466
602
  const psContent = [
467
603
  '$ErrorActionPreference = \'Continue\'',
@@ -580,7 +716,7 @@ function runOpen() {
580
716
  try {
581
717
  const releaseInfo = fs.readFileSync('/proc/version', 'utf8');
582
718
  if (/microsoft|wsl/i.test(releaseInfo)) isWSL = true;
583
- } catch {}
719
+ } catch { }
584
720
 
585
721
  if (isWSL) {
586
722
  // WSL2 环境下优先使用 Windows 端的 Chrome
@@ -612,7 +748,7 @@ function runOpen() {
612
748
  chromePath = p;
613
749
  break;
614
750
  }
615
- } catch {}
751
+ } catch { }
616
752
  }
617
753
 
618
754
  // 如果没有找到 Chrome
@@ -641,7 +777,7 @@ function runOpen() {
641
777
  }
642
778
  }
643
779
  }
644
- } catch {}
780
+ } catch { }
645
781
 
646
782
  console.log('安装 Chrome 后重新运行: ' + colors.yellow + 'myclaw open' + colors.nc);
647
783
  console.log('');
@@ -727,7 +863,7 @@ function runPatch() {
727
863
  current[parts[parts.length - 1]] = value;
728
864
  }
729
865
  const { config, configPath } = patchConfig(nested);
730
-
866
+
731
867
  // 【关键修复】深度合并不会删除错误的历史残留配置,这里主动帮学生清理掉遗留在顶层的 "exec"
732
868
  let requireSave = false;
733
869
  if (config && config.exec) {
@@ -735,7 +871,7 @@ function runPatch() {
735
871
  requireSave = true;
736
872
  console.log('[myclaw-config] ✅ 已清理历史遗留的错误顶层 "exec" 常驻配置');
737
873
  }
738
-
874
+
739
875
  if (requireSave) {
740
876
  const { writeConfig } = require('./find-config');
741
877
  writeConfig(config, configPath);
@@ -893,7 +1029,7 @@ function runUpdate() {
893
1029
  let currentVersion = 'unknown';
894
1030
  try {
895
1031
  currentVersion = require(path.join(__dirname, 'package.json')).version;
896
- } catch {}
1032
+ } catch { }
897
1033
 
898
1034
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
899
1035
  try {
@@ -942,7 +1078,7 @@ function runUpdate() {
942
1078
  console.log('');
943
1079
  console.log('[' + colors.yellow + '重试' + colors.nc + '] 8 秒后重试 (' + attempt + '/' + maxRetries + ')...');
944
1080
  const waitCmd = require('os').platform() === 'win32' ? 'timeout /t 8 >nul' : 'sleep 8';
945
- try { execSync(waitCmd, { stdio: 'ignore' }); } catch {}
1081
+ try { execSync(waitCmd, { stdio: 'ignore' }); } catch { }
946
1082
  } else {
947
1083
  console.log('');
948
1084
  console.log('[' + colors.red + '错误' + colors.nc + '] 升级失败: ' + err.message);
@@ -1037,31 +1173,38 @@ function padRight(str, len) {
1037
1173
  // ============================================================================
1038
1174
 
1039
1175
  const MENU_ITEMS = [
1040
- { key: 'start', label: '🦞启动🦞', cmd: 'mc start', desc: '把你的 AI 助手叫醒,让它开始工作', action: () => { const start = require('./start'); start.run(); } },
1041
- { key: 'restart', label: '重启', cmd: 'mc restart', desc: 'AI 助手卡住了?让它重新启动一下', action: runRestart },
1042
- { key: 'new', label: '😊新伙伴', cmd: 'mc new', desc: '创建一个新的 AI 助手,给它取个名字', action: runNew },
1043
- { key: 'status', label: '网址', cmd: 'mc status', desc: '获取控制台链接,复制到浏览器打开', action: runStatus },
1044
- { key: 'update', label: '升级', cmd: 'mc up', desc: '让 MyClaw 工具升级到最新版本', action: () => {
1045
- runUpdate();
1046
- console.log('');
1047
- console.log('🔧 自动执行 patch 以恢复自定义配置...');
1048
- runPatch();
1049
- if (detectPlatform() === 'windows') runBat();
1050
- }},
1051
- { key: 'patch', label: '修复', cmd: 'mc patch', desc: '给 AI 助手装上新技能和好看的外衣', action: runPatch },
1052
- { key: 'reinstall', label: '重装', cmd: 'mc longxia', desc: '出了大问题?把 AI 助手删了重新安装', action: runReinstall },
1053
- { key: 'uninstall', label: '卸载', cmd: 'mc uninstall', desc: '卸载 MyClaw,恢复 npm 源地址', action: runUninstall },
1054
- { key: 'wsl2reinstall', label: 'WSL2虚拟机重装', cmd: 'mc wsl2 --remote --force-phase1', desc: '强制从网络重新下载并重装 WSL2 虚拟机(仅限 Windows)', action: () => {
1055
- process.argv.push('--remote', '--force-phase1');
1056
- const wsl2 = require('./wsl2');
1057
- wsl2.run();
1058
- }},
1059
- { key: 'safe', label: '虚拟机屏蔽', cmd: 'mc safe', desc: '禁止 WSL2 访问 Windows 盘符(仅限 Windows)', action: () => {
1060
- const restrict = require('./restrict');
1061
- restrict.run();
1062
- }},
1176
+ { key: 'start', label: '🦞启动🦞', cmd: 'mc start', desc: '把你的 AI 助手叫醒,让它开始工作', action: () => { const start = require('./start'); start.run(); } },
1177
+ { key: 'restart', label: '重启', cmd: 'mc restart', desc: 'AI 助手卡住了?让它重新启动一下', action: runRestart },
1178
+ { key: 'new', label: '😊新伙伴', cmd: 'mc new', desc: '创建一个新的 AI 助手,给它取个名字', action: runNew },
1179
+ { key: 'tui', label: '新对话', cmd: 'mc tui', desc: '唤起新对话上下文', action: () => runTui(true) },
1180
+ { key: 'status', label: '网址', cmd: 'mc status', desc: '获取控制台链接,复制到浏览器打开', action: runStatus },
1181
+ {
1182
+ key: 'update', label: '升级', cmd: 'mc up', desc: '让 MyClaw 工具升级到最新版本', action: () => {
1183
+ runUpdate();
1184
+ console.log('');
1185
+ console.log('🔧 自动执行 patch 以恢复自定义配置...');
1186
+ runPatch();
1187
+ if (detectPlatform() === 'windows') runBat();
1188
+ }
1189
+ },
1190
+ { key: 'patch', label: '修复', cmd: 'mc patch', desc: ' AI 助手装上新技能和好看的外衣', action: runPatch },
1191
+ { key: 'reinstall', label: '重装', cmd: 'mc longxia', desc: '出了大问题?把 AI 助手删了重新安装', action: runReinstall },
1192
+ { key: 'uninstall', label: '卸载', cmd: 'mc uninstall', desc: '卸载 MyClaw,恢复 npm 源地址', action: runUninstall },
1193
+ {
1194
+ key: 'wsl2reinstall', label: 'WSL2虚拟机重装', cmd: 'mc wsl2 --remote --force-phase1', desc: '强制从网络重新下载并重装 WSL2 虚拟机(仅限 Windows)', action: () => {
1195
+ process.argv.push('--remote', '--force-phase1');
1196
+ const wsl2 = require('./wsl2');
1197
+ wsl2.run();
1198
+ }
1199
+ },
1200
+ {
1201
+ key: 'safe', label: '虚拟机屏蔽', cmd: 'mc safe', desc: '禁止 WSL2 访问 Windows 盘符(仅限 Windows)', action: () => {
1202
+ const restrict = require('./restrict');
1203
+ restrict.run();
1204
+ }
1205
+ },
1063
1206
  { key: 'bat', label: '桌面快捷方式', cmd: 'mc bat', desc: '在桌面生成一键启动脚本(仅限 Windows)', action: runBat },
1064
- { key: 'quit', label: '退出', cmd: 'Ctrl+C', desc: '不玩了,下次见', action: () => { console.log(' 👋 再见!\n'); process.exit(0); } },
1207
+ { key: 'quit', label: '退出', cmd: 'Ctrl+C', desc: '不玩了,下次见', action: () => { console.log(' 👋 再见!\n'); process.exit(0); } },
1065
1208
  ];
1066
1209
 
1067
1210
  function runInteractiveMenu() {
@@ -1124,6 +1267,13 @@ function runInteractiveMenu() {
1124
1267
  return;
1125
1268
  }
1126
1269
 
1270
+ // tui 命令需要交互式输入,不走菜单的"按回车返回"逻辑
1271
+ if (item.key === 'tui') {
1272
+ console.log('');
1273
+ item.action();
1274
+ return;
1275
+ }
1276
+
1127
1277
  console.log('');
1128
1278
  item.action();
1129
1279
 
@@ -1171,25 +1321,25 @@ function runInteractiveMenu() {
1171
1321
  // ============================================================================
1172
1322
 
1173
1323
  const INJECT_MENU = [
1174
- { key: '1', cmd: 'inject-minimax', desc: '注入 MiniMax 模型配置' },
1175
- { key: '2', cmd: 'inject-zai', desc: '注入智谱 GLM 模型配置' },
1176
- { key: '3', cmd: 'inject-image', desc: '注入图像生成模型配置 (vveai)' },
1177
- { key: '4', cmd: 'inject-search', desc: '注入 Tavily 搜索插件配置' },
1178
- { key: '5', cmd: 'inject-token', desc: '设置 Gateway Token 为 aiyiran' },
1324
+ { key: '1', cmd: 'inject-minimax', desc: '注入 MiniMax 模型配置' },
1325
+ { key: '2', cmd: 'inject-zai', desc: '注入智谱 GLM 模型配置' },
1326
+ { key: '3', cmd: 'inject-image', desc: '注入图像生成模型配置 (vveai)' },
1327
+ { key: '4', cmd: 'inject-search', desc: '注入 Tavily 搜索插件配置' },
1328
+ { key: '5', cmd: 'inject-token', desc: '设置 Gateway Token 为 aiyiran' },
1179
1329
  { key: '6', cmd: 'inject-workspaceAndSoul', desc: '替换默认 workspace 的 SOUL.md' },
1180
- { key: '7', cmd: 'inject-tooldeny', desc: 'deny image_generate + music_generate 内置工具' },
1181
- { key: 'a', cmd: 'all', desc: '执行以上全部注入' },
1330
+ { key: '7', cmd: 'inject-tooldeny', desc: 'deny image_generate + music_generate 内置工具' },
1331
+ { key: 'a', cmd: 'all', desc: '执行以上全部注入' },
1182
1332
  ];
1183
1333
 
1184
1334
  function runInjectCommand(cmd, extraArgs) {
1185
1335
  const modules = {
1186
- 'inject-minimax': './inject-minimax',
1187
- 'inject-zai': './inject-zai',
1188
- 'inject-image': './inject-image',
1189
- 'inject-search': './inject-search',
1190
- 'inject-token': './inject-token',
1336
+ 'inject-minimax': './inject-minimax',
1337
+ 'inject-zai': './inject-zai',
1338
+ 'inject-image': './inject-image',
1339
+ 'inject-search': './inject-search',
1340
+ 'inject-token': './inject-token',
1191
1341
  'inject-workspaceAndSoul': './inject-workspaceAndSoul',
1192
- 'inject-tooldeny': './inject-tooldeny',
1342
+ 'inject-tooldeny': './inject-tooldeny',
1193
1343
  };
1194
1344
  const mod = require(modules[cmd]);
1195
1345
  mod.run(extraArgs || []);
@@ -1267,6 +1417,7 @@ function showHelp() {
1267
1417
  console.log(' update 自动升级 MyClaw 到最新版本');
1268
1418
  console.log(' up 升级 + 刷新桌面快捷方式 (= update + bat)');
1269
1419
  console.log(' open 打开浏览器控制台(自动带 token)');
1420
+ console.log(' tui 唤起新对话上下文 (用法: mc tui <agentname>)');
1270
1421
  console.log(' fix 兜底修复(自动补装 WSL + Chrome,仅限 Windows)');
1271
1422
  console.log(' wsl2 WSL2 一键安装/修复 (仅限 Windows, 可选: --cli, --remote)');
1272
1423
  console.log(' safe 开启虚拟机屏蔽 (禁止自动挂载 Windows 盘符)');
@@ -1343,6 +1494,8 @@ if (!command) {
1343
1494
  runStart();
1344
1495
  } else if (command === 'open') {
1345
1496
  runOpen();
1497
+ } else if (command === 'tui') {
1498
+ runTui();
1346
1499
  } else if (command === 'fix') {
1347
1500
  runFix();
1348
1501
  } else if (command === 'wsl2') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.209",
3
+ "version": "1.0.210",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -2,16 +2,16 @@
2
2
  "output_dir": "media",
3
3
  "image": {
4
4
  "primary": {
5
- "provider": "minimax_image",
6
- "model": "image-01",
7
- "base_url": "https://api.minimaxi.com/v1",
8
- "api_key": "sk-cp-DC5lWd2Stt9CBFzLIT2awP4K-ZEn5AkYwjl3Cdj-mIBmgjxod518F2LaVF2L9c35Wv5-Eox0F1ctJD5vXtB9p3OmxoWLd9ge9zIUIMrCVuqBYdL_s6kb8Qs"
9
- },
10
- "fallback": {
11
5
  "provider": "vapi_image",
12
6
  "model": "nano-banana-pro",
13
7
  "base_url": "https://api.v3.cm/v1",
14
8
  "api_key": "sk-PXPUzqllWKJy2oj011Df510242264219Ba21093e3d2b2335"
9
+ },
10
+ "fallback": {
11
+ "provider": "minimax_image",
12
+ "model": "image-01",
13
+ "base_url": "https://api.minimaxi.com/v1",
14
+ "api_key": "sk-cp-DC5lWd2Stt9CBFzLIT2awP4K-ZEn5AkYwjl3Cdj-mIBmgjxod518F2LaVF2L9c35Wv5-Eox0F1ctJD5vXtB9p3OmxoWLd9ge9zIUIMrCVuqBYdL_s6kb8Qs"
15
15
  }
16
16
  },
17
17
  "music": {
@@ -44,10 +44,57 @@ def get_output_dir():
44
44
  return fallback
45
45
 
46
46
 
47
- def make_output_path(out_dir, resource_type, ext):
48
- """Generate timestamped filename."""
47
+ def ratio_to_size(ratio_str, max_dim=2048):
48
+ """Parse '16:9' style ratio and compute (width, height) capped at max_dim."""
49
+ if not ratio_str or ratio_str == "1:1":
50
+ return 1024, 1024
51
+
52
+ parts = ratio_str.split(":")
53
+ if len(parts) != 2:
54
+ return 1024, 1024
55
+
56
+ try:
57
+ rw, rh = float(parts[0]), float(parts[1])
58
+ except ValueError:
59
+ return 1024, 1024
60
+
61
+ # Scale so the longer side = max_dim
62
+ scale = max_dim / max(rw, rh)
63
+ w = round(rw * scale)
64
+ h = round(rh * scale)
65
+ return w, h
66
+
67
+
68
+ def make_output_path(out_dir, resource_type, model, params, ext):
69
+ """Generate filename with model name and key parameters.
70
+
71
+ Format: {type}_{model}_{params}_{timestamp}.{ext}
72
+ Example: image_nano-banana-pro_2048x1152_20260414_123456.png
73
+ """
49
74
  ts = datetime.now().strftime("%Y%m%d_%H%M%S")
50
- return os.path.join(out_dir, f"{resource_type}_{ts}.{ext}")
75
+
76
+ # Extract key parameters for filename
77
+ param_parts = []
78
+ if resource_type == "image":
79
+ aspect_ratio = params.get("aspect_ratio", "1:1")
80
+ # Convert ratio to actual dimensions
81
+ w, h = ratio_to_size(aspect_ratio)
82
+ param_parts.append(f"{w}x{h}")
83
+ elif resource_type == "music":
84
+ if params.get("instrumental"):
85
+ param_parts.append("instrumental")
86
+
87
+ # Build filename parts
88
+ # Clean model name: replace special chars with dash
89
+ clean_model = model.replace("/", "-").replace("_", "-")
90
+
91
+ parts = [resource_type, clean_model]
92
+ if param_parts:
93
+ parts.extend(param_parts)
94
+ parts.append(ts)
95
+
96
+ filename = "_".join(parts) + f".{ext}"
97
+ return os.path.join(out_dir, filename)
51
98
 
52
99
 
53
100
  def append_log(log_path, entry):
@@ -114,27 +161,46 @@ def main():
114
161
 
115
162
  out_dir = get_output_dir()
116
163
 
164
+ # Prepare kwargs
117
165
  if args.type == "image":
118
- output_path = args.output or make_output_path(out_dir, "image", "png")
119
166
  kwargs = {
120
167
  "out_dir": out_dir,
121
- "output_path": output_path,
122
168
  "aspect_ratio": args.aspect_ratio,
123
169
  }
170
+ ext = "png"
124
171
  else:
125
- output_path = args.output or make_output_path(out_dir, "music", "mp3")
126
172
  kwargs = {
127
173
  "out_dir": out_dir,
128
- "output_path": output_path,
129
174
  "lyrics": args.lyrics,
130
175
  "instrumental": args.instrumental,
131
176
  }
177
+ ext = "mp3"
132
178
 
133
179
  start_time = time.time()
134
180
  start_dt = datetime.now()
135
181
 
136
182
  try:
183
+ # Handle output path: user-specified or auto-named with model info
184
+ if args.output:
185
+ # User specified output path - use it directly
186
+ kwargs["output_path"] = args.output
187
+ else:
188
+ # Auto-naming: generate to temp path first, then rename with model info
189
+ import tempfile
190
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f".{ext}", dir=out_dir) as tmp:
191
+ tmp_path = tmp.name
192
+ kwargs["output_path"] = tmp_path
193
+
137
194
  files, used_provider = dispatch(args.type, args.prompt, **kwargs)
195
+
196
+ # Rename with proper filename containing model and params (only for auto-naming)
197
+ if not args.output:
198
+ model = used_provider["model"]
199
+ proper_path = make_output_path(out_dir, args.type, model, kwargs, ext)
200
+ import shutil
201
+ shutil.move(files[0], proper_path)
202
+ files = [proper_path]
203
+
138
204
  duration = round(time.time() - start_time, 2)
139
205
  end_dt = datetime.now()
140
206
 
@@ -16,5 +16,41 @@
16
16
  "started_at": "2026-04-13 14:11:12",
17
17
  "finished_at": "2026-04-13 14:11:36",
18
18
  "duration_seconds": 23.5
19
+ },
20
+ {
21
+ "id": "20260414_005846",
22
+ "type": "image",
23
+ "name": "test_output.png",
24
+ "files": [
25
+ "/tmp/test_output.png"
26
+ ],
27
+ "prompt": "a cute cat",
28
+ "params": {
29
+ "aspect_ratio": "1:1",
30
+ "output_path": "/tmp/test_output.png"
31
+ },
32
+ "provider": "vapi_image",
33
+ "model": "nano-banana-pro",
34
+ "started_at": "2026-04-14 00:58:46",
35
+ "finished_at": "2026-04-14 00:59:08",
36
+ "duration_seconds": 21.9
37
+ },
38
+ {
39
+ "id": "20260414_005910",
40
+ "type": "image",
41
+ "name": "image_nano-banana-pro_2048x1152_20260414_005930.png",
42
+ "files": [
43
+ "/Users/yiran/.openclaw/workspace/media/image_nano-banana-pro_2048x1152_20260414_005930.png"
44
+ ],
45
+ "prompt": "a beautiful sunset",
46
+ "params": {
47
+ "aspect_ratio": "16:9",
48
+ "output_path": "/Users/yiran/.openclaw/workspace/media/tmpdy8csqoa.png"
49
+ },
50
+ "provider": "vapi_image",
51
+ "model": "nano-banana-pro",
52
+ "started_at": "2026-04-14 00:59:10",
53
+ "finished_at": "2026-04-14 00:59:30",
54
+ "duration_seconds": 20.44
19
55
  }
20
56
  ]