@aiyiran/myclaw 1.0.212 → 1.0.214

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.
@@ -49,8 +49,9 @@
49
49
 
50
50
  // ═══ 构建预览 URL ═══
51
51
  function buildPreviewUrl(data, assetPath) {
52
- var wsName = data.workspace_id || 'main';
53
- return window.location.origin + '/' + wsName + '/' + assetPath;
52
+ var base = data.base_url || '';
53
+ var wsName = data.workspace_id || '';
54
+ return base + '/' + wsName + '/' + assetPath;
54
55
  }
55
56
 
56
57
  // ═══ 创建按钮 ═══
@@ -245,10 +246,8 @@
245
246
  'flex-shrink: 0',
246
247
  ].join(';');
247
248
  tableHeader.innerHTML = [
248
- '<span style="width:30px;text-align:center;">#</span>',
249
- '<span style="flex:2;">文件名</span>',
250
- '<span style="flex:1;text-align:center;">类别</span>',
251
- '<span style="flex:1;text-align:right;">更新时间</span>',
249
+ '<span style="flex-shrink:0;width:70px;">更新时间</span>',
250
+ '<span style="flex:1;">文件名</span>',
252
251
  ].join('');
253
252
  container.appendChild(tableHeader);
254
253
 
@@ -286,17 +285,29 @@
286
285
  row.onclick = function () {
287
286
  if (isLatest && !isLatestSeen) localStorage.setItem(latestSeenKey, '1');
288
287
  if (isUpdated && !isUpdateSeen) localStorage.setItem(updateSeenKey, '1');
289
- openPreviewModal(data, asset);
288
+ if (asset.type === 'html') {
289
+ window.open(buildPreviewUrl(data, asset.path), '_blank');
290
+ } else {
291
+ openPreviewModal(data, asset);
292
+ }
290
293
  };
291
294
 
292
- // 编号
293
- var num = document.createElement('span');
294
- num.style.cssText = 'width:30px;text-align:center;color:#888;flex-shrink:0;';
295
- num.textContent = String(idx + 1);
295
+ // 更新时间
296
+ var time = document.createElement('span');
297
+ time.style.cssText = 'flex-shrink:0;width:70px;color:#888;font-size:11px;';
298
+ var updatedAt = asset.updated_at || data.updated_at || '';
299
+ if (updatedAt) {
300
+ try {
301
+ var d = new Date(updatedAt);
302
+ time.textContent = (d.getMonth() + 1) + '/' + d.getDate() + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
303
+ } catch (e) {
304
+ time.textContent = '';
305
+ }
306
+ }
296
307
 
297
308
  // 文件名(从 path 提取)+ 标记
298
309
  var fname = document.createElement('span');
299
- fname.style.cssText = 'flex:2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:6px;';
310
+ fname.style.cssText = 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:6px;';
300
311
  var pathParts = (asset.path || '').split('/');
301
312
  var nameSpan = document.createElement('span');
302
313
  nameSpan.textContent = pathParts[pathParts.length - 1] || asset.name || '未命名';
@@ -316,28 +327,8 @@
316
327
  fname.appendChild(badge);
317
328
  }
318
329
 
319
- // 类别
320
- var type = document.createElement('span');
321
- type.style.cssText = 'flex:1;text-align:center;color:#888;';
322
- type.textContent = asset.type ? asset.type.toUpperCase() : '';
323
-
324
- // 更新时间
325
- var time = document.createElement('span');
326
- time.style.cssText = 'flex:1;text-align:right;color:#888;font-size:11px;flex-shrink:0;';
327
- var updatedAt = asset.updated_at || data.updated_at || '';
328
- if (updatedAt) {
329
- try {
330
- var d = new Date(updatedAt);
331
- time.textContent = (d.getMonth() + 1) + '/' + d.getDate() + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
332
- } catch (e) {
333
- time.textContent = '';
334
- }
335
- }
336
-
337
- row.appendChild(num);
338
- row.appendChild(fname);
339
- row.appendChild(type);
340
330
  row.appendChild(time);
331
+ row.appendChild(fname);
341
332
  container.appendChild(row);
342
333
  });
343
334
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.212",
3
+ "version": "1.0.214",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
package/patches/patch.js CHANGED
@@ -212,8 +212,9 @@ function patch() {
212
212
  return { success: false, reason: 'inject-failed' };
213
213
  }
214
214
 
215
- // 7. Patch Permissions-Policy 头(允许麦克风)
215
+ // 7. Patch Permissions-Policy 头(允许麦克风)+ CSP frame-src(允许 iframe 加载外部资源)
216
216
  // OpenClaw 在 gateway-cli-*.js 或 server-*.js 中硬编码了 microphone=(),需要改为 microphone=(self)
217
+ // 同时 CSP 中 default-src 'self' 导致 iframe 只能加载同源内容,需要添加 frame-src 允许外部资源
217
218
  try {
218
219
  const distParent = path.resolve(uiDir, '..'); // dist/
219
220
  const distFiles = fs.readdirSync(distParent);
@@ -229,33 +230,58 @@ function patch() {
229
230
  const filePath = path.join(distParent, f);
230
231
  let content = fs.readFileSync(filePath, 'utf8');
231
232
 
232
- if (content.includes('microphone=()')) {
233
+ // 检查是否需要 patch
234
+ const needsMicrophonePatch = content.includes('microphone=()');
235
+ const needsCspFramePatch = !content.includes('frame-src');
236
+
237
+ if (needsMicrophonePatch || needsCspFramePatch) {
233
238
  // 备份
234
239
  const backupFile = filePath + BACKUP_SUFFIX;
235
240
  if (!fs.existsSync(backupFile)) {
236
241
  fs.copyFileSync(filePath, backupFile);
237
242
  }
238
243
 
239
- content = content.replace(
240
- 'microphone=()',
241
- 'microphone=(self)'
242
- );
244
+ // Patch 1: microphone
245
+ if (needsMicrophonePatch) {
246
+ content = content.replace(
247
+ 'microphone=()',
248
+ 'microphone=(self)'
249
+ );
250
+ console.log('[myclaw-patch] ✅ 已修复 Permissions-Policy (microphone): ' + f);
251
+ }
252
+
253
+ // Patch 2: CSP frame-src - 在 buildControlUiCspHeader 的 CSP 数组中添加 frame-src
254
+ // 原始: "default-src 'self'", ... "frame-ancestors 'none'"
255
+ // 目标: 在 "default-src 'self'" 后插入 "frame-src 'self' https:", 并修改 frame-ancestors
256
+ if (needsCspFramePatch) {
257
+ // 在 "default-src 'self'" 后面插入 frame-src 指令
258
+ content = content.replace(
259
+ '"default-src \'self\'"',
260
+ '"default-src \'self\'",\n\t\t"frame-src \'self\' https:"'
261
+ );
262
+ // 同时放开 frame-ancestors,允许被外部 iframe 嵌入
263
+ content = content.replace(
264
+ '"frame-ancestors \'none\'"',
265
+ '"frame-ancestors \'self\' https:"'
266
+ );
267
+ console.log('[myclaw-patch] ✅ 已添加 CSP frame-src 允许 iframe 加载: ' + f);
268
+ }
269
+
243
270
  fs.writeFileSync(filePath, content, 'utf8');
244
- console.log('[myclaw-patch] ✅ 已修复 Permissions-Policy (microphone): ' + f);
245
271
  patched = true;
246
- } else if (content.includes('microphone=(self)')) {
247
- console.log('[myclaw-patch] ✅ Permissions-Policy (microphone) 已是允许状态');
272
+ } else {
273
+ console.log('[myclaw-patch] ✅ Permissions-Policy (microphone) 和 CSP (frame-src) 已是允许状态');
248
274
  patched = true;
249
275
  }
250
276
  }
251
277
 
252
278
  if (!patched && foundTarget) {
253
- console.log('[myclaw-patch] ⚠ 找到目标文件但未检测到 microphone 配置');
279
+ console.log('[myclaw-patch] ⚠ 找到目标文件但未检测到 microphone/CSP 配置');
254
280
  } else if (!patched) {
255
- console.log('[myclaw-patch] ⚠ 未找到 Permissions-Policy 配置文件,麦克风可能受限');
281
+ console.log('[myclaw-patch] ⚠ 未找到配置文件,麦克风和 iframe 可能受限');
256
282
  }
257
283
  } catch (err) {
258
- console.error('[myclaw-patch] ⚠ Permissions-Policy 修复失败 (非致命): ' + err.message);
284
+ console.error('[myclaw-patch] ⚠ Permissions-Policy/CSP 修复失败 (非致命): ' + err.message);
259
285
  }
260
286
 
261
287
  console.log('[myclaw-patch] ✅ 注入完成,重启 Gateway 后生效');
@@ -1,30 +1,29 @@
1
1
  ---
2
2
  name: yiran-skill-media
3
- description: 统一多媒体生成技能。支持图片和音乐生成,按资源类型自动路由到最优 provider,支持主备切换。资源生成规范:所有生成的资源必须存放在当前工作目录下,调用时通过 --output-dir 传入当前工作目录的绝对路径。
3
+ description: 统一多媒体生成技能。支持图片和音乐生成,按资源类型自动路由到最优 provider,支持主备切换。资源生成规范:所有生成的资源必须存放在当前工作目录下,调用时通过 --output-dir 传入当前工作目录的绝对路径,通过 --name 传入资源的中文名称。
4
4
  ---
5
5
 
6
6
  # 统一多媒体生成
7
7
 
8
8
  ## 资源生成规范(必须遵守)
9
9
 
10
- **`--output-dir` 为必填参数,必须传入绝对路径。**
10
+ 1. **`--output-dir` 必填** — 传入当前工作目录的绝对路径。你在哪个目录工作,就传哪个路径。
11
+ 2. **`--name` 必填** — 传入资源的中文名称,用于生成文件名。名称应简洁准确,能体现资源内容。
11
12
 
12
- **规范:所有生成的资源文件必须存放在当前工作目录下。你在哪个目录工作,就把该目录的绝对路径传给 `--output-dir`。**
13
+ **所有生成的资源文件必须存放在当前工作目录下。** 不允许省略,不允许猜测路径。
13
14
 
14
15
  例如:
15
16
  - 你当前在 `/root/.openclaw/workspace` → 传入 `--output-dir /root/.openclaw/workspace`
16
- - 你当前在 `/root/.openclaw/workspace-dev` → 传入 `--output-dir /root/.openclaw/workspace-dev`
17
-
18
- 这是硬性规范,不允许省略,不允许猜测路径。
17
+ - 生成一张日落图片 → 传入 `--name 日落风景`
19
18
 
20
19
  ## 一键脚本
21
20
 
22
21
  ```bash
23
- # 图片生成 — 传入当前工作目录的绝对路径
24
- ./image.sh --output-dir "$(pwd)" "描述" [--aspect-ratio 16:9]
22
+ # 图片生成
23
+ ./image.sh --output-dir "$(pwd)" --name 日落风景 "a sunset landscape" [--aspect-ratio 16:9]
25
24
 
26
25
  # 音乐生成
27
- ./music.sh --output-dir "$(pwd)" "描述" [--lyrics "歌词"] [--instrumental]
26
+ ./music.sh --output-dir "$(pwd)" --name 开场音乐 "epic opening" [--instrumental]
28
27
  ```
29
28
 
30
29
  ## 参数说明
@@ -34,6 +33,7 @@ description: 统一多媒体生成技能。支持图片和音乐生成,按资
34
33
  | 参数 | 必填 | 说明 |
35
34
  |------|------|------|
36
35
  | `--output-dir` | 是 | 输出目录的绝对路径,传入当前工作目录 |
36
+ | `--name` | 是 | 资源中文名称(如:日落风景、产品封面) |
37
37
  | `prompt` | 是 | 图片描述 |
38
38
  | `--aspect-ratio` | 否 | 比例,默认 1:1。可选:16:9, 9:16, 4:3 等 |
39
39
 
@@ -42,6 +42,7 @@ description: 统一多媒体生成技能。支持图片和音乐生成,按资
42
42
  | 参数 | 必填 | 说明 |
43
43
  |------|------|------|
44
44
  | `--output-dir` | 是 | 输出目录的绝对路径,传入当前工作目录 |
45
+ | `--name` | 是 | 资源中文名称(如:开场音乐、背景配乐) |
45
46
  | `prompt` | 是 | 音乐风格/情绪描述 |
46
47
  | `--lyrics` | 否 | 歌词文本 |
47
48
  | `--instrumental` | 否 | 纯音乐模式 |
@@ -55,11 +55,11 @@ def ratio_to_size(ratio_str, max_dim=2048):
55
55
  return w, h
56
56
 
57
57
 
58
- def make_output_path(out_dir, resource_type, model, params, ext):
59
- """Generate filename with model name and key parameters.
58
+ def make_output_path(out_dir, name, resource_type, params, ext):
59
+ """Generate filename with Chinese name and key parameters.
60
60
 
61
- Format: {type}_{model}_{params}_{timestamp}.{ext}
62
- Example: image_nano-banana-pro_2048x1152_20260414_123456.png
61
+ Format: {name}_{params}_{timestamp}.{ext}
62
+ Example: 日落风景_2048x1152_20260414_123456.png
63
63
  """
64
64
  ts = datetime.now().strftime("%Y%m%d_%H%M%S")
65
65
 
@@ -67,18 +67,13 @@ def make_output_path(out_dir, resource_type, model, params, ext):
67
67
  param_parts = []
68
68
  if resource_type == "image":
69
69
  aspect_ratio = params.get("aspect_ratio", "1:1")
70
- # Convert ratio to actual dimensions
71
70
  w, h = ratio_to_size(aspect_ratio)
72
71
  param_parts.append(f"{w}x{h}")
73
72
  elif resource_type == "music":
74
73
  if params.get("instrumental"):
75
74
  param_parts.append("instrumental")
76
75
 
77
- # Build filename parts
78
- # Clean model name: replace special chars with dash
79
- clean_model = model.replace("/", "-").replace("_", "-")
80
-
81
- parts = [resource_type, clean_model]
76
+ parts = [name]
82
77
  if param_parts:
83
78
  parts.extend(param_parts)
84
79
  parts.append(ts)
@@ -148,6 +143,7 @@ def main():
148
143
  parser.add_argument("--instrumental", action="store_true", help="Instrumental mode (music only)")
149
144
  parser.add_argument("--output", default=None, help="Output file path")
150
145
  parser.add_argument("--output-dir", required=True, help="Absolute path to output directory (required)")
146
+ parser.add_argument("--name", required=True, help="Resource name in Chinese (required, e.g. 日落风景)")
151
147
  args = parser.parse_args()
152
148
 
153
149
  out_dir = ensure_output_dir(args.output_dir)
@@ -184,10 +180,9 @@ def main():
184
180
 
185
181
  files, used_provider = dispatch(args.type, args.prompt, **kwargs)
186
182
 
187
- # Rename with proper filename containing model and params (only for auto-naming)
183
+ # Rename with proper filename containing name and params (only for auto-naming)
188
184
  if not args.output:
189
- model = used_provider["model"]
190
- proper_path = make_output_path(out_dir, args.type, model, kwargs, ext)
185
+ proper_path = make_output_path(out_dir, args.name, args.type, kwargs, ext)
191
186
  import shutil
192
187
  shutil.move(files[0], proper_path)
193
188
  files = [proper_path]
@@ -1,15 +1,17 @@
1
1
  #!/bin/bash
2
2
  # 图片生成入口
3
- # 用法: ./image.sh --output-dir /abs/path "描述" [--aspect-ratio 16:9]
3
+ # 用法: ./image.sh --output-dir /abs/path --name 中文名 "描述" [--aspect-ratio 16:9]
4
4
  set -euo pipefail
5
5
 
6
6
  OUTPUT_DIR=""
7
+ NAME=""
7
8
  PROMPT=""
8
9
  ASPECT_RATIO=""
9
10
 
10
11
  while [ $# -gt 0 ]; do
11
12
  case "$1" in
12
13
  --output-dir) OUTPUT_DIR="$2"; shift 2 ;;
14
+ --name) NAME="$2"; shift 2 ;;
13
15
  --aspect-ratio) ASPECT_RATIO="$2"; shift 2 ;;
14
16
  *)
15
17
  if [ -z "$PROMPT" ]; then
@@ -20,20 +22,19 @@ while [ $# -gt 0 ]; do
20
22
  esac
21
23
  done
22
24
 
23
- if [ -z "$OUTPUT_DIR" ] || [ -z "$PROMPT" ]; then
24
- echo "用法: ./image.sh --output-dir <绝对路径> \"描述\" [--aspect-ratio 16:9]"
25
- echo "示例: ./image.sh --output-dir /root/.openclaw/workspace/media \"a cute cat\" --aspect-ratio 16:9"
26
- echo "注意: --output-dir 必须是绝对路径,传入当前工作目录即可"
25
+ if [ -z "$OUTPUT_DIR" ] || [ -z "$NAME" ] || [ -z "$PROMPT" ]; then
26
+ echo "用法: ./image.sh --output-dir <绝对路径> --name <中文名> \"描述\" [--aspect-ratio 16:9]"
27
+ echo "示例: ./image.sh --output-dir /root/.openclaw/workspace --name 日落风景 \"a sunset landscape\""
27
28
  exit 1
28
29
  fi
29
30
 
30
31
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
31
32
 
32
- # Build args for generate.py
33
33
  ARGS=()
34
34
  ARGS+=("image")
35
35
  ARGS+=("$PROMPT")
36
36
  ARGS+=(--output-dir "$OUTPUT_DIR")
37
+ ARGS+=(--name "$NAME")
37
38
  [ -n "$ASPECT_RATIO" ] && ARGS+=(--aspect-ratio "$ASPECT_RATIO")
38
39
 
39
40
  python3 "$SCRIPT_DIR/generate.py" "${ARGS[@]}"
@@ -1,9 +1,10 @@
1
1
  #!/bin/bash
2
2
  # 音乐生成入口
3
- # 用法: ./music.sh --output-dir /abs/path "描述" [--lyrics "歌词"] [--instrumental]
3
+ # 用法: ./music.sh --output-dir /abs/path --name 中文名 "描述" [--lyrics "歌词"] [--instrumental]
4
4
  set -euo pipefail
5
5
 
6
6
  OUTPUT_DIR=""
7
+ NAME=""
7
8
  PROMPT=""
8
9
  LYRICS=""
9
10
  INSTRUMENTAL=false
@@ -11,6 +12,7 @@ INSTRUMENTAL=false
11
12
  while [ $# -gt 0 ]; do
12
13
  case "$1" in
13
14
  --output-dir) OUTPUT_DIR="$2"; shift 2 ;;
15
+ --name) NAME="$2"; shift 2 ;;
14
16
  --lyrics) LYRICS="$2"; shift 2 ;;
15
17
  --instrumental) INSTRUMENTAL=true; shift ;;
16
18
  *)
@@ -22,20 +24,19 @@ while [ $# -gt 0 ]; do
22
24
  esac
23
25
  done
24
26
 
25
- if [ -z "$OUTPUT_DIR" ] || [ -z "$PROMPT" ]; then
26
- echo "用法: ./music.sh --output-dir <绝对路径> \"描述\" [--lyrics \"歌词\"] [--instrumental]"
27
- echo "示例: ./music.sh --output-dir /root/.openclaw/workspace/media \"relaxing guitar\" --instrumental"
28
- echo "注意: --output-dir 必须是绝对路径,传入当前工作目录即可"
27
+ if [ -z "$OUTPUT_DIR" ] || [ -z "$NAME" ] || [ -z "$PROMPT" ]; then
28
+ echo "用法: ./music.sh --output-dir <绝对路径> --name <中文名> \"描述\" [--lyrics \"歌词\"] [--instrumental]"
29
+ echo "示例: ./music.sh --output-dir /root/.openclaw/workspace --name 开场音乐 \"epic opening\" --instrumental"
29
30
  exit 1
30
31
  fi
31
32
 
32
33
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
33
34
 
34
- # Build args for generate.py
35
35
  ARGS=()
36
36
  ARGS+=("music")
37
37
  ARGS+=("$PROMPT")
38
38
  ARGS+=(--output-dir "$OUTPUT_DIR")
39
+ ARGS+=(--name "$NAME")
39
40
  [ -n "$LYRICS" ] && ARGS+=(--lyrics "$LYRICS")
40
41
  [ "$INSTRUMENTAL" = true ] && ARGS+=(--instrumental)
41
42