Sfoglia il codice sorgente

feat: split skills into packages (voc-core/social-media/ai-creation), generate per-package install.json manifests

gangvy 5 mesi fa
parent
commit
4726f7467a

+ 145 - 0
docs/workshop-client-checklist.md

@@ -0,0 +1,145 @@
+# 📋 VOC虾工作坊 · 客户资料准备清单
+
+> 适用对象:品牌方(自有品牌/电商团队,年营收3000万+)
+> 目的:工作坊开始前,请品牌方按此清单准备资料,确保AI分析精准高效
+
+---
+
+## 一、必备资料(开营前必须提供)
+
+### 1. 品牌基础信息
+| 项目 | 说明 | 示例(健衡) |
+|------|------|-------------|
+| 品牌英文名 | Amazon店铺使用的英文品牌名 | JianHeng |
+| 品牌中文名 | 公司/品牌中文名称 | 健衡 |
+| 主营品类 | 核心产品品类(中文) | 健康护具 |
+| 品类英文关键词 | 2-3个核心搜索关键词 | knee brace, back support belt, posture corrector |
+| 主要销售站点 | Amazon哪个站点为主 | 美国站 / 欧洲站 / 日本站 |
+
+### 2. 核心产品信息
+| 项目 | 说明 | 数量要求 |
+|------|------|---------|
+| **自家主力ASIN** | 销量最好的3-5个产品的Amazon ASIN | 3-5个 |
+| **产品链接** | 对应的Amazon产品页面URL | 3-5个 |
+| **各ASIN月销量** | 近3个月平均月销(大致即可) | 有则提供 |
+| **各ASIN售价** | 当前售价 | 有则提供 |
+
+> 💡 ASIN在哪里找?打开Amazon产品页面,URL中 `/dp/` 后面的10位编码,如 `B0CJ5KXXXX`
+
+### 3. 竞品情报(已知的)
+| 项目 | 说明 | 数量要求 |
+|------|------|---------|
+| 已知主要竞品品牌名 | 你认为的直接竞争对手 | 3-5个 |
+| 竞品代表ASIN | 竞品的主力产品ASIN | 有多少给多少 |
+| 竞品链接 | 竞品产品页面URL | 有则提供 |
+
+> 如果不确定竞品是谁也没关系,AI会自动发现竞品
+
+### 4. 核心诉求(最重要❗)
+请品牌方回答以下问题(自由文本,越具体越好):
+
+**👉 你最想通过这次工作坊解决的3个问题是什么?**
+
+常见诉求示例:
+- "我们想推新品,不确定哪个方向有市场空间"
+- "现有产品差评集中在XX问题,想知道怎么改品"
+- "想了解用户真正在意什么功能,指导下一代产品开发"
+- "不确定定价是否合理,想看竞品定价策略"
+- "想知道品类还有没有增长空间,是否值得加大投入"
+- "社媒上消费者怎么讨论我们这个品类,有什么趋势"
+
+> 🎯 **承诺:解决一个就是惊喜,解决三个就是超预期**
+
+---
+
+## 二、加分资料(有则提供,大幅提升分析深度)
+
+### 5. 销售数据
+| 项目 | 说明 | 格式 |
+|------|------|------|
+| 近12个月销售趋势 | 各月销量/销售额 | Excel或截图 |
+| 各ASIN利润率 | 大致毛利水平 | 百分比即可 |
+| 广告数据 | ACOS、主要投放关键词 | 后台截图 |
+| BSR排名趋势 | 主力产品的BSR变化 | 截图 |
+
+### 6. 用户反馈积累
+| 项目 | 说明 |
+|------|------|
+| 客服常见问题 TOP10 | 买家最常问什么/最常投诉什么 |
+| 退货原因统计 | 退货率 + 主要退货原因 |
+| 自家产品已知问题 | 你已经知道的产品缺陷或用户痛点 |
+
+### 7. 社媒信息
+| 项目 | 说明 |
+|------|------|
+| 品牌TikTok账号 | 如果有官方TikTok |
+| 品牌抖音账号 | 如果有官方抖音 |
+| 品类相关话题标签 | 如 #kneebrace #backpain 等 |
+| 品牌小红书/Instagram | 其他社媒渠道 |
+
+### 8. 产品规划(如涉及新品/改品)
+| 项目 | 说明 |
+|------|------|
+| 在研/规划中的新品方向 | 简述即可 |
+| 改品目标 | 哪个现有产品要改?改什么方向? |
+| 供应链能力边界 | 哪些工艺/材料/成本是有限制的 |
+| 目标价格带 | 新品/改品期望的售价区间 |
+
+---
+
+## 三、资料提交格式
+
+### 推荐方式
+1. **填写在线表格**(工作坊组织方提供链接)
+2. **或发送到指定邮箱**,包含:
+   - 一份Excel/文档,包含上述第1-4项必备信息
+   - ASIN列表(文本即可)
+   - 品牌方最想解决的3个问题(文字描述)
+
+### 时间要求
+- **开营前3天**:提交必备资料(第1-4项)
+- **开营前1天**:提交加分资料(第5-8项,有多少给多少)
+
+---
+
+## 四、资料使用说明
+
+| 阶段 | 使用的资料 | 产出 |
+|------|-----------|------|
+| 阶段0:品牌建档 | 品牌信息 + ASIN + 竞品 + 核心诉求 | 品牌档案卡 |
+| 阶段1:品类全景 | 品类关键词 + 站点 | 品类健康度报告、市场集中度、价格带分布 |
+| 阶段2:品牌+竞品 | 自家ASIN + 竞品ASIN | 品牌六维画像、竞品对比矩阵、定价分析 |
+| 阶段3:评论VOC | 全部ASIN(自家+竞品) | 情感分析报告、关键词情感地图 |
+| 阶段4:深度分析 | 评论数据 + 社媒信息 | 痛点挖掘、用户画像、社媒趋势 |
+| 阶段5:行动提案 | 全部前序数据 + 产品规划 | 新品/改品提案、完整HTML报告 |
+
+---
+
+## 五、健衡案例参考(填写示例)
+
+```
+品牌英文名:JianHeng
+品牌中文名:健衡
+主营品类:健康护具
+品类英文关键词:knee brace, back support belt, posture corrector
+主要站点:美国站
+
+主力ASIN:
+1. B0XXXXXXXXX(护膝 - 月销约2000件 - $29.99)
+2. B0YYYYYYYYY(腰部支撑带 - 月销约1500件 - $34.99)
+3. B0ZZZZZZZZZ(矫姿带 - 月销约800件 - $24.99)
+
+已知竞品:
+1. Mueller Sports Medicine
+2. Copper Fit
+3. Modvel
+
+最想解决的3个问题:
+1. 护膝品类竞争激烈,想找到差异化新品方向
+2. 腰部支撑带差评较多,想了解用户真正的痛点来改品
+3. 想知道健康护具品类在社媒上的消费趋势,指导营销方向
+```
+
+---
+
+> 📌 **给品牌方的话**:资料越完整,AI分析越精准。但不必追求完美——缺什么虾(AI情报员)会在工作坊现场问你,边做边补也完全可以。

+ 183 - 45
scripts/deploy/upload-skills.js

@@ -43,6 +43,47 @@ const DEPLOYED_SKILLS_DIR = path.join(process.env.USERPROFILE || process.env.HOM
 // 要上传的技能目录列表(如果为空,则上传所有 skills)
 const SKILL_FILTER = []; // 例如 ['tiktok-video-search', 'jimeng-img-v4'] 指定只上传某些
 
+// ============================================
+// 技能包分组定义
+// ============================================
+const SKILL_PACKAGES = {
+    'voc-core': {
+        label: 'VOC 核心套件',
+        description: 'Amazon 品类洞察 + 评论分析 + 竞品分析 + 数据合成 + 社媒VOC + 工作坊',
+        categories: ['voc', 'review-analysis', 'competitor-analysis', 'synthesis', 'social-voc', 'workshop'],
+        status: 'stable',   // stable | testing | hidden
+        emoji: '🔍'
+    },
+    'social-media': {
+        label: '社媒数据套件',
+        description: 'TikTok / Instagram / 抖音数据采集',
+        categories: ['social-media', 'douyin'],
+        status: 'stable',
+        emoji: '📱'
+    },
+    'ai-creation': {
+        label: 'AI 创作套件',
+        description: '即梦AI图片视频生成 + 一键成片(测试中)',
+        categories: ['jimeng', 'video-creation'],
+        status: 'testing',
+        emoji: '🎨'
+    },
+    '_other': {
+        label: '其他/测试',
+        description: '测试技能,不对外提供',
+        categories: ['other'],
+        status: 'hidden',
+        emoji: '🧪'
+    }
+};
+
+function getPackageForCategory(category) {
+    for (const [pkgId, pkg] of Object.entries(SKILL_PACKAGES)) {
+        if (pkg.categories.includes(category || 'other')) return pkgId;
+    }
+    return '_other';
+}
+
 // ============================================
 // 七牛上传
 // ============================================
@@ -281,40 +322,85 @@ function collectSkills() {
 
 function generateReadme(skillEntries) {
     const now = new Date().toISOString().replace('T', ' ').substring(0, 19);
+
+    // 按技能包分组
+    const pkgMap = {};
+    for (const entry of skillEntries) {
+        const pkgId = getPackageForCategory(entry.meta.category);
+        if (!pkgMap[pkgId]) pkgMap[pkgId] = [];
+        pkgMap[pkgId].push(entry);
+    }
+
+    // 统计可见技能数
+    let visibleCount = 0;
+    for (const [pkgId, entries] of Object.entries(pkgMap)) {
+        if (SKILL_PACKAGES[pkgId]?.status !== 'hidden') visibleCount += entries.length;
+    }
+
     const lines = [
-        '# OpenClaw Skills 目录',
+        '# OpenClaw Skills 技能市场',
         '',
-        `> 自动生成于 ${now},共 ${skillEntries.length} 个技能`,
+        `> 自动生成于 ${now},共 ${visibleCount} 个技能(分 ${Object.keys(pkgMap).filter(k => SKILL_PACKAGES[k]?.status !== 'hidden').length} 个套件)`,
         '',
-        '---',
-        ''
     ];
 
-    // 按 category 分组
-    const grouped = {};
-    for (const entry of skillEntries) {
-        const cat = entry.meta.category || '其他';
-        if (!grouped[cat]) grouped[cat] = [];
-        grouped[cat].push(entry);
+    // 套件总览表
+    lines.push('## 📦 套件总览');
+    lines.push('');
+    lines.push('| 套件 | 说明 | 技能数 | 状态 | 安装清单 |');
+    lines.push('|------|------|--------|------|---------|');
+    for (const [pkgId, pkg] of Object.entries(SKILL_PACKAGES)) {
+        if (pkg.status === 'hidden') continue;
+        const count = (pkgMap[pkgId] || []).length;
+        const statusBadge = pkg.status === 'stable' ? '✅ 正式' : '🧪 测试中';
+        const installUrl = `${CDN_DOMAIN}/${CDN_PREFIX}/packages/${pkgId}.json`;
+        lines.push(`| ${pkg.emoji} **${pkg.label}** | ${pkg.description} | ${count} | ${statusBadge} | [${pkgId}.json](${installUrl}) |`);
     }
+    lines.push('');
+    lines.push('---');
+    lines.push('');
+
+    // 每个套件详细内容
+    for (const [pkgId, pkg] of Object.entries(SKILL_PACKAGES)) {
+        if (pkg.status === 'hidden') continue;
+        const entries = pkgMap[pkgId] || [];
+        if (entries.length === 0) continue;
 
-    for (const [cat, entries] of Object.entries(grouped).sort()) {
-        lines.push(`## ${cat}`);
+        const statusNote = pkg.status === 'testing' ? ' ⚠️ 测试中,功能可能变更' : '';
+        lines.push(`## ${pkg.emoji} ${pkg.label}${statusNote}`);
+        lines.push('');
+        lines.push(`> ${pkg.description}`);
+        lines.push('>');
+        const installUrl = `${CDN_DOMAIN}/${CDN_PREFIX}/packages/${pkgId}.json`;
+        lines.push(`> 📥 一键安装清单:[\`${pkgId}.json\`](${installUrl})`);
         lines.push('');
-        lines.push('| 技能名 | 说明 | 版本 | 下载 |');
-        lines.push('|--------|------|------|------|');
 
+        // 按 category 子分组
+        const catMap = {};
         for (const entry of entries) {
-            const m = entry.meta;
-            const desc = m.description.length > 60 ? m.description.substring(0, 60) + '...' : m.description;
-            const skillMdUrl = entry.cdnUrls['SKILL.md'] || '';
-            const apiCfgUrl = entry.cdnUrls['api-config.json'] || '';
-            const links = [];
-            if (skillMdUrl) links.push(`[SKILL.md](${skillMdUrl})`);
-            if (apiCfgUrl) links.push(`[api-config.json](${apiCfgUrl})`);
-            lines.push(`| **${m.displayName}** (\`${m.name}\`) | ${desc} | ${m.version} | ${links.join(' / ')} |`);
+            const cat = entry.meta.category || '其他';
+            if (!catMap[cat]) catMap[cat] = [];
+            catMap[cat].push(entry);
+        }
+
+        for (const [cat, catEntries] of Object.entries(catMap).sort()) {
+            lines.push(`### ${cat}`);
+            lines.push('');
+            lines.push('| 技能名 | 说明 | 版本 | 下载 |');
+            lines.push('|--------|------|------|------|');
+
+            for (const entry of catEntries) {
+                const m = entry.meta;
+                const desc = m.description.length > 60 ? m.description.substring(0, 60) + '...' : m.description;
+                const skillMdUrl = entry.cdnUrls['SKILL.md'] || '';
+                const apiCfgUrl = entry.cdnUrls['api-config.json'] || '';
+                const links = [];
+                if (skillMdUrl) links.push(`[SKILL.md](${skillMdUrl})`);
+                if (apiCfgUrl) links.push(`[api-config.json](${apiCfgUrl})`);
+                lines.push(`| **${m.displayName}** (\`${m.name}\`) | ${desc} | ${m.version} | ${links.join(' / ')} |`);
+            }
+            lines.push('');
         }
-        lines.push('');
     }
 
     lines.push('---');
@@ -327,10 +413,48 @@ function generateReadme(skillEntries) {
     lines.push('');
     lines.push('下载后放入 `~/.openclaw/skills/<skill-name>/` 即可被 OpenClaw Agent 识别和调用。');
     lines.push('');
+    lines.push('### 按套件安装');
+    lines.push('');
+    lines.push('下载对应套件的 `.json` 安装清单,包含该套件所有技能的 CDN 下载地址。');
+    lines.push('');
 
     return lines.join('\n');
 }
 
+// 生成每个技能包的 install.json 清单
+function generatePackageManifests(skillEntries) {
+    const pkgMap = {};
+    for (const entry of skillEntries) {
+        const pkgId = getPackageForCategory(entry.meta.category);
+        if (!pkgMap[pkgId]) pkgMap[pkgId] = [];
+        pkgMap[pkgId].push(entry);
+    }
+
+    const manifests = {};
+    for (const [pkgId, pkg] of Object.entries(SKILL_PACKAGES)) {
+        if (pkg.status === 'hidden') continue;
+        const entries = pkgMap[pkgId] || [];
+        if (entries.length === 0) continue;
+
+        manifests[pkgId] = {
+            package: pkgId,
+            label: pkg.label,
+            description: pkg.description,
+            status: pkg.status,
+            skillCount: entries.length,
+            generatedAt: new Date().toISOString(),
+            skills: entries.map(e => ({
+                name: e.meta.name,
+                displayName: e.meta.displayName,
+                category: e.meta.category,
+                version: e.meta.version,
+                files: e.cdnUrls
+            }))
+        };
+    }
+    return manifests;
+}
+
 // ============================================
 // Main
 // ============================================
@@ -432,8 +556,8 @@ async function main() {
     console.log(`   数据库同步完成!成功: ${dbSuccess}  失败: ${dbFail}`);
     console.log('');
 
-    // ── Step 3: 生成 README.md 并上传(包含在技能文件夹内)──
-    console.log('── Step 3: 生成 README.md 并上传 ──');
+    // ── Step 3: 生成 README.md + 技能包清单 并上传 ──
+    console.log('── Step 3: 生成 README.md + 技能包清单 并上传 ──');
     let readmeUrl = '';
     try {
         const readme = generateReadme(results);
@@ -444,42 +568,56 @@ async function main() {
     } catch (err) {
         console.log(`  ❌ README.md 上传失败: ${err.message}`);
     }
-    console.log('');
 
-    // ── 输出 URL 汇总(README 包含在内)──
-    console.log('📋 CDN 下载地址列表:');
+    // 上传每个技能包的 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('');
 
-    const urlMap = {};
-
-    // README 放在最前面
-    if (readmeUrl) {
-        urlMap['_README'] = { 'README.md': readmeUrl };
-        console.log(`  README.md`);
-        console.log(`    👉 ${readmeUrl}`);
+    // ── 输出 URL 汇总 ──
+    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}`);
+        console.log(`    👉 ${url}`);
     }
+    console.log('');
 
+    // 保存 URL 列表到文件(按技能包分组)
+    const urlMap = { _packages: packageUrls };
+    if (readmeUrl) urlMap['_README'] = readmeUrl;
     for (const entry of results) {
         if (Object.keys(entry.cdnUrls).length > 0) {
             urlMap[entry.name] = entry.cdnUrls;
-            for (const [fn, url] of Object.entries(entry.cdnUrls)) {
-                console.log(`  ${entry.name}/${fn}`);
-                console.log(`    👉 ${url}`);
-            }
         }
     }
-
-    // 保存 URL 列表到文件
     const outputPath = path.resolve(__dirname, '..', 'skills-cdn-urls.json');
     fs.writeFileSync(outputPath, JSON.stringify(urlMap, null, 2), 'utf8');
-    console.log('');
     console.log(`💾 URL 列表已保存到: ${outputPath}`);
     console.log('');
 
     console.log('==================================================');
-    console.log('📌 技能索引(README 已包含在文件夹内):');
-    console.log(`  README: ${CDN_DOMAIN}/${CDN_PREFIX}/README.md`);
-    console.log(`  CDN:    ${CDN_DOMAIN}/${CDN_PREFIX}/`);
+    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}`);
+    }
     console.log('==================================================');
 }
 

+ 5 - 2
scripts/skills-cdn-urls.json

@@ -1,7 +1,10 @@
 {
-  "_README": {
-    "README.md": "https://repos.fmode.cn/x/openclaw-skills/README.md"
+  "_packages": {
+    "voc-core": "https://repos.fmode.cn/x/openclaw-skills/packages/voc-core.json",
+    "social-media": "https://repos.fmode.cn/x/openclaw-skills/packages/social-media.json",
+    "ai-creation": "https://repos.fmode.cn/x/openclaw-skills/packages/ai-creation.json"
   },
+  "_README": "https://repos.fmode.cn/x/openclaw-skills/README.md",
   "asin-reverse-keywords": {
     "SKILL.md": "https://repos.fmode.cn/x/openclaw-skills/asin-reverse-keywords/SKILL.md",
     "api-config.json": "https://repos.fmode.cn/x/openclaw-skills/asin-reverse-keywords/api-config.json"