|
|
@@ -1,18 +1,22 @@
|
|
|
#!/usr/bin/env node
|
|
|
/**
|
|
|
- * OpenClaw Skills CDN 上传脚本
|
|
|
+ * OpenClaw Skills + Workshop CDN 上传脚本 v4.1
|
|
|
*
|
|
|
* 功能:
|
|
|
* 1. 将所有 skills 上传到七牛云 CDN,生成下载 URL
|
|
|
* 2. 将每个 skill 的元数据写入 Parse 数据库 Skill 表
|
|
|
- * 3. 生成 README.md(包含所有 skill 简介 + 下载链接)并上传到 CDN
|
|
|
+ * 3. 将 workshop 文件(playbook + session + memory templates)上传到 CDN
|
|
|
+ * 4. 生成 README.md(包含所有 skill 简介 + workshop 文件 + 下载链接)并上传到 CDN
|
|
|
*
|
|
|
* 使用方法:
|
|
|
- * node scripts/deploy/upload-skills.js
|
|
|
+ * node scripts/deploy/upload-skills.js # 上传全部(skills + workshop)
|
|
|
+ * node scripts/deploy/upload-skills.js --skills-only # 只上传 skills
|
|
|
+ * node scripts/deploy/upload-skills.js --workshop-only # 只上传 workshop
|
|
|
*
|
|
|
* 前置条件:
|
|
|
* 1. npm install qiniu
|
|
|
* 2. 配置下方 QINIU_ACCESS_KEY 和 QINIU_SECRET_KEY
|
|
|
+ * 3. 先运行 deploy-to-openclaw.ps1 确保 ~/.openclaw 是最新的
|
|
|
*
|
|
|
* 参考:hongben-server/modules/fmode-tikhub-server/upload.sh
|
|
|
*/
|
|
|
@@ -37,8 +41,16 @@ const PARSE_BASE = 'https://server.fmode.cn/parse';
|
|
|
const APP_ID = 'ncloudmaster';
|
|
|
const MASTER_KEY = process.env.PARSE_MASTER_KEY || 'SnkK12*&sunq2#@20!';
|
|
|
|
|
|
-// Skills 源目录(部署目录)
|
|
|
-const DEPLOYED_SKILLS_DIR = path.join(process.env.USERPROFILE || process.env.HOME, '.openclaw', 'skills');
|
|
|
+// 源目录(部署目录)
|
|
|
+const OPENCLAW_DIR = path.join(process.env.USERPROFILE || process.env.HOME, '.openclaw');
|
|
|
+const DEPLOYED_SKILLS_DIR = path.join(OPENCLAW_DIR, 'skills');
|
|
|
+const WORKSHOP_DIR = path.join(OPENCLAW_DIR, 'workspace');
|
|
|
+const MEMORY_TEMPLATES_DIR = path.join(WORKSHOP_DIR, 'memory');
|
|
|
+
|
|
|
+// CLI 参数
|
|
|
+const args = process.argv.slice(2);
|
|
|
+const SKILLS_ONLY = args.includes('--skills-only');
|
|
|
+const WORKSHOP_ONLY = args.includes('--workshop-only');
|
|
|
|
|
|
// 要上传的技能目录列表(如果为空,则上传所有 skills)
|
|
|
const SKILL_FILTER = []; // 例如 ['tiktok-video-search', 'jimeng-img-v4'] 指定只上传某些
|
|
|
@@ -77,6 +89,38 @@ const SKILL_PACKAGES = {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// ============================================
|
|
|
+// Workshop 文件定义 (v4.1)
|
|
|
+// ============================================
|
|
|
+const WORKSHOP_FILES = {
|
|
|
+ playbooks: [
|
|
|
+ 'workshop-voc-playbook.md',
|
|
|
+ 'workshop-global-rules.md',
|
|
|
+ 'workshop-session-0-intake.md',
|
|
|
+ 'workshop-session-1-category.md',
|
|
|
+ 'workshop-session-2-brand.md',
|
|
|
+ 'workshop-session-3-voc.md',
|
|
|
+ 'workshop-session-4-deep.md',
|
|
|
+ 'workshop-session-5-report.md'
|
|
|
+ ],
|
|
|
+ supporting: [
|
|
|
+ 'product-analysis-playbook.md',
|
|
|
+ 'synthesis-prompts.md',
|
|
|
+ 'voc-report-schema-spec.md'
|
|
|
+ ],
|
|
|
+ memoryTemplates: [
|
|
|
+ 'workshop-progress.json',
|
|
|
+ 'brand-context.json',
|
|
|
+ 'analysis-request.json',
|
|
|
+ 'calibration-notes.json',
|
|
|
+ 'stage-1-output.json',
|
|
|
+ 'stage-2-output.json',
|
|
|
+ 'stage-3-output.json',
|
|
|
+ 'stage-4-output.json',
|
|
|
+ 'stage-5-output.json'
|
|
|
+ ]
|
|
|
+};
|
|
|
+
|
|
|
function getPackageForCategory(category) {
|
|
|
for (const [pkgId, pkg] of Object.entries(SKILL_PACKAGES)) {
|
|
|
if (pkg.categories.includes(category || 'other')) return pkgId;
|
|
|
@@ -272,6 +316,83 @@ function extractSkillMeta(skillDir, skillName) {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+// ============================================
|
|
|
+// Collect workshop files
|
|
|
+// ============================================
|
|
|
+
|
|
|
+function collectWorkshopFiles() {
|
|
|
+ const files = [];
|
|
|
+
|
|
|
+ // Playbooks + supporting docs
|
|
|
+ const allMdFiles = [...WORKSHOP_FILES.playbooks, ...WORKSHOP_FILES.supporting];
|
|
|
+ for (const fileName of allMdFiles) {
|
|
|
+ const filePath = path.join(WORKSHOP_DIR, fileName);
|
|
|
+ if (fs.existsSync(filePath)) {
|
|
|
+ files.push({
|
|
|
+ localPath: filePath,
|
|
|
+ cdnKey: `${CDN_PREFIX}/workshop/${fileName}`,
|
|
|
+ fileName,
|
|
|
+ type: WORKSHOP_FILES.playbooks.includes(fileName) ? 'playbook' : 'supporting'
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ console.log(` ⚠️ 跳过 ${fileName}(不存在)`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Memory templates
|
|
|
+ for (const fileName of WORKSHOP_FILES.memoryTemplates) {
|
|
|
+ const filePath = path.join(MEMORY_TEMPLATES_DIR, fileName);
|
|
|
+ if (fs.existsSync(filePath)) {
|
|
|
+ files.push({
|
|
|
+ localPath: filePath,
|
|
|
+ cdnKey: `${CDN_PREFIX}/workshop/memory-templates/${fileName}`,
|
|
|
+ fileName: `memory-templates/${fileName}`,
|
|
|
+ type: 'memory-template'
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ console.log(` ⚠️ 跳过 memory-templates/${fileName}(不存在)`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return files;
|
|
|
+}
|
|
|
+
|
|
|
+function generateWorkshopManifest(workshopResults) {
|
|
|
+ const playbooks = {};
|
|
|
+ const supporting = {};
|
|
|
+ const memoryTemplates = {};
|
|
|
+
|
|
|
+ for (const item of workshopResults) {
|
|
|
+ if (item.status !== 'ok') continue;
|
|
|
+ if (item.type === 'playbook') {
|
|
|
+ playbooks[item.fileName] = item.url;
|
|
|
+ } else if (item.type === 'supporting') {
|
|
|
+ supporting[item.fileName] = item.url;
|
|
|
+ } else if (item.type === 'memory-template') {
|
|
|
+ const baseName = item.fileName.replace('memory-templates/', '');
|
|
|
+ memoryTemplates[baseName] = item.url;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ package: 'workshop',
|
|
|
+ label: 'VOC虾工作坊',
|
|
|
+ description: 'VOC 工作坊完整流程文件(v4.1 Side Quest 架构)',
|
|
|
+ version: '4.1.0',
|
|
|
+ generatedAt: new Date().toISOString(),
|
|
|
+ architecture: {
|
|
|
+ sessions: 6,
|
|
|
+ sideQuests: 17,
|
|
|
+ stateFlow: 'pending → in_progress → awaitingCalibration → exploring → completed'
|
|
|
+ },
|
|
|
+ installTarget: {
|
|
|
+ playbooks: '~/.openclaw/workspace/',
|
|
|
+ memoryTemplates: '~/.openclaw/workspace/memory/'
|
|
|
+ },
|
|
|
+ files: { playbooks, supporting, memoryTemplates }
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
// ============================================
|
|
|
// Collect skills
|
|
|
// ============================================
|
|
|
@@ -320,7 +441,7 @@ function collectSkills() {
|
|
|
// Generate README.md
|
|
|
// ============================================
|
|
|
|
|
|
-function generateReadme(skillEntries) {
|
|
|
+function generateReadme(skillEntries, workshopManifestUrl) {
|
|
|
const now = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
|
|
|
|
// 按技能包分组
|
|
|
@@ -338,9 +459,9 @@ function generateReadme(skillEntries) {
|
|
|
}
|
|
|
|
|
|
const lines = [
|
|
|
- '# OpenClaw Skills 技能市场',
|
|
|
+ '# OpenClaw Skills 技能市场 + Workshop',
|
|
|
'',
|
|
|
- `> 自动生成于 ${now},共 ${visibleCount} 个技能(分 ${Object.keys(pkgMap).filter(k => SKILL_PACKAGES[k]?.status !== 'hidden').length} 个套件)`,
|
|
|
+ `> 自动生成于 ${now},共 ${visibleCount} 个技能(分 ${Object.keys(pkgMap).filter(k => SKILL_PACKAGES[k]?.status !== 'hidden').length} 个套件)+ VOC工作坊 v4.1`,
|
|
|
'',
|
|
|
];
|
|
|
|
|
|
@@ -418,6 +539,29 @@ function generateReadme(skillEntries) {
|
|
|
lines.push('下载对应套件的 `.json` 安装清单,包含该套件所有技能的 CDN 下载地址。');
|
|
|
lines.push('');
|
|
|
|
|
|
+ // Workshop section
|
|
|
+ if (workshopManifestUrl) {
|
|
|
+ lines.push('---');
|
|
|
+ lines.push('');
|
|
|
+ lines.push('## 🦐 VOC虾工作坊 v4.1');
|
|
|
+ lines.push('');
|
|
|
+ lines.push('> VOC 工作坊完整流程文件,含 Side Quest 支线探索系统');
|
|
|
+ lines.push('>');
|
|
|
+ lines.push(`> 📥 安装清单:[\`workshop.json\`](${workshopManifestUrl})`);
|
|
|
+ lines.push('');
|
|
|
+ lines.push('**架构**:6个Session + 17个Side Quest,主线感知 + 支线深耕');
|
|
|
+ lines.push('');
|
|
|
+ lines.push('| 文件 | 说明 |');
|
|
|
+ lines.push('|------|------|');
|
|
|
+ lines.push('| `workshop-voc-playbook.md` | 入口路由 |');
|
|
|
+ lines.push('| `workshop-global-rules.md` | 全局规则 + Side Quest Protocol |');
|
|
|
+ lines.push('| `workshop-session-0~5-*.md` | Session 0-5 流程文件 |');
|
|
|
+ lines.push('| `memory-templates/*.json` | 记忆模板(9个JSON) |');
|
|
|
+ lines.push('');
|
|
|
+ lines.push('安装方式:将 playbook 文件放入 `~/.openclaw/workspace/`,memory templates 放入 `~/.openclaw/workspace/memory/`');
|
|
|
+ lines.push('');
|
|
|
+ }
|
|
|
+
|
|
|
return lines.join('\n');
|
|
|
}
|
|
|
|
|
|
@@ -461,9 +605,12 @@ function generatePackageManifests(skillEntries) {
|
|
|
|
|
|
async function main() {
|
|
|
console.log('==================================================');
|
|
|
- console.log(' OpenClaw Skills CDN 上传 + Parse 同步脚本');
|
|
|
+ console.log(' OpenClaw Skills + Workshop CDN 上传 v4.1');
|
|
|
console.log('==================================================');
|
|
|
console.log('');
|
|
|
+ if (SKILLS_ONLY) console.log(' 📌 模式: 仅上传 Skills');
|
|
|
+ if (WORKSHOP_ONLY) console.log(' 📌 模式: 仅上传 Workshop');
|
|
|
+ console.log('');
|
|
|
|
|
|
// 检查凭证
|
|
|
if (QINIU_ACCESS_KEY === 'YOUR_ACCESS_KEY_HERE' || QINIU_SECRET_KEY === 'YOUR_SECRET_KEY_HERE') {
|
|
|
@@ -473,94 +620,179 @@ async function main() {
|
|
|
process.exit(1);
|
|
|
}
|
|
|
|
|
|
- // 收集技能
|
|
|
- const skills = collectSkills();
|
|
|
- console.log(`📦 找到 ${skills.length} 个技能待上传`);
|
|
|
- console.log(`📂 源目录: ${DEPLOYED_SKILLS_DIR}`);
|
|
|
console.log(`☁️ Bucket: ${BUCKET}`);
|
|
|
console.log(`🔗 CDN前缀: ${CDN_PREFIX}`);
|
|
|
console.log('');
|
|
|
|
|
|
+ // 收集技能
|
|
|
+ const skills = WORKSHOP_ONLY ? [] : collectSkills();
|
|
|
+ if (!WORKSHOP_ONLY) {
|
|
|
+ console.log(`📦 找到 ${skills.length} 个技能待上传`);
|
|
|
+ console.log(`📂 Skills源: ${DEPLOYED_SKILLS_DIR}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 收集 workshop 文件
|
|
|
+ const workshopFiles = SKILLS_ONLY ? [] : collectWorkshopFiles();
|
|
|
+ if (!SKILLS_ONLY) {
|
|
|
+ console.log(`� 找到 ${workshopFiles.length} 个 workshop 文件待上传`);
|
|
|
+ console.log(`📂 Workshop源: ${WORKSHOP_DIR}`);
|
|
|
+ }
|
|
|
+
|
|
|
let totalFiles = 0;
|
|
|
skills.forEach(s => { totalFiles += s.files.length; });
|
|
|
+ totalFiles += workshopFiles.length;
|
|
|
console.log(`📄 总计 ${totalFiles} 个文件`);
|
|
|
console.log('');
|
|
|
|
|
|
- // ── Step 1: 上传文件到七牛 ──
|
|
|
- console.log('── Step 1: 上传文件到七牛云 ──');
|
|
|
+ // ── Step 1: 上传 Skill 文件到七牛 ──
|
|
|
let successCount = 0;
|
|
|
let failCount = 0;
|
|
|
const results = [];
|
|
|
|
|
|
- for (const skill of skills) {
|
|
|
- process.stdout.write(` 📦 ${skill.name} ... `);
|
|
|
- const skillResults = [];
|
|
|
- const cdnUrls = {};
|
|
|
+ if (!WORKSHOP_ONLY && skills.length > 0) {
|
|
|
+ console.log('── Step 1: 上传 Skills 到七牛云 ──');
|
|
|
+
|
|
|
+ for (const skill of skills) {
|
|
|
+ process.stdout.write(` 📦 ${skill.name} ... `);
|
|
|
+ const skillResults = [];
|
|
|
+ const cdnUrls = {};
|
|
|
+
|
|
|
+ for (const file of skill.files) {
|
|
|
+ try {
|
|
|
+ const result = await uploadFile(file.localPath, file.cdnKey);
|
|
|
+ skillResults.push({ ...result, fileName: file.fileName, status: 'ok' });
|
|
|
+ cdnUrls[file.fileName] = result.url;
|
|
|
+ successCount++;
|
|
|
+ } catch (err) {
|
|
|
+ skillResults.push({ fileName: file.fileName, status: 'fail', error: err.message });
|
|
|
+ failCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const allOk = skillResults.every(r => r.status === 'ok');
|
|
|
+ console.log(allOk ? '✅' : '⚠️');
|
|
|
+ results.push({ name: skill.name, files: skillResults, meta: skill.meta, cdnUrls });
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('');
|
|
|
+ console.log(` Skills 上传完成!成功: ${successCount} 失败: ${failCount}`);
|
|
|
+ console.log('');
|
|
|
+ } else if (!WORKSHOP_ONLY) {
|
|
|
+ console.log('── Step 1: 跳过(无 Skills)──');
|
|
|
+ console.log('');
|
|
|
+ }
|
|
|
|
|
|
- for (const file of skill.files) {
|
|
|
+ // ── Step 1b: 上传 Workshop 文件到七牛 ──
|
|
|
+ let wsSuccessCount = 0;
|
|
|
+ let wsFailCount = 0;
|
|
|
+ const workshopResults = [];
|
|
|
+
|
|
|
+ if (!SKILLS_ONLY && workshopFiles.length > 0) {
|
|
|
+ console.log('── Step 1b: 上传 Workshop 文件到七牛云 ──');
|
|
|
+
|
|
|
+ for (const file of workshopFiles) {
|
|
|
+ process.stdout.write(` 📝 ${file.fileName} ... `);
|
|
|
try {
|
|
|
const result = await uploadFile(file.localPath, file.cdnKey);
|
|
|
- skillResults.push({ ...result, fileName: file.fileName, status: 'ok' });
|
|
|
- cdnUrls[file.fileName] = result.url;
|
|
|
- successCount++;
|
|
|
+ workshopResults.push({ ...result, fileName: file.fileName, type: file.type, status: 'ok' });
|
|
|
+ wsSuccessCount++;
|
|
|
+ console.log('✅');
|
|
|
} catch (err) {
|
|
|
- skillResults.push({ fileName: file.fileName, status: 'fail', error: err.message });
|
|
|
- failCount++;
|
|
|
+ workshopResults.push({ fileName: file.fileName, type: file.type, status: 'fail', error: err.message });
|
|
|
+ wsFailCount++;
|
|
|
+ console.log(`❌ ${err.message}`);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const allOk = skillResults.every(r => r.status === 'ok');
|
|
|
- console.log(allOk ? '✅' : '⚠️');
|
|
|
- results.push({ name: skill.name, files: skillResults, meta: skill.meta, cdnUrls });
|
|
|
+ console.log('');
|
|
|
+ console.log(` Workshop 上传完成!成功: ${wsSuccessCount} 失败: ${wsFailCount}`);
|
|
|
+ console.log('');
|
|
|
}
|
|
|
|
|
|
- console.log('');
|
|
|
- console.log(` 上传完成!成功: ${successCount} 失败: ${failCount}`);
|
|
|
- console.log('');
|
|
|
-
|
|
|
// ── Step 2: 写入 Parse Skill 表 ──
|
|
|
- console.log('── Step 2: 同步到 Parse 数据库 (Skill 表) ──');
|
|
|
- await ensureSkillClass();
|
|
|
let dbSuccess = 0;
|
|
|
let dbFail = 0;
|
|
|
|
|
|
- for (const entry of results) {
|
|
|
- if (Object.keys(entry.cdnUrls).length === 0) continue;
|
|
|
- process.stdout.write(` 💾 ${entry.name} ... `);
|
|
|
- try {
|
|
|
- // Parse 不允许嵌套 key 含 '.',将文件名中的 '.' 替换为 '_'
|
|
|
- const safeCdnUrls = {};
|
|
|
- for (const [k, v] of Object.entries(entry.cdnUrls)) {
|
|
|
- safeCdnUrls[k.replace(/\./g, '_')] = v;
|
|
|
+ if (!WORKSHOP_ONLY && results.length > 0) {
|
|
|
+ console.log('── Step 2: 同步到 Parse 数据库 (Skill 表) ──');
|
|
|
+ await ensureSkillClass();
|
|
|
+
|
|
|
+ for (const entry of results) {
|
|
|
+ if (Object.keys(entry.cdnUrls).length === 0) continue;
|
|
|
+ process.stdout.write(` 💾 ${entry.name} ... `);
|
|
|
+ try {
|
|
|
+ // Parse 不允许嵌套 key 含 '.',将文件名中的 '.' 替换为 '_'
|
|
|
+ const safeCdnUrls = {};
|
|
|
+ for (const [k, v] of Object.entries(entry.cdnUrls)) {
|
|
|
+ safeCdnUrls[k.replace(/\./g, '_')] = v;
|
|
|
+ }
|
|
|
+ const skillData = {
|
|
|
+ name: entry.meta.name,
|
|
|
+ displayName: entry.meta.displayName,
|
|
|
+ description: entry.meta.description,
|
|
|
+ category: entry.meta.category,
|
|
|
+ version: entry.meta.version,
|
|
|
+ author: entry.meta.author,
|
|
|
+ cdnUrls: safeCdnUrls,
|
|
|
+ cdnPrefix: `${CDN_DOMAIN}/${CDN_PREFIX}/${entry.name}`
|
|
|
+ };
|
|
|
+ const result = await upsertSkill(skillData);
|
|
|
+ console.log(`${result.action === 'created' ? '🆕 创建' : '🔄 更新'} (${result.objectId})`);
|
|
|
+ dbSuccess++;
|
|
|
+ } catch (err) {
|
|
|
+ console.log(`❌ ${err.message}`);
|
|
|
+ dbFail++;
|
|
|
}
|
|
|
- const skillData = {
|
|
|
- name: entry.meta.name,
|
|
|
- displayName: entry.meta.displayName,
|
|
|
- description: entry.meta.description,
|
|
|
- category: entry.meta.category,
|
|
|
- version: entry.meta.version,
|
|
|
- author: entry.meta.author,
|
|
|
- cdnUrls: safeCdnUrls,
|
|
|
- cdnPrefix: `${CDN_DOMAIN}/${CDN_PREFIX}/${entry.name}`
|
|
|
- };
|
|
|
- const result = await upsertSkill(skillData);
|
|
|
- console.log(`${result.action === 'created' ? '🆕 创建' : '🔄 更新'} (${result.objectId})`);
|
|
|
- dbSuccess++;
|
|
|
- } catch (err) {
|
|
|
- console.log(`❌ ${err.message}`);
|
|
|
- dbFail++;
|
|
|
}
|
|
|
+
|
|
|
+ console.log('');
|
|
|
+ console.log(` 数据库同步完成!成功: ${dbSuccess} 失败: ${dbFail}`);
|
|
|
+ console.log('');
|
|
|
}
|
|
|
|
|
|
- console.log('');
|
|
|
- console.log(` 数据库同步完成!成功: ${dbSuccess} 失败: ${dbFail}`);
|
|
|
- console.log('');
|
|
|
+ // ── Step 3: 生成清单 + README 并上传 ──
|
|
|
+ console.log('── Step 3: 生成清单 + README 并上传 ──');
|
|
|
+ const packageUrls = {};
|
|
|
+
|
|
|
+ // 上传每个技能包的 install.json
|
|
|
+ if (!WORKSHOP_ONLY && results.length > 0) {
|
|
|
+ const manifests = generatePackageManifests(results);
|
|
|
+ for (const [pkgId, manifest] of Object.entries(manifests)) {
|
|
|
+ try {
|
|
|
+ const key = `${CDN_PREFIX}/packages/${pkgId}.json`;
|
|
|
+ const buf = Buffer.from(JSON.stringify(manifest, null, 2), 'utf8');
|
|
|
+ const res = await uploadBuffer(buf, key);
|
|
|
+ packageUrls[pkgId] = res.url;
|
|
|
+ const pkg = SKILL_PACKAGES[pkgId];
|
|
|
+ const statusTag = pkg.status === 'testing' ? ' 🧪' : ' ✅';
|
|
|
+ console.log(` ${statusTag} ${pkg.label} (${manifest.skillCount}个技能): ${res.url}`);
|
|
|
+ } catch (err) {
|
|
|
+ console.log(` ❌ ${pkgId}.json 上传失败: ${err.message}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // ── Step 3: 生成 README.md + 技能包清单 并上传 ──
|
|
|
- console.log('── Step 3: 生成 README.md + 技能包清单 并上传 ──');
|
|
|
+ // 上传 workshop manifest
|
|
|
+ let workshopManifestUrl = '';
|
|
|
+ if (!SKILLS_ONLY && workshopResults.length > 0) {
|
|
|
+ try {
|
|
|
+ const wsManifest = generateWorkshopManifest(workshopResults);
|
|
|
+ const key = `${CDN_PREFIX}/packages/workshop.json`;
|
|
|
+ const buf = Buffer.from(JSON.stringify(wsManifest, null, 2), 'utf8');
|
|
|
+ const res = await uploadBuffer(buf, key);
|
|
|
+ workshopManifestUrl = res.url;
|
|
|
+ packageUrls['workshop'] = res.url;
|
|
|
+ const okCount = workshopResults.filter(r => r.status === 'ok').length;
|
|
|
+ console.log(` 🦐 VOC虾工作坊 (${okCount}个文件): ${res.url}`);
|
|
|
+ } catch (err) {
|
|
|
+ console.log(` ❌ workshop.json 上传失败: ${err.message}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 上传 README.md
|
|
|
let readmeUrl = '';
|
|
|
try {
|
|
|
- const readme = generateReadme(results);
|
|
|
+ const readme = generateReadme(results, workshopManifestUrl);
|
|
|
const readmeKey = `${CDN_PREFIX}/README.md`;
|
|
|
const readmeResult = await uploadBuffer(Buffer.from(readme, 'utf8'), readmeKey);
|
|
|
readmeUrl = readmeResult.url;
|
|
|
@@ -568,32 +800,20 @@ async function main() {
|
|
|
} catch (err) {
|
|
|
console.log(` ❌ README.md 上传失败: ${err.message}`);
|
|
|
}
|
|
|
-
|
|
|
- // 上传每个技能包的 install.json
|
|
|
- const manifests = generatePackageManifests(results);
|
|
|
- const packageUrls = {};
|
|
|
- for (const [pkgId, manifest] of Object.entries(manifests)) {
|
|
|
- try {
|
|
|
- const key = `${CDN_PREFIX}/packages/${pkgId}.json`;
|
|
|
- const buf = Buffer.from(JSON.stringify(manifest, null, 2), 'utf8');
|
|
|
- const res = await uploadBuffer(buf, key);
|
|
|
- packageUrls[pkgId] = res.url;
|
|
|
- const pkg = SKILL_PACKAGES[pkgId];
|
|
|
- const statusTag = pkg.status === 'testing' ? ' 🧪' : ' ✅';
|
|
|
- console.log(` ${statusTag} ${pkg.label} (${manifest.skillCount}个技能): ${res.url}`);
|
|
|
- } catch (err) {
|
|
|
- console.log(` ❌ ${pkgId}.json 上传失败: ${err.message}`);
|
|
|
- }
|
|
|
- }
|
|
|
console.log('');
|
|
|
|
|
|
// ── 输出 URL 汇总 ──
|
|
|
- console.log('📋 技能包下载地址:');
|
|
|
+ console.log('📋 下载地址:');
|
|
|
console.log('');
|
|
|
for (const [pkgId, url] of Object.entries(packageUrls)) {
|
|
|
- const pkg = SKILL_PACKAGES[pkgId];
|
|
|
- const statusTag = pkg.status === 'testing' ? '🧪' : '✅';
|
|
|
- console.log(` ${statusTag} ${pkg.label}`);
|
|
|
+ if (pkgId === 'workshop') {
|
|
|
+ console.log(` 🦐 VOC虾工作坊`);
|
|
|
+ } else {
|
|
|
+ const pkg = SKILL_PACKAGES[pkgId];
|
|
|
+ if (!pkg) continue;
|
|
|
+ const statusTag = pkg.status === 'testing' ? '🧪' : '✅';
|
|
|
+ console.log(` ${statusTag} ${pkg.label}`);
|
|
|
+ }
|
|
|
console.log(` 👉 ${url}`);
|
|
|
}
|
|
|
console.log('');
|
|
|
@@ -606,17 +826,27 @@ async function main() {
|
|
|
urlMap[entry.name] = entry.cdnUrls;
|
|
|
}
|
|
|
}
|
|
|
+ if (workshopResults.length > 0) {
|
|
|
+ urlMap['_workshop'] = {};
|
|
|
+ for (const item of workshopResults) {
|
|
|
+ if (item.status === 'ok') urlMap['_workshop'][item.fileName] = item.url;
|
|
|
+ }
|
|
|
+ }
|
|
|
const outputPath = path.resolve(__dirname, '..', 'skills-cdn-urls.json');
|
|
|
fs.writeFileSync(outputPath, JSON.stringify(urlMap, null, 2), 'utf8');
|
|
|
console.log(`💾 URL 列表已保存到: ${outputPath}`);
|
|
|
console.log('');
|
|
|
|
|
|
console.log('==================================================');
|
|
|
- console.log('📌 技能市场索引:');
|
|
|
+ console.log('📌 索引:');
|
|
|
console.log(` README: ${CDN_DOMAIN}/${CDN_PREFIX}/README.md`);
|
|
|
for (const [pkgId, url] of Object.entries(packageUrls)) {
|
|
|
- const pkg = SKILL_PACKAGES[pkgId];
|
|
|
- console.log(` ${pkg.emoji} ${pkg.label}: ${url}`);
|
|
|
+ if (pkgId === 'workshop') {
|
|
|
+ console.log(` 🦐 VOC虾工作坊: ${url}`);
|
|
|
+ } else {
|
|
|
+ const pkg = SKILL_PACKAGES[pkgId];
|
|
|
+ if (pkg) console.log(` ${pkg.emoji} ${pkg.label}: ${url}`);
|
|
|
+ }
|
|
|
}
|
|
|
console.log('==================================================');
|
|
|
}
|