@aiyiran/myclaw 1.1.33 → 1.1.35
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/assets/myclaw-artifacts.js +157 -103
- package/package.json +1 -1
- package/patches/patch-manifest.json +2 -1
- package/patches/patch-skill.js +18 -8
- package/server/sync_workspace.py +92 -0
- package/skills/yiran-course-template-pipeline/SKILL.md +1 -2
- package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2651-demo/347/224/237/346/210/220.md +0 -7
- package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2652-student/347/224/237/346/210/220.md +35 -0
- package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2653-teacher/347/224/237/346/210/220.md +6 -5
- package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2654-/346/211/223/345/214/205/350/220/275/347/233/230.md +26 -21
- package/skills/yiran-course-template-pipeline/references/teacher-fields.md +5 -5
- package/skills/yiran-course-template-pipeline/references/teacher-scaffold.json +1 -1
- package/skills/yiran-course-template-pipeline/scripts/build_template_index.py +14 -2
- package/skills/yiran-course-template-pipeline/scripts/move_template_task.py +43 -7
- package/skills/yiran-course-template-pipeline/scripts/publish_template.py +44 -120
- package/skills/yiran-course-template-pipeline/template-index.json +269 -0
- package/skills/yiran-course-template-pipeline/template-index.md +52 -0
- package/skills/yiran-course-template-pipeline//345/272/237/345/274/203/rewrite_html_refs.py +102 -0
- package/skills/yiran-playground-template-use/SKILL.md +2 -12
- package/skills/yiran-course-template-pipeline/cdn-convention.md +0 -77
- package/skills/yiran-playground-template-use/scripts/build_template_index.py +0 -103
|
@@ -484,17 +484,44 @@
|
|
|
484
484
|
return tb.localeCompare(ta);
|
|
485
485
|
});
|
|
486
486
|
|
|
487
|
-
// 黑名单:隐藏以下文件(__XXX__
|
|
487
|
+
// 黑名单:隐藏以下文件(__XXX__ 格式的内部文件以及系统文件),豁免 student-view.html
|
|
488
488
|
var ARTIFACT_BLACKLIST = [
|
|
489
489
|
'__demo__.html',
|
|
490
490
|
'__student__.json',
|
|
491
491
|
'__teacher__.json',
|
|
492
492
|
'__teacher-view__.html',
|
|
493
|
+
'USER.md',
|
|
494
|
+
'TOOLS.md',
|
|
495
|
+
'SOUL.md',
|
|
496
|
+
'MEMORY.md',
|
|
497
|
+
'IDENTITY.md',
|
|
498
|
+
'HEARTBEAT.md',
|
|
499
|
+
'BOOTSTRAP.md',
|
|
500
|
+
'AGENTS.md'
|
|
493
501
|
];
|
|
502
|
+
|
|
503
|
+
// 黑名单目录:隐藏特定的系统目录
|
|
504
|
+
var DIR_BLACKLIST = [
|
|
505
|
+
'memory/',
|
|
506
|
+
'.openclaw/'
|
|
507
|
+
];
|
|
508
|
+
|
|
494
509
|
if (!showHiddenArtifacts) {
|
|
495
510
|
sorted = sorted.filter(function (asset) {
|
|
496
|
-
var
|
|
497
|
-
|
|
511
|
+
var path = asset.path || asset.name || '';
|
|
512
|
+
var basename = path.split('/').pop();
|
|
513
|
+
|
|
514
|
+
// 1. 检查文件名是否包含在黑名单中
|
|
515
|
+
if (ARTIFACT_BLACKLIST.indexOf(basename) !== -1) return false;
|
|
516
|
+
|
|
517
|
+
// 2. 检查路径是否以黑名单目录开头
|
|
518
|
+
for (var i = 0; i < DIR_BLACKLIST.length; i++) {
|
|
519
|
+
if (path.indexOf(DIR_BLACKLIST[i]) === 0) {
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return true;
|
|
498
525
|
});
|
|
499
526
|
}
|
|
500
527
|
|
|
@@ -828,10 +855,14 @@
|
|
|
828
855
|
'background: #fff',
|
|
829
856
|
].join(';');
|
|
830
857
|
|
|
831
|
-
//
|
|
858
|
+
// 1. 视频和 HTML 走 CDN
|
|
859
|
+
// 2. 其余类型(PDF、文本、音频等)走本地 API (避免乱码或 CDN 延迟)
|
|
832
860
|
var assetExt = (asset.path || '').split('.').pop().toLowerCase();
|
|
833
|
-
var
|
|
834
|
-
|
|
861
|
+
var VIDEO_EXTS = ['mp4', 'webm', 'mov', 'avi', 'mkv'];
|
|
862
|
+
var isHtml = assetExt === 'html' || assetExt === 'htm';
|
|
863
|
+
var isVideo = VIDEO_EXTS.indexOf(assetExt) !== -1;
|
|
864
|
+
|
|
865
|
+
if (isHtml || isVideo) {
|
|
835
866
|
iframe.src = previewUrl + '?t=' + Date.now();
|
|
836
867
|
} else {
|
|
837
868
|
iframe.src = MYCLAW_API_BASE + '/api/file?path=' + encodeURIComponent(getWorkspaceId() + '/' + asset.path) + '&t=' + Date.now();
|
|
@@ -2166,16 +2197,27 @@
|
|
|
2166
2197
|
// ── 头部 ──
|
|
2167
2198
|
var head = document.createElement('div');
|
|
2168
2199
|
head.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:12px 18px;border-bottom:1px solid rgba(255,255,255,0.1);flex-shrink:0;';
|
|
2200
|
+
var headLeft = document.createElement('span');
|
|
2201
|
+
headLeft.style.cssText = 'display:flex;align-items:center;gap:10px;';
|
|
2202
|
+
|
|
2169
2203
|
var headTitle = document.createElement('span');
|
|
2170
2204
|
headTitle.textContent = '📋 作品模板库';
|
|
2171
2205
|
headTitle.style.cssText = 'font-size:14px;font-weight:bold;';
|
|
2206
|
+
|
|
2207
|
+
var syncStatus = document.createElement('span');
|
|
2208
|
+
syncStatus.style.cssText = 'font-size:11px;color:rgba(205,214,244,0.4);transition:opacity 0.3s;';
|
|
2209
|
+
syncStatus.textContent = '⟳ 检查更新...';
|
|
2210
|
+
|
|
2211
|
+
headLeft.appendChild(headTitle);
|
|
2212
|
+
headLeft.appendChild(syncStatus);
|
|
2213
|
+
|
|
2172
2214
|
var headClose = document.createElement('span');
|
|
2173
2215
|
headClose.textContent = '✕';
|
|
2174
2216
|
headClose.style.cssText = 'cursor:pointer;font-size:14px;padding:2px 8px;border-radius:3px;transition:background 0.12s;';
|
|
2175
2217
|
headClose.onmouseenter = function () { headClose.style.background = 'rgba(255,255,255,0.1)'; };
|
|
2176
2218
|
headClose.onmouseleave = function () { headClose.style.background = 'none'; };
|
|
2177
2219
|
headClose.onclick = function () { overlay.remove(); };
|
|
2178
|
-
head.appendChild(
|
|
2220
|
+
head.appendChild(headLeft);
|
|
2179
2221
|
head.appendChild(headClose);
|
|
2180
2222
|
box.appendChild(head);
|
|
2181
2223
|
|
|
@@ -2315,108 +2357,120 @@
|
|
|
2315
2357
|
loadingEl.textContent = '加载中...';
|
|
2316
2358
|
leftPane.appendChild(loadingEl);
|
|
2317
2359
|
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
var templates = [];
|
|
2326
|
-
Object.keys(indexData).sort().forEach(function (series) {
|
|
2327
|
-
Object.keys(indexData[series]).sort(function (a, b) { return parseInt(a) - parseInt(b); }).forEach(function (num) {
|
|
2328
|
-
templates.push(indexData[series][num]);
|
|
2329
|
-
});
|
|
2360
|
+
function flattenTemplates(indexData) {
|
|
2361
|
+
// 兼容新格式 {index_updated_at, templates: {...}} 和旧格式 {A: {...}}
|
|
2362
|
+
var raw = indexData.templates || indexData;
|
|
2363
|
+
var list = [];
|
|
2364
|
+
Object.keys(raw).sort().forEach(function (series) {
|
|
2365
|
+
Object.keys(raw[series]).sort(function (a, b) { return parseInt(a) - parseInt(b); }).forEach(function (num) {
|
|
2366
|
+
list.push(raw[series][num]);
|
|
2330
2367
|
});
|
|
2368
|
+
});
|
|
2369
|
+
return list;
|
|
2370
|
+
}
|
|
2331
2371
|
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
};
|
|
2369
|
-
|
|
2370
|
-
rightHeader.appendChild(infoSpan);
|
|
2371
|
-
rightHeader.appendChild(useBtn);
|
|
2372
|
+
function renderTemplateList(templates) {
|
|
2373
|
+
leftPane.textContent = '';
|
|
2374
|
+
var activeRow = null;
|
|
2375
|
+
|
|
2376
|
+
templates.forEach(function (tpl) {
|
|
2377
|
+
var row = document.createElement('div');
|
|
2378
|
+
row.style.cssText = 'padding:10px;border-radius:5px;cursor:pointer;transition:background 0.12s;';
|
|
2379
|
+
|
|
2380
|
+
function setActive() {
|
|
2381
|
+
if (activeRow) activeRow.style.background = 'transparent';
|
|
2382
|
+
activeRow = row;
|
|
2383
|
+
row.style.background = 'rgba(100,149,237,0.15)';
|
|
2384
|
+
currentTpl = tpl;
|
|
2385
|
+
setFooterStatus('');
|
|
2386
|
+
rightHeader.innerHTML = '';
|
|
2387
|
+
var infoSpan = document.createElement('span');
|
|
2388
|
+
infoSpan.style.cssText = 'font-size:12px;color:#cdd6f4;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
|
|
2389
|
+
infoSpan.textContent = tpl['系列'] + tpl['编号'] + ' ' + tpl['名称'];
|
|
2390
|
+
var useBtn = document.createElement('button');
|
|
2391
|
+
useBtn.textContent = '✨ 使用模板';
|
|
2392
|
+
useBtn.style.cssText = 'flex-shrink:0;padding:5px 14px;background:#a78bfa;border:none;border-radius:4px;color:#fff;font-size:12px;font-family:monospace;cursor:pointer;transition:background 0.15s;white-space:nowrap;';
|
|
2393
|
+
useBtn.onmouseenter = function () { useBtn.style.background = '#8b5cf6'; };
|
|
2394
|
+
useBtn.onmouseleave = function () { useBtn.style.background = '#a78bfa'; };
|
|
2395
|
+
useBtn.onclick = function () {
|
|
2396
|
+
var promptText = '我要使用' + tpl['系列'] + tpl['编号'] + '模板:' + tpl['名称'] + '。' + tpl['一句话说明'];
|
|
2397
|
+
navigator.clipboard.writeText(promptText).then(function () {
|
|
2398
|
+
useBtn.textContent = '✓ 提示词已复制';
|
|
2399
|
+
useBtn.style.background = '#10b981';
|
|
2400
|
+
setTimeout(function () { useBtn.textContent = '✨ 使用模板'; useBtn.style.background = '#a78bfa'; }, 2000);
|
|
2401
|
+
});
|
|
2402
|
+
};
|
|
2403
|
+
rightHeader.appendChild(infoSpan);
|
|
2404
|
+
rightHeader.appendChild(useBtn);
|
|
2405
|
+
previewIframe.src = MYCLAW_API_BASE + '/api/file?path='
|
|
2406
|
+
+ encodeURIComponent(TEMPLATE_ROOT + '/templates/' + tpl['文件夹名'] + '/__student-view__.html');
|
|
2407
|
+
}
|
|
2372
2408
|
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2409
|
+
row.onclick = setActive;
|
|
2410
|
+
row.onmouseenter = function () { if (row !== activeRow) row.style.background = 'rgba(255,255,255,0.06)'; };
|
|
2411
|
+
row.onmouseleave = function () { if (row !== activeRow) row.style.background = 'transparent'; };
|
|
2412
|
+
|
|
2413
|
+
var topRow = document.createElement('div');
|
|
2414
|
+
topRow.style.cssText = 'display:flex;align-items:center;gap:6px;margin-bottom:4px;';
|
|
2415
|
+
var badge = document.createElement('span');
|
|
2416
|
+
badge.textContent = tpl['系列'] + tpl['编号'];
|
|
2417
|
+
badge.style.cssText = 'font-size:10px;font-weight:bold;background:#4a4a7a;color:#cdd6f4;padding:1px 7px;border-radius:8px;flex-shrink:0;';
|
|
2418
|
+
var nameEl = document.createElement('span');
|
|
2419
|
+
nameEl.textContent = tpl['名称'];
|
|
2420
|
+
nameEl.style.cssText = 'font-size:12px;color:#cdd6f4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
|
|
2421
|
+
topRow.appendChild(badge);
|
|
2422
|
+
topRow.appendChild(nameEl);
|
|
2423
|
+
|
|
2424
|
+
var descEl = document.createElement('div');
|
|
2425
|
+
descEl.textContent = tpl['一句话说明'];
|
|
2426
|
+
descEl.style.cssText = 'font-size:11px;color:rgba(205,214,244,0.5);line-height:1.4;margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;';
|
|
2427
|
+
|
|
2428
|
+
var tagsEl = document.createElement('div');
|
|
2429
|
+
tagsEl.style.cssText = 'display:flex;gap:4px;margin-top:5px;flex-wrap:wrap;';
|
|
2430
|
+
[tpl['主能力标签'], tpl['任务类型标签']].filter(Boolean).forEach(function (tag) {
|
|
2431
|
+
var tagEl = document.createElement('span');
|
|
2432
|
+
tagEl.textContent = tag;
|
|
2433
|
+
tagEl.style.cssText = 'font-size:9px;padding:1px 7px;border-radius:8px;background:rgba(167,139,250,0.15);color:#a78bfa;';
|
|
2434
|
+
tagsEl.appendChild(tagEl);
|
|
2435
|
+
});
|
|
2377
2436
|
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
topRow.style.cssText = 'display:flex;align-items:center;gap:6px;margin-bottom:4px;';
|
|
2385
|
-
|
|
2386
|
-
var badge = document.createElement('span');
|
|
2387
|
-
badge.textContent = tpl['系列'] + tpl['编号'];
|
|
2388
|
-
badge.style.cssText = 'font-size:10px;font-weight:bold;background:#4a4a7a;color:#cdd6f4;padding:1px 7px;border-radius:8px;flex-shrink:0;';
|
|
2389
|
-
|
|
2390
|
-
var nameEl = document.createElement('span');
|
|
2391
|
-
nameEl.textContent = tpl['名称'];
|
|
2392
|
-
nameEl.style.cssText = 'font-size:12px;color:#cdd6f4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
|
|
2393
|
-
|
|
2394
|
-
topRow.appendChild(badge);
|
|
2395
|
-
topRow.appendChild(nameEl);
|
|
2396
|
-
|
|
2397
|
-
// 一句话说明
|
|
2398
|
-
var descEl = document.createElement('div');
|
|
2399
|
-
descEl.textContent = tpl['一句话说明'];
|
|
2400
|
-
descEl.style.cssText = 'font-size:11px;color:rgba(205,214,244,0.5);line-height:1.4;margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;';
|
|
2401
|
-
|
|
2402
|
-
// 标签
|
|
2403
|
-
var tagsEl = document.createElement('div');
|
|
2404
|
-
tagsEl.style.cssText = 'display:flex;gap:4px;margin-top:5px;flex-wrap:wrap;';
|
|
2405
|
-
[tpl['主能力标签'], tpl['任务类型标签']].filter(Boolean).forEach(function (tag) {
|
|
2406
|
-
var tagEl = document.createElement('span');
|
|
2407
|
-
tagEl.textContent = tag;
|
|
2408
|
-
tagEl.style.cssText = 'font-size:9px;padding:1px 7px;border-radius:8px;background:rgba(167,139,250,0.15);color:#a78bfa;';
|
|
2409
|
-
tagsEl.appendChild(tagEl);
|
|
2410
|
-
});
|
|
2437
|
+
row.appendChild(topRow);
|
|
2438
|
+
row.appendChild(descEl);
|
|
2439
|
+
row.appendChild(tagsEl);
|
|
2440
|
+
leftPane.appendChild(row);
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2411
2443
|
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2444
|
+
// ── 并行:加载本地 index + 检查 CDN 更新 ──────────────────────────────
|
|
2445
|
+
// 检查 CDN 更新(不阻塞列表加载)
|
|
2446
|
+
fetch(MYCLAW_API_BASE + '/api/sync-templates')
|
|
2447
|
+
.then(function (r) { return r.json(); })
|
|
2448
|
+
.then(function (sync) {
|
|
2449
|
+
if (!sync.changed) {
|
|
2450
|
+
syncStatus.textContent = '';
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
var msg = '';
|
|
2454
|
+
if (sync.added && sync.added.length) msg += '新增 ' + sync.added.length + ' 个';
|
|
2455
|
+
if (sync.updated && sync.updated.length) msg += (msg ? ',' : '') + '更新 ' + sync.updated.length + ' 个';
|
|
2456
|
+
syncStatus.textContent = '✓ ' + msg;
|
|
2457
|
+
syncStatus.style.color = '#10b981';
|
|
2458
|
+
// 重新加载列表
|
|
2459
|
+
fetch(MYCLAW_API_BASE + '/api/file?path=' + encodeURIComponent(TEMPLATE_ROOT + '/template-index.json'))
|
|
2460
|
+
.then(function (r) { return r.json(); })
|
|
2461
|
+
.then(function (newIndex) { renderTemplateList(flattenTemplates(newIndex)); })
|
|
2462
|
+
.catch(function () {});
|
|
2463
|
+
setTimeout(function () { syncStatus.textContent = ''; }, 4000);
|
|
2464
|
+
})
|
|
2465
|
+
.catch(function () { syncStatus.textContent = ''; });
|
|
2416
2466
|
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2467
|
+
fetch(MYCLAW_API_BASE + '/api/file?path=' + encodeURIComponent(TEMPLATE_ROOT + '/template-index.json'))
|
|
2468
|
+
.then(function (r) {
|
|
2469
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
2470
|
+
return r.json();
|
|
2471
|
+
})
|
|
2472
|
+
.then(function (indexData) {
|
|
2473
|
+
renderTemplateList(flattenTemplates(indexData));
|
|
2420
2474
|
})
|
|
2421
2475
|
.catch(function (err) {
|
|
2422
2476
|
leftPane.textContent = '';
|
package/package.json
CHANGED
package/patches/patch-skill.js
CHANGED
|
@@ -43,18 +43,27 @@ function findSkillsDir() {
|
|
|
43
43
|
/**
|
|
44
44
|
* 递归复制目录
|
|
45
45
|
*/
|
|
46
|
-
function copyDirSync(src, dest) {
|
|
46
|
+
function copyDirSync(src, dest, ignores = []) {
|
|
47
47
|
if (!fs.existsSync(dest)) {
|
|
48
48
|
fs.mkdirSync(dest, { recursive: true });
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
52
52
|
for (const entry of entries) {
|
|
53
|
+
// 全局通用忽略项
|
|
54
|
+
if (entry.name === '__pycache__' || entry.name === '.DS_Store') {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// 配置覆盖忽略项
|
|
58
|
+
if (ignores && ignores.indexOf(entry.name) !== -1) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
53
62
|
const srcPath = path.join(src, entry.name);
|
|
54
63
|
const destPath = path.join(dest, entry.name);
|
|
55
64
|
|
|
56
65
|
if (entry.isDirectory()) {
|
|
57
|
-
copyDirSync(srcPath, destPath);
|
|
66
|
+
copyDirSync(srcPath, destPath, ignores);
|
|
58
67
|
} else {
|
|
59
68
|
fs.copyFileSync(srcPath, destPath);
|
|
60
69
|
}
|
|
@@ -96,16 +105,16 @@ function patchSkills() {
|
|
|
96
105
|
const manifestSkills = manifest && Array.isArray(manifest.skills) ? manifest.skills : [];
|
|
97
106
|
|
|
98
107
|
// 把 manifest 里的配置做成一个 map 方便查询
|
|
99
|
-
const
|
|
108
|
+
const skillConfigMap = {};
|
|
100
109
|
for (const s of manifestSkills) {
|
|
101
|
-
if (s.name)
|
|
110
|
+
if (s.name) skillConfigMap[s.name] = s;
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
let count = 0;
|
|
105
114
|
|
|
106
115
|
// 1. 处理 manifest 中明确被 delete 的 skill
|
|
107
|
-
for (const [name,
|
|
108
|
-
if (strategy === 'delete') {
|
|
116
|
+
for (const [name, config] of Object.entries(skillConfigMap)) {
|
|
117
|
+
if (config.strategy === 'delete') {
|
|
109
118
|
const targetDir = path.join(skillsDir, name);
|
|
110
119
|
if (fs.existsSync(targetDir)) {
|
|
111
120
|
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
@@ -120,7 +129,8 @@ function patchSkills() {
|
|
|
120
129
|
for (const skillDir of skillDirs) {
|
|
121
130
|
const srcDir = path.join(myclawSkillsDir, skillDir.name);
|
|
122
131
|
const destDir = path.join(skillsDir, skillDir.name);
|
|
123
|
-
const
|
|
132
|
+
const config = skillConfigMap[skillDir.name];
|
|
133
|
+
const strategy = config ? config.strategy : null;
|
|
124
134
|
|
|
125
135
|
if (!strategy) {
|
|
126
136
|
console.log('[myclaw-skill] ⊘ 跳过: ' + skillDir.name + ' [未配置,跳过]');
|
|
@@ -148,7 +158,7 @@ function patchSkills() {
|
|
|
148
158
|
continue;
|
|
149
159
|
}
|
|
150
160
|
|
|
151
|
-
copyDirSync(srcDir, destDir);
|
|
161
|
+
copyDirSync(srcDir, destDir, config.ignore || []);
|
|
152
162
|
count++;
|
|
153
163
|
console.log('[myclaw-skill] ✅ 已注入: ' + skillDir.name + ' [' + strategy + ']');
|
|
154
164
|
}
|
package/server/sync_workspace.py
CHANGED
|
@@ -747,6 +747,8 @@ class MyclawAPIHandler(BaseHTTPRequestHandler):
|
|
|
747
747
|
return self._handle_agents_list()
|
|
748
748
|
elif path == '/api/file':
|
|
749
749
|
return self._handle_file(params)
|
|
750
|
+
elif path == '/api/sync-templates':
|
|
751
|
+
return self._handle_sync_templates()
|
|
750
752
|
else:
|
|
751
753
|
self._send_json({"error": "not found"}, 404)
|
|
752
754
|
|
|
@@ -1101,6 +1103,96 @@ class MyclawAPIHandler(BaseHTTPRequestHandler):
|
|
|
1101
1103
|
except Exception as e:
|
|
1102
1104
|
self._send_json({"error": str(e)}, 500)
|
|
1103
1105
|
|
|
1106
|
+
def _handle_sync_templates(self):
|
|
1107
|
+
"""GET /api/sync-templates
|
|
1108
|
+
检查 CDN 上的 template-index.json 是否比本地新,若有变化则增量下载。
|
|
1109
|
+
返回 {changed: false} 或 {changed: true, added: [...], updated: [...]}
|
|
1110
|
+
"""
|
|
1111
|
+
CDN_INDEX_URL = 'https://cdn.yiranlaoshi.com/myclaw/live/yiran/skills/yiran-playground-template-use/template-index.json'
|
|
1112
|
+
CDN_TEMPLATE_BASE = 'https://cdn.yiranlaoshi.com/myclaw/live/yiran/skills/yiran-playground-template-use/templates'
|
|
1113
|
+
|
|
1114
|
+
local_templates_dir = os.path.join(
|
|
1115
|
+
get_openclaw_path(), 'skills', 'yiran-playground-template-use', 'templates'
|
|
1116
|
+
)
|
|
1117
|
+
local_index_path = os.path.join(
|
|
1118
|
+
get_openclaw_path(), 'skills', 'yiran-playground-template-use', 'template-index.json'
|
|
1119
|
+
)
|
|
1120
|
+
|
|
1121
|
+
try:
|
|
1122
|
+
# 拉取 CDN index
|
|
1123
|
+
with urllib.request.urlopen(CDN_INDEX_URL, timeout=10) as resp:
|
|
1124
|
+
cdn_index = json.loads(resp.read().decode('utf-8'))
|
|
1125
|
+
except Exception as e:
|
|
1126
|
+
self._send_json({'error': 'CDN index 获取失败: ' + str(e)}, 502)
|
|
1127
|
+
return
|
|
1128
|
+
|
|
1129
|
+
# 读取本地 index
|
|
1130
|
+
local_index = {}
|
|
1131
|
+
if os.path.isfile(local_index_path):
|
|
1132
|
+
try:
|
|
1133
|
+
with open(local_index_path, 'r', encoding='utf-8') as f:
|
|
1134
|
+
local_index = json.load(f)
|
|
1135
|
+
except Exception:
|
|
1136
|
+
pass
|
|
1137
|
+
|
|
1138
|
+
cdn_updated_at = cdn_index.get('index_updated_at', '')
|
|
1139
|
+
local_updated_at = local_index.get('index_updated_at', '')
|
|
1140
|
+
|
|
1141
|
+
if cdn_updated_at and cdn_updated_at == local_updated_at:
|
|
1142
|
+
self._send_json({'changed': False})
|
|
1143
|
+
return
|
|
1144
|
+
|
|
1145
|
+
# 找出新增和变更的模板
|
|
1146
|
+
cdn_templates = cdn_index.get('templates', {})
|
|
1147
|
+
local_templates = local_index.get('templates', {})
|
|
1148
|
+
added = []
|
|
1149
|
+
updated = []
|
|
1150
|
+
|
|
1151
|
+
for series, nums in cdn_templates.items():
|
|
1152
|
+
for num, record in nums.items():
|
|
1153
|
+
folder_name = record.get('文件夹名', '')
|
|
1154
|
+
files = record.get('files', [])
|
|
1155
|
+
cdn_ua = record.get('updated_at', '')
|
|
1156
|
+
local_ua = local_templates.get(series, {}).get(num, {}).get('updated_at', '')
|
|
1157
|
+
|
|
1158
|
+
if not folder_name or not files:
|
|
1159
|
+
continue
|
|
1160
|
+
|
|
1161
|
+
is_new = series not in local_templates or num not in local_templates.get(series, {})
|
|
1162
|
+
is_changed = not is_new and cdn_ua != local_ua
|
|
1163
|
+
|
|
1164
|
+
if not is_new and not is_changed:
|
|
1165
|
+
continue
|
|
1166
|
+
|
|
1167
|
+
# 下载该文件夹的所有文件
|
|
1168
|
+
folder_dir = os.path.join(local_templates_dir, folder_name)
|
|
1169
|
+
os.makedirs(folder_dir, exist_ok=True)
|
|
1170
|
+
for filename in files:
|
|
1171
|
+
url = '{}/{}/{}'.format(CDN_TEMPLATE_BASE, folder_name, filename)
|
|
1172
|
+
dest = os.path.join(folder_dir, filename)
|
|
1173
|
+
try:
|
|
1174
|
+
with urllib.request.urlopen(url, timeout=15) as r:
|
|
1175
|
+
with open(dest, 'wb') as out:
|
|
1176
|
+
out.write(r.read())
|
|
1177
|
+
except Exception as e:
|
|
1178
|
+
print('[sync-templates] 下载失败 {}: {}'.format(url, e))
|
|
1179
|
+
|
|
1180
|
+
if is_new:
|
|
1181
|
+
added.append(folder_name)
|
|
1182
|
+
else:
|
|
1183
|
+
updated.append(folder_name)
|
|
1184
|
+
|
|
1185
|
+
# 覆盖本地 index
|
|
1186
|
+
try:
|
|
1187
|
+
os.makedirs(os.path.dirname(local_index_path), exist_ok=True)
|
|
1188
|
+
with open(local_index_path, 'w', encoding='utf-8') as f:
|
|
1189
|
+
json.dump(cdn_index, f, ensure_ascii=False, indent=2)
|
|
1190
|
+
except Exception as e:
|
|
1191
|
+
self._send_json({'error': 'index 写入失败: ' + str(e)}, 500)
|
|
1192
|
+
return
|
|
1193
|
+
|
|
1194
|
+
self._send_json({'changed': True, 'added': added, 'updated': updated})
|
|
1195
|
+
|
|
1104
1196
|
def log_message(self, format, *args):
|
|
1105
1197
|
# 静默日志,避免轮询刷屏
|
|
1106
1198
|
pass
|
|
@@ -61,8 +61,7 @@ description: 面向中文课堂的小专题课程模板流水线。适用于把
|
|
|
61
61
|
- 再定 __student__.json
|
|
62
62
|
- 再定 __teacher__.json
|
|
63
63
|
- 阶段一到三先在当前工作目录的任务文件夹里完成
|
|
64
|
-
-
|
|
65
|
-
- publish 脚本会统一重新打包:无论原引用是相对路径还是其他 CDN,只要本地能找到对应文件,都会上传到指定 CDN 并重写引用(见 `cdn-convention.md`)
|
|
64
|
+
- publish 脚本把模板文件夹内所有文件上传到 CDN,CDN 路径规则见 `prompts/阶段4-打包落盘.md`
|
|
66
65
|
- 第4阶段拆成 build、move、publish 三个独立脚本
|
|
67
66
|
- templates 作为 skill 的资产目录统一维护
|
|
68
67
|
- 模板有变化后,要重新构建索引
|
|
@@ -113,13 +113,6 @@
|
|
|
113
113
|
- 例如:`班级小组徽章_47.png`
|
|
114
114
|
- 这样可以减少浏览器缓存带来的旧图问题
|
|
115
115
|
|
|
116
|
-
### 资源路径方式
|
|
117
|
-
- demo 网页中的所有媒体资源,不要使用本地相对路径
|
|
118
|
-
- 必须使用 CDN 链接
|
|
119
|
-
- CDN 固定前缀写死为:`https://cdn.yiranlaoshi.com/yiran/workspace-ai-demo`
|
|
120
|
-
- 后面再拼接资源相对路径
|
|
121
|
-
- 例如:`https://cdn.yiranlaoshi.com/yiran/workspace-ai-demo/c102_给小组作品做微调训练/班级小组徽章_47.png`
|
|
122
|
-
|
|
123
116
|
## demo 生成后的下一步
|
|
124
117
|
|
|
125
118
|
生成 demo 页面后,不要立刻结束。
|
|
@@ -78,6 +78,41 @@
|
|
|
78
78
|
- 每一步都包含:
|
|
79
79
|
- 标题
|
|
80
80
|
- 说明
|
|
81
|
+
- 默认优先从学生动作出发来写步骤,例如:先试一试、先看一看、先改一点、再加一个
|
|
82
|
+
- 但不要把动作顺序写死,不同任务按实际内容调整
|
|
83
|
+
- 文案要短,读完就能做,优先使用孩子能直接执行的动作词
|
|
84
|
+
- 少写抽象提问,少写老师视角的分析话术
|
|
85
|
+
|
|
86
|
+
补充判断规则:
|
|
87
|
+
- 步骤标题要像“要去做的事”,不要像“要去理解的概念”
|
|
88
|
+
- 说明句尽量一句话说清,避免解释过长
|
|
89
|
+
- 默认优先写“小改动”,再写“自主新增”;不要一上来就要求学生从零设计完整新内容
|
|
90
|
+
- 如果一句步骤说明读完以后,学生还不知道手上下一步该做什么,这句就还不够好
|
|
91
|
+
|
|
92
|
+
常见不推荐写法:
|
|
93
|
+
- 想一想,它影响了什么
|
|
94
|
+
- 分析这个功能作用于哪里
|
|
95
|
+
- 理解页面结构变化逻辑
|
|
96
|
+
- 总结这个页面的设计特点
|
|
97
|
+
|
|
98
|
+
这些问题常见的坏处是:
|
|
99
|
+
- 太抽象
|
|
100
|
+
- 不贴近孩子动作
|
|
101
|
+
- 有时和页面真实任务也不完全对应
|
|
102
|
+
|
|
103
|
+
因此 student 步骤里优先写这些动作词:
|
|
104
|
+
- 点
|
|
105
|
+
- 看
|
|
106
|
+
- 挑
|
|
107
|
+
- 改
|
|
108
|
+
- 加
|
|
109
|
+
|
|
110
|
+
尽量少写这些老师视角词:
|
|
111
|
+
- 分析
|
|
112
|
+
- 理解
|
|
113
|
+
- 总结
|
|
114
|
+
- 归纳
|
|
115
|
+
- 识别
|
|
81
116
|
|
|
82
117
|
### 4)评价目标
|
|
83
118
|
也是本阶段核心。
|
|
@@ -88,13 +88,14 @@
|
|
|
88
88
|
### 4)主能力标签
|
|
89
89
|
这是最核心的教学能力标签,只能选一个。
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
91
|
+
固定集合(建议直接按完整写法输出):
|
|
92
|
+
- 第一能力:构思能力
|
|
93
|
+
- 第二能力:表达构建
|
|
94
|
+
- 第三能力:问题判断
|
|
95
|
+
- 第四能力:迭代推进
|
|
96
96
|
|
|
97
97
|
它回答的是:这个任务主要训练什么能力。
|
|
98
|
+
注意:为了减少模型漏写编号,输出时优先直接使用上面的完整写法,不要只写后半段名称。
|
|
98
99
|
|
|
99
100
|
### 5)任务类型标签
|
|
100
101
|
说明作品本身属于什么类型,也只能选一个。
|