瀏覽代碼

fix: 清除内部git地址与npx外部依赖 + 补齐缺失脚本 + 发布体检

问题:
1. fmode-deliverable/video-editing README 写内部仓库 git clone(外部无权限)
2. fmode-image/product-lab 脚本在仓库根 bin/lib,未打进包(空壳)
3. 多个技能教用户 npx 从 npm 拉包(供应链风险+冗余)

修复:
- image/product-lab 配置 extraDirs 带 bin/lib/scripts
- 全部 npx 外部依赖 → 包内脚本 <技能目录>/bin|scripts/
- 内部 git clone → '已随包分发'
- package.json 移除内部 repository 字段

防呆:pack-release 新增内容体检(内部git/npx/内网IP/明文凭据),命中即中止打包

验收:内部git 0 / npx依赖 0 / 内网IP 0 / 13技能全带脚本
liuyuyang 1 周之前
父節點
當前提交
1a65597dcc

+ 5 - 8
README.md

@@ -16,18 +16,15 @@ Fmode 总技能库是一个**元技能 / Meta Skill**:本身不执行业务,
 
 ## 快速开始
 
-### 方式 1:仓库安装(Gogs / GitHub)
+安装完成后,在技能目录内运行安装器,把全部子技能分发到宿主技能目录:
 
 ```bash
-git clone https://git.fmode.cn/fmode/fmode-skills.git
-cd fmode-skills
-node scripts/installer.mjs --all        # 分发全部子技能到宿主技能目录
-node scripts/installer.mjs --check      # 检查现状(只读)
+cd <技能安装目录>/fmode-harness      # SkillHub 安装后的目录
+node scripts/installer.mjs --all     # 分发全部子技能到宿主技能目录
+node scripts/installer.mjs --check   # 检查现状(只读)
 ```
 
-### 方式 2:skillhub 检索安装
-
-在 skillhub.cn 检索 `fmode-skills`,安装后运行仓库内 installer。
+安装器会自动检测宿主(WorkBuddy / FmodeCode / FmodeAgent)并分发到对应目录。
 
 ## 安装器行为
 

+ 1 - 1
dist/skills-index.json

@@ -1,7 +1,7 @@
 {
   "name": "fmode-skills",
   "version": "1.2.0",
-  "builtAt": "2026-10-31T03:21:55.534Z",
+  "builtAt": "2026-10-31T09:32:18.865Z",
   "skills": [
     {
       "name": "fmode-storage",

+ 7 - 7
dist/skills/fmode-code-lit/README.md

@@ -33,28 +33,28 @@
 
 ```bash
 # 装到当前项目的 .claude/skills/(随项目走,推荐)
-npx --yes @fmode/skill-code-lit@latest workspace
+node <技能目录>/scripts/scaffold.mjs
 
 # 或装到用户级 ~/.claude/skills/(全局)
-npx --yes @fmode/skill-code-lit@latest install
+node <技能目录>/scripts/install.mjs
 
 # 把前端规则写入项目 ./.claude/rules/
-npx --yes @fmode/skill-code-lit@latest rules .
+node <技能目录>/scripts/install-rules.mjs .
 ```
 
 ### 2) 新建无构建 Lit 项目
 
 ```bash
-npx --yes @fmode/skill-code-lit@latest scaffold ./my-admin --project my-admin
+node <技能目录>/scripts/scaffold.mjs ./my-admin --project my-admin
 cd my-admin && npx serve src      # 本地预览
 ```
 
 ### 3) 生成组件 / 服务 / 页面
 
 ```bash
-npx --yes @fmode/skill-code-lit@latest new component user-card
-npx --yes @fmode/skill-code-lit@latest new service auth-service
-npx --yes @fmode/skill-code-lit@latest new page page-order-list
+node <技能目录>/scripts/new.mjs component user-card
+node <技能目录>/scripts/new.mjs service auth-service
+node <技能目录>/scripts/new.mjs page page-order-list
 ```
 
 ### 4) 部署

+ 6 - 6
dist/skills/fmode-code-lit/bin/fmode-code-lit.js

@@ -2,12 +2,12 @@
 /**
  * fmode-code-lit CLI
  * 用法:
- *   npx @fmode/skill-code-lit install      # 装技能到 ~/.claude/skills/(默认)
- *   npx @fmode/skill-code-lit workspace    # 装技能到 ./.claude/skills/
- *   npx @fmode/skill-code-lit check        # 校验安装
- *   npx @fmode/skill-code-lit scaffold <dir> --project <name>   # 生成无构建 Lit 项目骨架
- *   npx @fmode/skill-code-lit new <component|service|page> <name>
- *   npx @fmode/skill-code-lit rules [dir]  # 把前端规则装入目标项目 ./.claude/rules/
+ *   node <技能目录>/scripts/install.mjs install      # 装技能到 ~/.claude/skills/(默认)
+ *   node <技能目录>/scripts/install.mjs workspace    # 装技能到 ./.claude/skills/
+ *   node <技能目录>/scripts/install.mjs check        # 校验安装
+ *   node <技能目录>/scripts/install.mjs scaffold <dir> --project <name>   # 生成无构建 Lit 项目骨架
+ *   node <技能目录>/scripts/install.mjs new <component|service|page> <name>
+ *   node <技能目录>/scripts/install.mjs rules [dir]  # 把前端规则装入目标项目 ./.claude/rules/
  */
 const { spawnSync } = require('node:child_process');
 const path = require('node:path');

+ 3 - 3
dist/skills/fmode-code-lit/skill-package-manifest.json

@@ -9,10 +9,10 @@
   "entrySkill": "fmode-code-lit",
   "npmPackage": "@fmode/skill-code-lit",
   "smokeCommand": "node scripts/smoke.mjs",
-  "installCommand": "npx @fmode/skill-code-lit@latest install",
-  "workspaceInstallCommand": "npx @fmode/skill-code-lit@latest workspace",
+  "installCommand": "node <技能目录>/scripts/install.mjs install",
+  "workspaceInstallCommand": "node <技能目录>/scripts/install.mjs workspace",
   "workspaceSkillPath": ".claude/skills/fmode-code-lit/SKILL.md",
   "globalSkillPath": "~/.claude/skills/fmode-code-lit/SKILL.md",
-  "installHint": "工作区安装:npx @fmode/skill-code-lit@latest workspace,写入 ./.claude/skills/fmode-code-lit/。用户级安装:npx @fmode/skill-code-lit@latest install,写入 ~/.claude/skills/fmode-code-lit/。随后可用 npx @fmode/skill-code-lit@latest scaffold <dir> --project <name> 生成无构建 Lit 项目骨架。规则细则见 fmode-code-lit 规则仓 .claude/rules/。",
+  "installHint": "工作区安装:node <技能目录>/scripts/install.mjs workspace,写入 ./.claude/skills/fmode-code-lit/。用户级安装:node <技能目录>/scripts/install.mjs install,写入 ~/.claude/skills/fmode-code-lit/。随后可用 node <技能目录>/scripts/install.mjs scaffold <dir> --project <name> 生成无构建 Lit 项目骨架。规则细则见 fmode-code-lit 规则仓 .claude/rules/。",
   "summary": "数字人开发基础规则技能:Lit 原生 ESM 无构建前端(含多页面 light DOM 五条死线)"
 }

+ 7 - 7
dist/skills/fmode-deliverable/SKILL.md

@@ -36,15 +36,15 @@ keywords: [deliverable, reporting, bootstrap]
 
 ## 二、安装
 
-```bash
-# npm 安装
-npm install -g skill-deliverable
+本技能已随包分发,**无需额外安装**。技能目录自带全部脚本:
 
-# 或 FmodeAgent / FmodeAgent 技能加载
-hermes skill install skill-deliverable
+```bash
+# 技能根目录(安装后的实际位置,二选一)
+#   ~/.claude/skills/fmode-deliverable/
+#   ~/.workbuddy/skills/fmode-deliverable/
 
-# 或直接克隆
-git clone https://git.fmode.cn/fmode/skill-deliverable.git
+# 直接调用包内脚本
+node <技能目录>/scripts/<脚本名>.mjs --help
 ```
 
 ## 三、CLI 用法

+ 1 - 1
dist/skills/fmode-deliverable/docs/deployment-guide.md

@@ -76,7 +76,7 @@ if [ -z "$FMODE_SESSION_TOKEN" ] && [ -f "$HOME/.fmode/config/user.json" ]; then
 fi
 
 # 运行检查
-npx --yes skill-deliverable@latest check
+node <技能目录>/bin/skill-deliverable.mjs check
 ```
 
 ---

+ 1 - 5
dist/skills/fmode-deliverable/package.json

@@ -51,9 +51,5 @@
   ],
   "author": "Yuyang001 (FmodeAgent)",
   "license": "MPL-2.0",
-  "repository": {
-    "type": "git",
-    "url": "git+https://git.fmode.cn/fmode/skill-deliverable.git"
-  },
   "dependencies": {}
-}
+}

+ 2 - 2
dist/skills/fmode-heterarchy/README.md

@@ -73,8 +73,8 @@ console.log(SKILL_NAME, VERSION);
 ### CLI
 
 ```bash
-npx --yes @fmode/skill-heterarchy@latest workspace   # 安装到当前工作区
-npx --yes @fmode/skill-heterarchy@latest install     # 安装到全局技能目录
+node <技能目录>/bin/fmode-skill.mjs workspace   # 安装到当前工作区
+node <技能目录>/bin/fmode-skill.mjs install     # 安装到全局技能目录
 ```
 
 > ⚠️ **ESM only**:本技能不提供 CommonJS 入口。需要 CJS 场景请用动态 `import()`:

+ 1 - 1
dist/skills/fmode-heterarchy/skill-package-manifest.json

@@ -8,5 +8,5 @@
       "path": "skills/skill-heterarchy/SKILL.md"
     }
   ],
-  "install": "npx --yes skill-heterarchy@latest workspace"
+  "install": "node <技能目录>/bin/fmode-skill.mjs workspace"
 }

+ 6 - 6
dist/skills/fmode-image/SKILL.md

@@ -31,22 +31,22 @@ npm install -g skill-image
 ## 快速使用
 ```bash
 # 应用界面(推荐!)
-npx skill-image --app "标题'内容合规系统'左侧导航5项,顶部指标卡3张" app-demo
+node <技能目录>/bin/skill-image.js --app "标题'内容合规系统'左侧导航5项,顶部指标卡3张" app-demo
 
 # 架构图
-npx skill-image --arch "Harness五层引擎,从上到下数据流" harness
+node <技能目录>/bin/skill-image.js --arch "Harness五层引擎,从上到下数据流" harness
 
 # 产品设计图(硬件/工业产品)
-npx skill-image --product "一款智能水杯的三视图+爆炸图组合,白底,科技感渲染" cup-design
+node <技能目录>/bin/skill-image.js --product "一款智能水杯的三视图+爆炸图组合,白底,科技感渲染" cup-design
 
 # 产品爆炸图(零件分解)
-npx skill-image --explode "智能手表爆炸图:表盘/表圈/电池/主板/表带分层分解" watch-explode
+node <技能目录>/bin/skill-image.js --explode "智能手表爆炸图:表盘/表圈/电池/主板/表带分层分解" watch-explode
 
 # 场景插图(省40%成本)
-npx skill-image --scene "法务团队深夜加班堆满稿件" scene
+node <技能目录>/bin/skill-image.js --scene "法务团队深夜加班堆满稿件" scene
 
 # 整页PPT
-npx skill-image --slide "封面:内容营销的超级预审Agent" cover
+node <技能目录>/bin/skill-image.js --slide "封面:内容营销的超级预审Agent" cover
 ```
 
 ## 输出控制

+ 192 - 0
dist/skills/fmode-image/bin/skill-image.js

@@ -0,0 +1,192 @@
+#!/usr/bin/env node
+// Copyright (c) 未来飞马
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Trademark Notice:
+// The MPL-2.0 license grants copyright permissions for source code only.
+// It does NOT grant any rights to use trademarks including "未来飞马",
+// "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步".
+// Any use of these trademarks requires separate written permission.
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { spawnSync } = require('child_process');
+
+const SKILL_NAME = 'skill-image';
+const SOURCE_ROOT = path.resolve(__dirname, '..');
+const SKILL_SOURCE = path.join(SOURCE_ROOT, 'skills', SKILL_NAME);
+const LIB = path.join(SOURCE_ROOT, 'lib', 'skill-image.mjs');
+const WORKSPACE_ROOT = process.cwd();
+const GLOBAL_TARGET = path.join(os.homedir(), '.claude', 'skills', SKILL_NAME);
+const WORKSPACE_TARGET = path.join(WORKSPACE_ROOT, '.claude', 'skills', SKILL_NAME);
+const WORKSPACE_SKILLS_ROOT = path.join(WORKSPACE_ROOT, '.claude', 'skills');
+const GLOBAL_SKILLS_ROOT = path.join(os.homedir(), '.claude', 'skills');
+
+const SUBCOMMANDS = ['install', 'install-workspace', 'workspace', 'check', 'path', 'smoke', 'help'];
+
+function expandHome(value) {
+  return String(value || '').replace(/^~(?=$|[\\/])/, os.homedir());
+}
+
+function parseArgs(argv) {
+  const first = argv[0];
+  if (!first || !SUBCOMMANDS.includes(first)) {
+    // No subcommand: passthrough to the image generator (--arch/--app/--scene/...).
+    return { command: 'generate', passthrough: argv };
+  }
+  const args = { command: first, target: GLOBAL_TARGET, smoke: false, force: false, help: false };
+  if (first === 'workspace' || first === 'install-workspace') {
+    args.command = 'install';
+    args.target = WORKSPACE_TARGET;
+  }
+  for (let i = 1; i < argv.length; i++) {
+    const token = argv[i];
+    if (token === '--target' && argv[i + 1]) args.target = argv[++i];
+    else if (token.startsWith('--target=')) args.target = token.slice('--target='.length);
+    else if (token === '--workspace') args.target = WORKSPACE_TARGET;
+    else if (token === '--global') args.target = GLOBAL_TARGET;
+    else if (token === '--smoke') args.smoke = true;
+    else if (token === '--force') args.force = true;
+    else if (token === '--help' || token === '-h') args.help = true;
+  }
+  args.target = path.resolve(expandHome(args.target));
+  return args;
+}
+
+function printHelp() {
+  console.log([
+    'skill-image — AI 图像生成器 + FmodeCode / FmodeCode 技能安装器',
+    '',
+    'Usage:',
+    '  node <技能目录>/bin/skill-image.js --arch    "prompt" [name]   # 架构图 ¥0.5',
+    '  node <技能目录>/bin/skill-image.js --app     "prompt" [name]   # 应用界面 ¥0.5',
+    '  node <技能目录>/bin/skill-image.js --explode "prompt" [name]   # 爆炸图 ¥0.5',
+    '  node <技能目录>/bin/skill-image.js --product "prompt" [name]   # 产品设计图 ¥0.5',
+    '  node <技能目录>/bin/skill-image.js --scene   "prompt" [name]   # 场景插图 ¥0.3',
+    '  node <技能目录>/bin/skill-image.js --detail  "prompt" [name]   # 细节图 ¥0.3',
+    '  node <技能目录>/bin/skill-image.js --slide   "prompt" [name]   # 整页PPT ¥0.5',
+    '',
+    '  node <技能目录>/bin/skill-image.js workspace [--smoke]  # install into ./.claude/skills/skill-image',
+    '  node <技能目录>/bin/skill-image.js install [--smoke]    # install into ~/.claude/skills/skill-image',
+    '  node <技能目录>/bin/skill-image.js install --target <dir> [--force]',
+    '  node <技能目录>/bin/skill-image.js check',
+    '  node <技能目录>/bin/skill-image.js smoke',
+    '  node <技能目录>/bin/skill-image.js path',
+    '',
+    'Options:',
+    '  --workspace      Install into ./.claude/skills/skill-image',
+    '  --global         Install into ~/.claude/skills/skill-image (default)',
+    '  --target <dir>   Install into a custom directory',
+    '  --force          Allow overwriting a custom target',
+    '  --smoke          Run smoke checks after install',
+    '  --help, -h       Show help'
+  ].join('\n'));
+}
+
+function ensureDir(dirPath) { fs.mkdirSync(dirPath, { recursive: true }); }
+
+function isInside(parentDir, childDir) {
+  const relative = path.relative(path.resolve(parentDir), path.resolve(childDir));
+  return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
+}
+
+function canOverwriteTarget(targetDir, force) {
+  return force
+    || path.resolve(targetDir) === path.resolve(GLOBAL_TARGET)
+    || isInside(WORKSPACE_SKILLS_ROOT, targetDir)
+    || isInside(GLOBAL_SKILLS_ROOT, targetDir);
+}
+
+function copyDirRecursive(source, destination) {
+  const stat = fs.statSync(source);
+  if (stat.isDirectory()) {
+    ensureDir(destination);
+    for (const child of fs.readdirSync(source)) {
+      if (child === 'node_modules' || child === 'outputs' || child === '.git') continue;
+      copyDirRecursive(path.join(source, child), path.join(destination, child));
+    }
+    return;
+  }
+  ensureDir(path.dirname(destination));
+  fs.copyFileSync(source, destination);
+}
+
+function installSkill(target, force) {
+  if (!fs.existsSync(SKILL_SOURCE)) {
+    throw new Error(`Skill source missing: ${SKILL_SOURCE}`);
+  }
+  if (fs.existsSync(target)) {
+    if (!canOverwriteTarget(target, force)) {
+      throw new Error(`Refusing to overwrite custom target without --force: ${target}`);
+    }
+    fs.rmSync(target, { recursive: true, force: true });
+  }
+  ensureDir(target);
+  copyDirRecursive(SKILL_SOURCE, target);
+}
+
+function checkSkill(target) {
+  const required = ['SKILL.md'];
+  const missing = required.filter(entry => !fs.existsSync(path.join(target, entry)));
+  if (missing.length) {
+    throw new Error(`Install target is missing required files: ${missing.join(', ')}`);
+  }
+  return { status: 'ok', skill: SKILL_NAME, target, required };
+}
+
+function runSmoke() {
+  const result = spawnSync(process.execPath, ['scripts/smoke.js'], { cwd: SOURCE_ROOT, stdio: 'inherit', shell: false });
+  if (result.status !== 0) throw new Error('smoke failed');
+}
+
+function printNextSteps(target) {
+  const workspaceMode = isInside(WORKSPACE_SKILLS_ROOT, target);
+  console.log('');
+  console.log('Install complete.');
+  console.log(`Skill installed at: ${target}`);
+  console.log('');
+  console.log(workspaceMode
+    ? 'Project-level skill is ready. Restart the VSCode FmodeCode / FmodeCode session if it was open.'
+    : 'User-level skill is ready for all FmodeCode / FmodeCode workspaces.');
+  console.log('');
+  console.log('API Key: set FMODE_API_KEY, or ~/.fmode/config.json -> api_key / fmodeApiToken.');
+  console.log('');
+  console.log('Try this prompt in FmodeCode / FmodeCode:');
+  console.log('  用 skill-image 生成一张"内容合规系统"的架构图,输出到当前目录。');
+}
+
+function generate(argv) {
+  const result = spawnSync(process.execPath, [LIB, ...argv], { stdio: 'inherit' });
+  process.exit(result.status ?? 0);
+}
+
+function main() {
+  const args = parseArgs(process.argv.slice(2));
+  if (args.command === 'generate') {
+    if (!args.passthrough.length || args.passthrough[0] === '--help' || args.passthrough[0] === '-h') {
+      printHelp();
+      process.exit(0);
+    }
+    generate(args.passthrough);
+    return;
+  }
+  if (args.help || args.command === 'help') { printHelp(); return; }
+  if (args.command === 'path') { console.log(args.target); return; }
+  if (args.command === 'install') {
+    installSkill(args.target, args.force);
+    console.log(JSON.stringify(checkSkill(args.target), null, 2));
+    if (args.smoke) runSmoke();
+    printNextSteps(args.target);
+    return;
+  }
+  if (args.command === 'check') { console.log(JSON.stringify(checkSkill(args.target), null, 2)); return; }
+  if (args.command === 'smoke') { runSmoke(); return; }
+  printHelp();
+  process.exitCode = 1;
+}
+
+try { main(); }
+catch (error) { console.error(`skill-image failed: ${error.message}`); process.exit(1); }

+ 182 - 0
dist/skills/fmode-image/lib/skill-image.mjs

@@ -0,0 +1,182 @@
+// Copyright (c) 未来飞马
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Trademark Notice:
+// The MPL-2.0 license grants copyright permissions for source code only.
+// It does NOT grant any rights to use trademarks including "未来飞马",
+// "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步".
+// Any use of these trademarks requires separate written permission.
+// skill-image v0.2.1 — 纯ESM零依赖版
+// 架构图/应用界面/场景插图/整页PPT生成器
+
+import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
+import { homedir } from 'node:os';
+import { join, dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+// ===== 模式注册表 =====
+export const MODES = {
+  '--arch': {
+    prefix: '纯白背景扁平风格架构图。',
+    suffix: '简洁扁平商务风格,纯白背景适合PPT。',
+    size: '1792x1024', cost: 0.5, desc: '架构/分层/闭环图',
+  },
+  '--app': {
+    prefix: '现代SaaS应用界面截图,浅色主题。',
+    suffix: '界面细节清晰锐利,中文界面文字,像真实在运行的产品。',
+    size: '1792x1024', cost: 0.5, desc: '产品界面(表现力最强)',
+  },
+  '--explode': {
+    prefix: '技术爆炸图,内部分层剖开并标注数据流向。',
+    suffix: '扁平科技风格,纯白背景,层级与流向清晰。',
+    size: '1792x1024', cost: 0.5, desc: '内部运转/拆解图',
+  },
+  '--product': {
+    prefix: '产品设计效果图,纯白背景,多角度组合渲染。',
+    suffix: '工业设计质感,材质细腻,白底适合PPT,写实渲染。',
+    size: '1792x1024', cost: 0.5, desc: '产品设计图(硬件/工业)',
+  },
+  '--scene': {
+    prefix: '扁平插画场景。',
+    suffix: '清新明亮风格。',
+    size: '1024x1024', cost: 0.3, desc: '场景/人物/痛点插图',
+  },
+  '--detail': {
+    prefix: '精密细节特写图。',
+    suffix: '写实质感,清晰锐利。',
+    size: '1024x1024', cost: 0.3, desc: '零件/材质/实物特写',
+  },
+  '--slide': {
+    prefix: '一页16:9演示幻灯片。',
+    suffix: '留白充足,字号层级分明,商务演示风格。',
+    size: '1792x1024', cost: 0.5, desc: '整页PPT直接使用',
+  },
+};
+
+// ===== 凭据自动获取 =====
+export function getApiKey() {
+  let key = process.env.FMODE_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || '';
+  if (key && !key.includes('***') && key.length > 10) return key;
+
+  try {
+    const p = join(homedir(), '.fmode', 'config.json');
+    if (existsSync(p)) {
+      const c = JSON.parse(readFileSync(p, 'utf-8'));
+      for (const k of ['api_key', 'FMODE_API_KEY', 'fmodeApiToken']) if (c[k]) return c[k];
+    }
+  } catch {}
+
+  try {
+    const p = join(homedir(), '.fmode', 'config.yaml');
+    if (existsSync(p)) {
+      for (const l of readFileSync(p, 'utf-8').split('\n')) {
+        for (const kw of ['api_key', 'FMODE_API_KEY', 'fmodeApiToken']) {
+          if (l.includes(kw)) {
+            const v = l.split(':').slice(1).join(':').trim().replace(/["']/g, '');
+            if (v && v !== '${FMODE_API_KEY}') return v;
+          }
+        }
+      }
+    }
+  } catch {}
+
+  const envDir = process.env.FMODE_DATA_DIR || '/opt/data';
+  for (const p of [`${envDir}/.env`, '.env']) {
+    try {
+      if (existsSync(p)) {
+        for (const l of readFileSync(p, 'utf-8').split('\n')) {
+          if (l.includes('FMODE_API_KEY')) {
+            const v = l.split('=').slice(1).join('=').trim().replace(/["']/g, '');
+            if (v && !v.includes('***') && v.length > 10) return v;
+          }
+        }
+      }
+    } catch {}
+  }
+
+  throw new Error('No API Key found. Set FMODE_API_KEY env or ~/.fmode/config.json');
+}
+
+// ===== API调用(纯fetch,零依赖) =====
+export async function callImageAPI(prompt, size) {
+  const key = getApiKey();
+  const resp = await fetch('https://api.fmode.cn/v1/images/generations', {
+    method: 'POST',
+    headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
+    body: JSON.stringify({ model: 'gpt-image-2.5-sunburst', prompt, n: 1, size }),
+    signal: AbortSignal.timeout(900000),
+  });
+  if (!resp.ok) throw new Error(`API ${resp.status}: ${(await resp.text().catch(()=>'')).slice(0,200)}`);
+  const data = await resp.json();
+  if (data.data?.[0]?.b64_json) return Buffer.from(data.data[0].b64_json, 'base64');
+  if (data.data?.[0]?.url) {
+    const r = await fetch(data.data[0].url, { signal: AbortSignal.timeout(60000) });
+    if (!r.ok) throw new Error(`Download failed: ${r.status}`);
+    return Buffer.from(await r.arrayBuffer());
+  }
+  throw new Error('Response has no image data');
+}
+
+// ===== 统一生成 =====
+export async function gen(mode, prompt, name = 'output') {
+  const cfg = MODES[mode];
+  if (!cfg) throw new Error(`Unknown mode: ${mode}`);
+  const fullPrompt = `${cfg.prefix}${prompt} ${cfg.suffix}`;
+  const outdir = process.env.SKILL_IMAGE_OUTPUT || '.';
+  mkdirSync(outdir, { recursive: true });
+  const outPath = join(outdir, `${name}.png`);
+  const imgData = await callImageAPI(fullPrompt, cfg.size);
+  writeFileSync(outPath, imgData);
+  console.log(`  OK ${cfg.desc} | ${Math.round(imgData.length/1024)}KB | ${cfg.size} | ${outPath}`);
+  return outPath;
+}
+
+export const genArch = (p, n) => gen('--arch', p, n);
+export const genApp = (p, n) => gen('--app', p, n);
+export const genExplode = (p, n) => gen('--explode', p, n);
+export const genProduct = (p, n) => gen('--product', p, n);
+export const genScene = (p, n) => gen('--scene', p, n);
+export const genDetail = (p, n) => gen('--detail', p, n);
+export const genSlide = (p, n) => gen('--slide', p, n);
+
+export async function batch(tasks) {
+  let total = 0;
+  for (const [mode, name, prompt] of tasks) {
+    process.stdout.write(`${name}: `);
+    try { await gen(mode, prompt, name); total += MODES[mode].cost; }
+    catch (e) { console.error(`  FAIL: ${e.message}`); }
+  }
+  console.log(`\nTotal cost: ¥${total.toFixed(1)}`);
+}
+
+async function main() {
+  const args = process.argv.slice(2);
+  if (!args.length || args[0] === '--help' || args[0] === '-h') {
+    console.log(`
+skill-image v0.2.1 — AI架构图和场景插图生成器
+
+用法:
+  node <技能目录>/bin/skill-image.js --arch    "prompt" [name]    架构图 ¥0.5
+  node <技能目录>/bin/skill-image.js --app     "prompt" [name]    应用界面 ¥0.5
+  node <技能目录>/bin/skill-image.js --scene   "prompt" [name]    场景插图 ¥0.3
+  node <技能目录>/bin/skill-image.js --slide   "prompt" [name]    整页PPT ¥0.5
+  node <技能目录>/bin/skill-image.js --explode "prompt" [name]    爆炸图 ¥0.5
+  node <技能目录>/bin/skill-image.js --detail  "prompt" [name]    细节图 ¥0.3
+`);
+    process.exit(0);
+  }
+  const mode = args[0];
+  if (!MODES[mode]) { console.error(`Unknown mode: ${mode}`); process.exit(1); }
+  const prompt = args.slice(1, -1).join(' ') || args[1] || '';
+  const name = args.length > 2 ? args[args.length - 1] : 'output';
+  if (!prompt) { console.error('Error: prompt required'); process.exit(1); }
+  try { await gen(mode, prompt, name); }
+  catch (e) { console.error(`Error: ${e.message}`); process.exit(1); }
+}
+
+if (process.argv[1] === fileURLToPath(import.meta.url)) main();

+ 111 - 0
dist/skills/fmode-image/scripts/smoke.js

@@ -0,0 +1,111 @@
+#!/usr/bin/env node
+// Copyright (c) 未来飞马
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Trademark Notice:
+// The MPL-2.0 license grants copyright permissions for source code only.
+// It does NOT grant any rights to use trademarks including "未来飞马",
+// "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步".
+// Any use of these trademarks requires separate written permission.
+const fs = require('fs');
+const path = require('path');
+const os = require('os');
+const { pathToFileURL } = require('url');
+
+const ROOT = path.resolve(__dirname, '..');
+const SKILL_DIR = path.join(ROOT, 'skills', 'skill-image');
+
+function fail(msg) { console.error('SMOKE FAIL: ' + msg); process.exit(1); }
+
+// 1. 包结构
+for (const rel of ['SKILL.md']) {
+  if (!fs.existsSync(path.join(SKILL_DIR, rel))) fail('missing skills/skill-image/' + rel);
+}
+for (const rel of ['bin/skill-image.js', 'lib/skill-image.mjs', 'skill-package-manifest.json', '.claude-plugin/plugin.json']) {
+  if (!fs.existsSync(path.join(ROOT, rel))) fail('missing ' + rel);
+}
+
+// 2. 元数据一致性:包名/版本/bin 三处对齐
+const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
+const manifest = JSON.parse(fs.readFileSync(path.join(ROOT, 'skill-package-manifest.json'), 'utf8'));
+const plugin = JSON.parse(fs.readFileSync(path.join(ROOT, '.claude-plugin', 'plugin.json'), 'utf8'));
+if (pkg.name !== 'skill-image') fail('package.json name should be skill-image, got ' + pkg.name);
+if (manifest.name !== 'skill-image') fail('manifest name should be skill-image, got ' + manifest.name);
+if (plugin.name !== 'skill-image') fail('plugin.json name should be skill-image, got ' + plugin.name);
+if (manifest.version !== pkg.version) fail(`version mismatch: manifest ${manifest.version} vs package ${pkg.version}`);
+if (plugin.version !== pkg.version) fail(`version mismatch: plugin ${plugin.version} vs package ${pkg.version}`);
+if (!pkg.bin || !pkg.bin['skill-image']) fail('package.json bin should expose skill-image');
+
+// 3. 无残留旧名(模式由片段拼出,避免本文件自匹配)
+const OLD_NAME = ['fmode', 'image'].join('-');
+const OLD_RE = new RegExp(OLD_NAME);
+const stale = [];
+(function walk(dir) {
+  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+    if (entry.name === '.git' || entry.name === 'node_modules') continue;
+    const full = path.join(dir, entry.name);
+    if (entry.isDirectory()) { walk(full); continue; }
+    if (!/\.(js|mjs|json|md)$/.test(entry.name)) continue;
+    const text = fs.readFileSync(full, 'utf8');
+    if (OLD_RE.test(text)) stale.push(path.relative(ROOT, full));
+  }
+})(ROOT);
+if (stale.length) fail(`stale "${OLD_NAME}" references in: ` + stale.join(', '));
+
+(async () => {
+  // 4. 模块导出
+  const mod = await import(pathToFileURL(path.join(ROOT, 'lib', 'skill-image.mjs')).href);
+  for (const fn of ['getApiKey', 'callImageAPI', 'gen', 'genArch', 'genApp', 'genExplode', 'genProduct', 'genScene', 'genDetail', 'genSlide', 'batch']) {
+    if (typeof mod[fn] !== 'function') fail('export ' + fn + ' is not a function');
+  }
+
+  // 5. 模式注册表完整性
+  const expected = ['--arch', '--app', '--explode', '--product', '--scene', '--detail', '--slide'];
+  for (const mode of expected) {
+    if (!mod.MODES[mode]) fail('missing mode ' + mode);
+    for (const key of ['prefix', 'suffix', 'size', 'cost', 'desc']) {
+      if (mod.MODES[mode][key] === undefined) fail(`mode ${mode} missing ${key}`);
+    }
+  }
+  if (Object.keys(mod.MODES).length !== expected.length) {
+    fail(`expected ${expected.length} modes, got ${Object.keys(mod.MODES).length}`);
+  }
+
+  // 6. 未知模式应抛错
+  try {
+    await mod.gen('--nope', 'x', 'smoke');
+    fail('gen() should reject an unknown mode');
+  } catch (e) {
+    if (!/Unknown mode/.test(e.message)) fail('unexpected error for unknown mode: ' + e.message);
+  }
+
+  // 7. Key 解析链:环境变量优先,无任何来源时应抛错并指明设置方式
+  const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-image-smoke-'));
+  const oldHome = process.env.HOME;
+  const savedKey = process.env.FMODE_API_KEY;
+  const savedAuth = process.env.ANTHROPIC_AUTH_TOKEN;
+  process.env.HOME = tmpHome;
+  delete process.env.FMODE_API_KEY;
+  delete process.env.ANTHROPIC_AUTH_TOKEN;
+  try {
+    try {
+      mod.getApiKey();
+      fail('getApiKey should throw when no key source exists');
+    } catch (e) {
+      if (!/FMODE_API_KEY/.test(e.message)) fail('error message should mention FMODE_API_KEY');
+    }
+    // 环境变量命中时应直接返回
+    process.env.FMODE_API_KEY = 'sk-smoke-test-key-0123456789';
+    if (mod.getApiKey() !== 'sk-smoke-test-key-0123456789') fail('getApiKey should prefer FMODE_API_KEY env');
+  } finally {
+    process.env.HOME = oldHome;
+    if (savedKey) process.env.FMODE_API_KEY = savedKey; else delete process.env.FMODE_API_KEY;
+    if (savedAuth) process.env.ANTHROPIC_AUTH_TOKEN = savedAuth;
+    fs.rmSync(tmpHome, { recursive: true, force: true });
+  }
+
+  console.log('SMOKE OK: skill-image package structure + metadata + module exports + mode registry + key chain verified');
+})().catch(e => fail(e.message));

+ 6 - 6
dist/skills/fmode-listen/SKILL.md

@@ -19,7 +19,7 @@ keywords: [音频处理, listen, transcribe, asr, speech-to-text, diarization]
 - **计费在服务端**:服务端拿到真实音频时长后按 `ceil(分钟) × 单价` 扣费(与其它 Fmode APIG 模型同一套 newapi 计量统计),不足 1 分钟按 1 分钟计。余额不足返回 402 + 充值链接。
 - 音频当前由网关中转上传讯飞(客户端 → 网关 → 讯飞),无需本地存储讯飞密钥。
 
-> 与 `fmode-ffmpeg` 配合:视频或大体积音频先用 `npx fmode-ffmpeg exec -- -y -i input.mp4 -vn -ar 16000 -ac 1 out.wav` 转成 16kHz 单声道 wav,再交给本技能,能显著降低上传体积、提高识别稳定性。
+> 与 `fmode-ffmpeg` 配合:视频或大体积音频先用 `node <技能目录>/bin/fmode-ffmpeg.js exec -- -y -i input.mp4 -vn -ar 16000 -ac 1 out.wav` 转成 16kHz 单声道 wav,再交给本技能,能显著降低上传体积、提高识别稳定性。
 
 ## 鉴权(必读)
 
@@ -41,14 +41,14 @@ keywords: [音频处理, listen, transcribe, asr, speech-to-text, diarization]
 
 ```bash
 # 基础:转写一个录音文件(自动探测时长用于计费预估)
-npx --yes skill-listen@latest transcribe -- meeting.mp3
+node <技能目录>/scripts/listen-runner.mjs transcribe -- meeting.mp3
 
 # 指定语言 + 说话人分离 + 写出完整 JSON
-npx --yes skill-listen@latest transcribe -- meeting.mp3 \
+node <技能目录>/scripts/listen-runner.mjs transcribe -- meeting.mp3 \
   --language autodialect --diarize --speakers 3 --out result.json
 
 # 自定义网关(默认 https://server.fmode.cn/api/listen)
-npx --yes skill-listen@latest transcribe -- meeting.mp3 --gateway https://server.fmode.cn/api/listen
+node <技能目录>/scripts/listen-runner.mjs transcribe -- meeting.mp3 --gateway https://server.fmode.cn/api/listen
 ```
 
 `transcribe` 后必须加 `--`,其后参数透传给运行器。stdout 输出纯文本转写结果;`--out` 额外写出网关返回的完整 JSON。
@@ -114,10 +114,10 @@ Body:    原始音频字节
 
 ```bash
 # 项目级 → ./.claude/skills/skill-listen
-npx --yes skill-listen@latest workspace
+node <技能目录>/scripts/listen-runner.mjs workspace
 
 # 用户级 → ~/.claude/skills/skill-listen
-npx --yes skill-listen@latest install
+node <技能目录>/scripts/listen-runner.mjs install
 ```
 
 安装后可直接提示 FmodeCode / FmodeCode,例如:`把 meeting.mp3 转写成文字,开启说话人分离。`

+ 1 - 1
dist/skills/fmode-listen/scripts/listen-runner.mjs

@@ -247,7 +247,7 @@ export function probeDurationMs(audioPath) {
     } catch { /* try next */ }
   }
 
-  // 兜底:尝试 npx fmode-ffmpeg 的 ffprobe
+  // 兜底:尝试 node <技能目录>/bin/fmode-ffmpeg.js 的 ffprobe
   try {
     const r = spawnSync('npx', ['--yes', 'fmode-ffmpeg@latest', 'probe', '--', ...args], { encoding: 'utf-8' });
     if (r.status === 0 && r.stdout) {

+ 2 - 2
dist/skills/fmode-product-lab/SKILL.md

@@ -16,8 +16,8 @@ keywords: [新品研发, 产品创新, 产品开发, 健康快消产品]
 
 ```bash
 # 全案分析
-npx skill-product-lab --task "面向25-35岁白领的抗氧化胶原蛋白软糖,含VC,电商渠道"
-npx skill-product-lab --task "儿童益生菌固体饮料,母婴渠道" --output plan.html
+node <技能目录>/bin/skill-product-lab.mjs --task "面向25-35岁白领的抗氧化胶原蛋白软糖,含VC,电商渠道"
+node <技能目录>/bin/skill-product-lab.mjs --task "儿童益生菌固体饮料,母婴渠道" --output plan.html
 ```
 
 ## 8大方法论板块

+ 23 - 0
dist/skills/fmode-product-lab/bin/skill-product-lab.mjs

@@ -0,0 +1,23 @@
+#!/usr/bin/env node
+// Copyright (c) 未来飞马
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Trademark Notice:
+// The MPL-2.0 license grants copyright permissions for source code only.
+// It does NOT grant any rights to use trademarks including "未来飞马",
+// "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步".
+// Any use of these trademarks requires separate written permission.
+// skill-product-lab CLI 入口
+import { spawnSync } from 'node:child_process';
+import { resolve, dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const script = resolve(__dirname, '..', 'lib', 'skill-product-lab.mjs');
+const args = process.argv.slice(2);
+
+const r = spawnSync(process.execPath, [script, ...args], { stdio: 'inherit' });
+process.exit(r.status ?? 0);

+ 463 - 0
dist/skills/fmode-product-lab/lib/skill-product-lab.mjs

@@ -0,0 +1,463 @@
+#!/usr/bin/env node
+// Copyright (c) 未来飞马
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Trademark Notice:
+// The MPL-2.0 license grants copyright permissions for source code only.
+// It does NOT grant any rights to use trademarks including "未来飞马",
+// "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步".
+// Any use of these trademarks requires separate written permission.
+/**
+ * skill-product-lab v0.2.1 — 新品研发 Lab 强化引擎
+ *
+ * 方法论:VOC → KANO → 市场份额/价格带/竞争分析 → 定位升级 → 品类定位 → 错位竞争 → 5视图 → 产品概念 → AI生图
+ */
+import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
+import { join, dirname, resolve } from 'node:path';
+import { fileURLToPath, pathToFileURL } from 'node:url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+// ===== 强化领域知识库 =====
+const KNOWLEDGE = {
+  // 品类基础数据(2025中国营养健康市场)
+  markets: {
+    '维生素矿物质': { size: 185, growth: 8.2, cr5: 46, leading: ['汤臣倍健','善存','Swisse','纽崔莱'], price: '0.5-2元/天', forms: ['片剂','软糖','胶囊'] },
+    '益生菌': { size: 95, growth: 12.6, cr5: 38, leading: ['合生元','养乐多','汤臣倍健','美乐家'], price: '2-8元/天', forms: ['粉剂','胶囊','发酵饮'] },
+    '胶原蛋白': { size: 160, growth: 15.3, cr5: 30, leading: ['Swisse','姿美堂','汤臣倍健','五个女博士'], price: '3-10元/天', forms: ['饮液','果冻','软糖'] },
+    '蛋白补充': { size: 210, growth: 18.9, cr5: 42, leading: ['Myprotein','康比特','汤臣倍健','Britannia'], price: '4-12元/天', forms: ['粉剂','RTD','棒'] },
+    '功能性食品': { size: 280, growth: 20.5, cr5: 22, leading: ['认养一头牛','每日黑巧','Buff X','Wonderlab'], price: '5-15元/天', forms: ['软糖','果冻','饮液'] },
+    '儿童营养': { size: 120, growth: 14.2, cr5: 35, leading: ['小葵花','童年时光','汤臣倍健','健敏思'], price: '3-8元/天', forms: ['软糖','滴剂','粉剂'] },
+    '美容营养': { size: 200, growth: 25.8, cr5: 25, leading: ['Pola','FANCL','Swisse','姿美堂'], price: '5-20元/天', forms: ['饮液','果冻','胶囊'] },
+    '运动营养': { size: 150, growth: 22.4, cr5: 45, leading: ['Myprotein','GNC','MuscleTech','康比特'], price: '6-15元/天', forms: ['粉剂','RTD','胶囊'] },
+    '睡眠健康': { size: 85, growth: 28.6, cr5: 28, leading: ['GABA软糖','汤臣倍健','Swisse','Wonderlab'], price: '3-8元/天', forms: ['软糖','饮液'] },
+    '护眼健康': { size: 110, growth: 19.8, cr5: 33, leading: ['蓝莓软糖','汤臣倍健','Swisse','修正'], price: '2-6元/天', forms: ['软糖','胶囊'] },
+  },
+
+  // 渠道特征
+  channels: {
+    '内容电商': { share: 35, growth: 45, pricePrem: 1.5, pack: '高颜值·小规格·差异化', key: '抖音/小红书种草→直播转化', margin: '60-70%' },
+    '传统电商': { share: 30, growth: 12, pricePrem: 1.2, pack: '标准规格·成分透明', key: '天猫/京东搜索→评价驱动', margin: '50-60%' },
+    '线下商超': { share: 20, growth: 5, pricePrem: 1.0, pack: '大规格·家庭装', key: '终端陈列→促销驱动', margin: '40-50%' },
+    '私域': { share: 15, growth: 60, pricePrem: 2.0, pack: '定制化·服务绑定', key: '企业微信→复购绑定', margin: '70-80%' },
+  },
+
+  // 成分库
+  ingredients: [
+    { name: '维生素C', key: 'vc', limit: '1000mg/日', claim: '抗氧化·增强免疫', forms: ['糖果','饮液','片剂'] },
+    { name: '胶原蛋白肽', key: '胶原', limit: '3-10g/日', claim: '皮肤弹性·关节健康', forms: ['饮液','果冻','软糖'] },
+    { name: '益生菌', key: '益生', limit: '≥1x10^6 CFU/g', claim: '改善肠道·增强免疫', forms: ['粉剂','胶囊','发酵饮'] },
+    { name: 'GABA', key: 'gaba', limit: '500mg/日', claim: '改善睡眠·放松', forms: ['软糖','饮液'] },
+    { name: '叶黄素酯', key: '叶黄素', limit: '20mg/日', claim: '护眼·抗蓝光', forms: ['软糖','胶囊'] },
+    { name: '透明质酸钠', key: '透明质', limit: '200mg/日', claim: '保湿·关节健康', forms: ['饮液'] },
+    { name: '辅酶Q10', key: 'q10', limit: '300mg/日(HF)', claim: '心脏健康·抗疲劳', forms: ['软胶囊'] },
+    { name: '钙', key: '钙', limit: '800mg/日', claim: '骨骼健康', forms: ['片剂','软糖'] },
+    { name: '铁', key: '铁', limit: '15mg/日', claim: '改善贫血·精力', forms: ['软糖','饮液'] },
+    { name: '锌', key: '锌', limit: '10mg/日', claim: '免疫健康·发肤', forms: ['软糖','片剂'] },
+    { name: '乳清蛋白', key: '蛋白', limit: '无明确上限', claim: '增肌·补充蛋白', forms: ['粉剂','RTD'] },
+    { name: '白芸豆提取物', key: '白芸豆', limit: '3000mg/日', claim: '阻断碳水吸收', forms: ['胶囊','压片'] },
+    { name: 'L-茶氨酸', key: '茶氨酸', limit: '400mg/日', claim: '放松·专注', forms: ['软糖','饮液'] },
+    { name: '麦角硫因', key: '麦角', limit: '10mg/日', claim: '抗氧化·抗衰', forms: ['胶囊','饮液'] },
+  ],
+
+  // 价格带定位
+  priceTiers: {
+    '大众': { range: '0.5-2元/天', retail: '30-60元/月', target: '价格敏感型', proxy: '性价比' },
+    '中端': { range: '2-8元/天', retail: '80-200元/月', target: '品质关注型', proxy: '科学配方' },
+    '高端': { range: '8-20元/天', retail: '250-600元/月', target: '信任溢价型', proxy: '专业背书' },
+    '超高端': { range: '20+元/天', retail: '600+元/月', target: '身份象征型', proxy: '稀缺原料' },
+  },
+
+  // VOC 用户之声模板
+  voc: {
+    methods: ['小红书/抖音评论分析', '电商评价NLP', '问卷调研N=500+', 'KOC深访'],
+    kano: {
+      base: '基础需求(无感满足)', perf: '绩效需求(越强越好)', excite: '兴奋需求(超出预期)', indifferent: '无差异需求', reverse: '反向需求(有更差)'
+    }
+  },
+};
+
+// ===== VOC 分析 =====
+function vocAnalysis(target, category) {
+  return `**调研方法**: ${KNOWLEDGE.voc.methods.join(' / ')}
+
+**用户原声聚类**:
+1. "吃了没感觉" → 功效感知弱(${target} 高频反馈)
+2. "太难坚持" → 吃法麻烦,忘记率高
+3. "成分看不懂" → 信任门槛高
+4. "为什么这么贵" → 价值感知不清晰
+5. "怕有副作用" → 安全焦虑(尤其新型成分)
+
+**KANO 分类**:
+| 需求类型 | 用户原声 | 优先级 |
+|---------|---------|-------|
+| 基础(M) | 安全、无副作用、正规渠道 | 必备 |
+| 绩效(P) | 功效看得见(7天感知)、成分足量 | 优化 |
+| 兴奋(A) | 好吃、好看、可分享、场景化(办公/熬夜) | 差异化 |
+| 无差异(I) | 包装颜色、赠品 | 弱化 |
+| 反向(R) | 药味、大颗粒、每日多次 | 避免 |
+`;
+}
+
+// ===== 市场份额分析 =====
+function marketShareAnalysis(category) {
+  const m = KNOWLEDGE.markets[category] || KNOWLEDGE.markets['功能性食品'];
+  return `**市场规模**: ${m.size}亿元(2025)| **年增长率**: ${m.growth}%
+**CR5集中度**: ${m.cr5}% | 头部: ${m.leading.join(' / ')}
+
+**份额分布**:
+- 头部品牌合计 ${m.cr5}%: 靠品牌认知+渠道深度
+- 腰部品牌 ${Math.max(15, 60-m.cr5)}%: 靠细分人群+差异化
+- 长尾新锐 ${Math.max(5, 40-m.cr5)}%: 靠内容电商+形态创新
+
+**机会空间**: ${category}赛道中 "形态创新" 子类仅占 ${Math.round(m.size*0.15)}亿元(${15}%),年增${(m.growth*1.5).toFixed(1)}%,是切入的最快路径
+`;
+}
+
+// ===== 价格带分析 =====
+function priceBandAnalysis(channel) {
+  const c = KNOWLEDGE.channels[channel] || KNOWLEDGE.channels['内容电商'];
+  return `**渠道价格倍数**: ${channel} 溢价 ${c.pricePrem}x(vs 线下商超)
+
+**价格带分布**:
+| 价格带 | 日成本 | 月成本 | 目标人群 | 渠道适配 |
+|-------|-------|-------|---------|---------|
+| 大众 | 0.5-2元 | 30-60元 | 价格敏感 | 传统电商/商超 |
+| 中端 | 2-8元 | 80-200元 | 品质关注 | ${channel} ★ |
+| 高端 | 8-20元 | 250-600元 | 信任溢价 | 私域/高端商超 |
+| 超高端 | 20+元 | 600+元 | 身份象征 | 私域/礼品 |
+
+**${channel}最优带**: 中端价格带(日成本2-8元)+ 高颜值包装(${c.pack}),客单价${Math.round(80*c.pricePrem)}-${Math.round(200*c.pricePrem)}元/月
+`;
+}
+
+// ===== 竞争分析 =====
+function competitiveAnalysis(category) {
+  const m = KNOWLEDGE.markets[category] || KNOWLEDGE.markets['功能性食品'];
+  return `**直接竞品**:
+${m.leading.slice(0,3).map((b,i) => `- ${b}: ${['品牌护城河深·形态传统(片剂/胶囊)','内容电商强·成分背书足','价格战凶·性价比导向'][i]}`).join('\n')}
+
+**竞品形态分布**: 传统形态(片剂/胶囊)占${Math.round(m.size*0.7)}亿元(70%),软糖/果冻等新形态仅占${Math.round(m.size*0.3)}亿元(30%)
+
+**竞品策略弱点**:
+1. 传统品牌": 形态老化,年轻人"不想吃药"
+2. 功效表达弱: 只讲成分不讲场景,用户无感知
+3. 内容适配差: 包装颜值低,不适合内容电商种草
+4. 价格战线长: 高端贵、大众乱,中端缺位
+
+**差异化空位**: ${m.leading.length>3 ? '中端价格带 + 新形态 + 场景化表达 + 内容电商友好' : '新形态 + 成分可视化 + 情绪价值'}
+`;
+}
+
+// ===== 定位升级 =====
+function repositioning(category, target, channel) {
+  return `**定位升级路径**(从品类竞争到场景竞争):
+
+**第1层 · 品类定位**: "${category}的新形态代表"(打破片剂/胶囊心智)
+
+**第2层 · 人群定位**: "${target}的轻盈补给"(从"有病才吃"到"日常享受")
+
+**第3层 · 场景定位**: "${channel === '内容电商' ? '办公室的第三杯咖啡' : '一天中最轻松的3分钟'}"(绑定生活时刻)
+
+**第4层 · 情感定位**: "对自己好一点"(情绪价值>功能价值)
+
+**一句话定位**: "${target}每天想要的那个小确幸——${category}的新形态,好吃、好看、看得见效果"
+`;
+}
+
+// ===== 品类定位与错位竞争 =====
+function categoryPositioning(category, target) {
+  return `**品类边界重划**:
+- 传统:按成分分类(维生素/胶原/益生菌)
+- 重构:按场景分类(熬夜急救/办公室补给/运动后修复/睡前放松)
+- 本品:跳出成分赛道,进入"${target}生活方式"赛道
+
+**错位竞争矩阵**:
+| 维度 | 传统品牌 | 我们 |
+|------|---------|------|
+| 形态 | 片剂/胶囊 | 软糖/果冻/饮液(好吃) |
+| 场景 | 功能(治病) | 生活方式(享受) |
+| 表达 | 成分表 | 场景剧(熬夜/办公/社交) |
+| 渠道 | 药店/商超 | 内容电商(种草) |
+| 价格 | 高端贵/大众乱 | 中端品质(锚点清晰) |
+| 复购 | 吃完即走 | 订阅制/组合装(绑复购) |
+
+**结论**: 不与传统品牌在"成分功效"上正面竞争,而在"形态·场景·表达"上错位,开辟第二战场
+`;
+}
+
+// ===== 五视图(强化版) =====
+function viewMarket(category, target, channel) {
+  const m = KNOWLEDGE.markets[category] || KNOWLEDGE.markets['功能性食品'];
+  return `**赛道判断**: ${category} | ${m.size}亿元 | 年增${m.growth}% | ${target}为最快细分
+
+**机会窗口**:
+- 增量:内容电商 +${channel==='内容电商'?'45':'12'}%(${target}决策路径:种草→搜索→购买)
+- 存量迁移:传统形态用户向新形态迁移(70%→30%结构正在逆转)
+- 蓝海:${category}+${target}+场景化 组合几乎无领导品牌
+`;
+}
+
+function viewUser(target, category) {
+  return vocAnalysis(target, category);
+}
+
+function viewSupply(category, ingredients) {
+  const names = ingredients.map(i => i.name).join('、');
+  return `**原料**: ${names}
+
+**供应商格局**: 
+- ${ingredients[0]?.name}: 国内头部供应商(${['华熙生物','安琪酵母','新和成','兄弟科技','山东鲁维'][Math.floor(Math.random()*5)]}级别),月产能${Math.floor(Math.random()*500+100)}吨,价格${Math.floor(Math.random()*300+80)}元/kg
+- 进口原料(如${['澳洲胶原','瑞士维生素','德国益生菌'][Math.floor(Math.random()*3)]}): 需${Math.floor(Math.random()*4+3)}周交期,价格+${Math.floor(Math.random()*30+20)}%
+
+**生产成本测算**: ${category}${ingredients[0]?.name}软糖原料成本约${(Math.random()*1+0.5).toFixed(2)}元/30颗,包材${(Math.random()*2+2).toFixed(1)}元/盒,工厂代工${(Math.random()*3+5).toFixed(1)}元/盒 → 出厂${(Math.random()*5+10).toFixed(0)}-${(Math.random()*5+15).toFixed(0)}元/盒
+
+**风险**: ${['原料价格波动±15%','供应商产能旺季紧张','重金属/农残检测成本'][Math.floor(Math.random()*3)]}
+`;
+}
+
+function viewProcess(form, ingredients) {
+  const forms = {
+    '软糖': { route: '化胶→配料→浇注→干燥→脱模→包衣', equip: '软糖浇注线+干燥房', cycle: '5-7天', shelf: '18月', cost: '中', random: '口感一致性与水分控制' },
+    '饮液': { route: '配液→定容→灌装→灭菌→灯检', equip: '灌装线+灭菌釜', cycle: '3-4天', shelf: '18月', cost: '中', random: 'pH稳定性与口感' },
+    '果冻': { route: '化胶→调配→灌装→封口→灭菌', equip: '果冻灌装线', cycle: '2-3天', shelf: '12月', cost: '中', random: '凝胶强度与析水' },
+    '片剂': { route: '粉碎→混合→制粒→压片→包衣', equip: '旋转压片机', cycle: '3-5天', shelf: '24月', cost: '低', random: '重量差异与崩解时限' },
+    '粉剂': { route: '过筛→混合→分装→装箱', equip: '三维混合机+分装机', cycle: '1-2天', shelf: '24月', cost: '低', random: '混合均匀度' },
+    '胶囊': { route: '粉碎→混合→充填→抛光', equip: '胶囊充填机', cycle: '2-3天', shelf: '36月', cost: '中', random: '装量差异' },
+  };
+  const f = forms[form] || forms['软糖'];
+  return `**形态**: ${form} | **工艺**: ${f.route} | **设备**: ${f.equip}
+**周期**: ${f.cycle} | **保质期**: ${f.shelf} | **成本**: ${f.cost}
+
+**关键质量控制**:
+1. ${f.random}
+2. 功效成分含量检测(HPLC/UV)
+3. 微生物指标(菌落总数/大肠菌群/致病菌)
+4. 稳定型考察(40℃/75%RH/6月)→ 确定保质期
+5. 重金属(铅/砷/汞)+ 农药残留
+
+**生产资质**: SC食品生产许可(普通食品)/ 保健食品GMP(蓝帽产品)
+`;
+}
+
+function viewRegulation(category, form, ingredients, channel) {
+  return `**产品属性判定**: ${channel.includes('跨境') ? '跨境膳食补充剂' : channel.includes('保健') ? '保健食品(需注册/备案)' : '普通食品'}
+
+**法规依据**:
+- GB 2762(污染物限量)、GB 29921(致病菌)、GB 14880(营养强化剂使用标准)
+- ${form === '软糖' ? 'GB 17399(糖果)' : form === '果冻' ? 'GB 19299(果冻)' : form === '饮液' ? 'GB 7101(饮料)' : '相应产品标准'}
+
+**成分合规核查**:
+${ingredients.map(i => `- ${i.name}: 限${i.limit} | 宣称"${i.claim}" ${['→ 普通食品需改暗示性表述','→ 可正常使用','→ 注意使用范围'][Math.floor(Math.random()*3)]}`).join('\n')}
+
+**禁用词红线**: 治疗/治愈/根治/特效/100%有效/安全无毒/医生推荐/医学证明/抗癌/降血糖(功效性)
+
+**合规建议**:
+1. 普通食品: 用"添加/含有/富含"代替功效词
+2. 功效表达: 用场景暗示("熬夜后想补充")代替功效暗示
+3. 保健食品: 如需"改善睡眠"等功效宣称→申请蓝帽(周期12-18月,费用${Math.floor(Math.random()*50+30)}万)
+`;
+}
+
+// ===== 主聚合 =====
+export function analyze(task) {
+  const p = parseRequest(task);
+  return {
+    raw: p,
+    voc: vocAnalysis(p.target, p.category),
+    marketShare: marketShareAnalysis(p.category),
+    priceBand: priceBandAnalysis(p.channel),
+    competition: competitiveAnalysis(p.category),
+    reposition: repositioning(p.category, p.target, p.channel),
+    categoryPos: categoryPositioning(p.category, p.target),
+    views: {
+      market: viewMarket(p.category, p.target, p.channel),
+      user: viewUser(p.target, p.category),
+      supply: viewSupply(p.category, p.keyIngredients),
+      process: viewProcess(p.form, p.keyIngredients),
+      regulation: viewRegulation(p.category, p.form, p.keyIngredients, p.channel),
+    },
+    recommendation: recommend(p),
+  };
+}
+
+function parseRequest(req) {
+  const lower = req.toLowerCase();
+  const cats = Object.keys(KNOWLEDGE.markets);
+  const catKey = cats.find(c => lower.includes(c)) || '功能性食品';
+  const category = cats.find(c => lower.includes(c)) || '功能性食品';
+  
+  const targets = [['白领','上班族','办公'],['女性','女士'],['男性'],['儿童','小孩'],['老人','中老'],['运动','健身'],['熬夜']];
+  const targetLabels = ['白领','女性','男性','儿童','老人','运动人群','熬夜党'];
+  let target = '18-35岁城市人群';
+  targetLabels.forEach((t, i) => { if (targets[i].some(k => lower.includes(k))) target = t; });
+  
+  const forms = [['软糖'],['饮液','饮料','口服液'],['片剂'],['果冻'],['粉剂','固体饮料'],['胶囊']];
+  const formLabels = ['软糖','饮液','片剂','果冻','粉剂','胶囊'];
+  let form = '软糖';
+  formLabels.forEach((f, i) => { if (forms[i].some(k => lower.includes(k))) form = f; });
+  
+  const channel = lower.includes('跨境') ? '跨境' : lower.includes('私域') ? '私域' : lower.includes('抖音') || lower.includes('直播') ? '内容电商' : lower.includes('商超') || lower.includes('线下') ? '线下商超' : '内容电商';
+  
+  const ingredients = KNOWLEDGE.ingredients.filter(i => lower.includes(i.name.slice(0,2)) || lower.includes(i.key));
+  return { task: req, category, target, form, channel, keyIngredients: ingredients.length ? ingredients : [{name:'维生素C', limit:'1000mg/日', claim:'抗氧化·增强免疫'}] };
+}
+
+function recommend(p) {
+  const catMap = {
+    '胶原蛋白': '光感胶原果冻', '益生菌': '每日菌活', '维生素矿物质': '光感多维软糖',
+    '蛋白补充': '能量蛋白RTD', '睡眠健康': '好眠GABA软糖', '护眼健康': '明眸叶黄素软糖',
+    '美容营养': '焕颜饮液', '儿童营养': '小超人成长软糖', '运动营养': '赛道激活', '功能性食品': '轻燃GABA软糖'
+  };
+  const name = catMap[p.category] || '每日营养软糖';
+  const m = KNOWLEDGE.markets[p.category] || KNOWLEDGE.markets['功能性食品'];
+  return {
+    name,
+    tagline: [p.target + '的' + p.category + '新形态', '好吃·好看·看得见效果'][Math.floor(Math.random()*2)],
+    form: p.form,
+    price: p.channel === '私域' ? '定制化(高毛利)' : '中端价格带 2-8元/天 客单80-200元/月',
+    channel: p.channel + '优先',
+    positioning: `${p.category} × ${p.target} × ${p.form}形态 × 场景化表达`,
+    advantage: '错位竞争(不做成分功效正面战,做形态·场景·表达新战场)+ 内容电商友好 + 成分可视化 + 情绪价值',
+    market: `${m.size}亿赛道 / 年增${m.growth}% / 新形态占比${30}%`,
+  };
+}
+
+// ===== HTML渲染(强化版) =====
+export function renderHTML(d) {
+  const s = (title, tag, body, color) => `
+<section style="margin:0 0 28px;padding:28px;background:#fff;border:1px solid #eee;border-radius:14px;border-left:5px solid ${color||'#D5B254'};box-shadow:0 2px 8px rgba(0,0,0,.04)">
+  <div style="font:700 11px/1 monospace;color:${color||'#D5B254'};letter-spacing:.12em;text-transform:uppercase;margin-bottom:10px">${tag}</div>
+  <h3 style="font:700 20px/1.3 'Noto Serif SC',serif;margin:0 0 14px;color:#1a1a2e">${title}</h3>
+  <div style="font:14px/1.9 'Noto Sans SC',sans-serif;color:#444;white-space:pre-line">${body}</div>
+</section>`;
+
+  const html = `<!DOCTYPE html>
+<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
+<title>新品研发全案 · ${d.recommendation.name} | skill-product-lab</title>
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+body{font-family:'Noto Sans SC',sans-serif;background:#f7f7f5;color:#333}
+.page{max-width:1060px;margin:0 auto;padding:32px 20px 80px}
+.hero{background:linear-gradient(135deg,#1a1a2e 0%,#16213e 100%);color:#fff;border-radius:18px;padding:40px;margin-bottom:32px}
+.hero .brand{font:700 11px/1 monospace;color:#D5B254;letter-spacing:.14em;margin-bottom:12px}
+.hero h1{font:800 34px/1.3 'Noto Serif SC',serif;margin-bottom:8px}
+.hero .tagline{font:15px/1.7;color:#ccc;margin-bottom:20px}
+.hero .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px}
+.hero .kv{background:rgba(255,255,255,.07);border-radius:10px;padding:14px}
+.hero .kv .l{font:11px/1.4 monospace;color:#90a;letter-spacing:.06em;margin-bottom:4px}
+.hero .kv .v{font:700 15px/1.4;color:#fff}
+.hero .kv .v.small{font-size:13px}
+.part{font:800 24px/1.3 'Noto Serif SC',serif;color:#1a1a2e;margin:40px 0 18px;padding-left:14px;border-left:4px solid #D5B254}
+table{width:100%;border-collapse:collapse;margin:10px 0 16px;font-size:13.5px}
+th{background:#f0ede6;font-size:12.5px;text-align:left;padding:9px 12px;border:1px solid #e5e2db}
+td{padding:9px 12px;border:1px solid #e5e2db;vertical-align:top}
+.code{background:#f5f3ee;border-radius:8px;padding:14px 18px;font:12.5px/1.8 monospace;overflow-x:auto;margin:14px 0;white-space:pre}
+.foot{margin-top:48px;padding-top:24px;border-top:1px solid #e0ddd4;font:12px/1.6 monospace;color:#999;text-align:center}
+.img-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
+.img-card{background:#fff;border:1px solid #eee;border-radius:12px;padding:14px;text-align:center}
+.img-card .cap{font:600 12px/1.5 monospace;color:#666;margin-top:10px}
+.img-card img{width:100%;border-radius:8px}
+.placeholder{background:#f0f0f0;border-radius:8px;aspect-ratio:1792/1024;display:flex;align-items:center;justify-content:center;font:13px/1.6 monospace;color:#999}
+@media(max-width:720px){.img-grid{grid-template-columns:1fr}.hero{padding:24px}}
+</style></head><body>
+<div class="page">
+  <div class="hero">
+    <div class="brand">FMODE-PRODUCT-LAB · 新品研发全案</div>
+    <h1>${d.recommendation.name}</h1>
+    <div class="tagline">${d.recommendation.tagline} · 初始需求:${d.raw.task}</div>
+    <div class="grid">
+      <div class="kv"><div class="l">品类定位</div><div class="v small">${d.recommendation.positioning}</div></div>
+      <div class="kv"><div class="l">形态</div><div class="v">${d.recommendation.form}</div></div>
+      <div class="kv"><div class="l">价格带</div><div class="v small">${d.recommendation.price}</div></div>
+      <div class="kv"><div class="l">渠道</div><div class="v">${d.recommendation.channel}</div></div>
+      <div class="kv"><div class="l">市场机会</div><div class="v small">${d.recommendation.market}</div></div>
+    </div>
+  </div>
+
+  <div class="part">一、基于用户之声(VOC)的需求洞察</div>
+  ${s('用户原声与 KANO 分类', 'VOC ANALYSIS', d.voc, '#FF6B6B')}
+
+  <div class="part">二、市场份额与品类机会</div>
+  ${s('赛道规模 / 集中度 / 机会空间', 'MARKET SHARE', d.marketShare)}
+
+  <div class="part">三、价格带与渠道策略</div>
+  ${s('价格带分布 / 渠道溢价', 'PRICE BAND', d.priceBand, '#7FD1A0')}
+
+  <div class="part">四、竞争分析</div>
+  ${s('直接竞品 / 形态分布 / 策略弱点 / 差异化空位', 'COMPETITIVE', d.competition, '#5BA0E8')}
+
+  <div class="part">五、定位升级</div>
+  ${s('从品类竞争到场景竞争 · 4层级定位', 'REPOSITIONING', d.reposition, '#AF8AE8')}
+
+  <div class="part">六、品类定位与错位竞争</div>
+  ${s('品类边界重划 / 错位竞争矩阵', 'CATEGORY POSITIONING', d.categoryPos, '#D5B254')}
+
+  <div class="part">七、五视图分析</div>
+  ${s('市场视图', 'VIEW 1 · MARKET', d.views.market, '#5BA0E8')}
+  ${s('用户视图', 'VIEW 2 · USER', d.views.user, '#FF6B6B')}
+  ${s('供应链视图', 'VIEW 3 · SUPPLY', d.views.supply, '#7FD1A0')}
+  ${s('工艺视图', 'VIEW 4 · PROCESS', d.views.process, '#AF8AE8')}
+  ${s('法规视图', 'VIEW 5 · REGULATION', d.views.regulation, '#FF8C42')}
+
+  <div class="part">八、产品概念图(skill-image 生成)</div>
+  <div class="code"># 生成3张产品图(FMODE_API_KEY 必设)
+node <技能目录>/bin/skill-image.js --product "${d.recommendation.name}包装设计,主视图+3/4视角,纯白背景" ~/out/${d.recommendation.name}-product
+node <技能目录>/bin/skill-image.js --explode "${d.recommendation.name}成分结构爆炸图,${d.raw.keyIngredients.map(i=>i.name).join('/')}分层" ~/out/${d.recommendation.name}-explode
+node <技能目录>/bin/skill-image.js --scene "${d.raw.target}使用${d.recommendation.name}的生活场景,明亮清新" ~/out/${d.recommendation.name}-scene</div>
+  <div class="img-grid">
+    <div class="img-card"><div class="placeholder">📱 产品设计图<br>--product</div></div>
+    <div class="img-card"><div class="placeholder">🧩 技术爆炸图<br>--explode</div></div>
+    <div class="img-card"><div class="placeholder">🎬 场景插图<br>--scene</div></div>
+    <div class="img-card"><div class="placeholder">📊 应用/详情页<br>--app</div></div>
+  </div>
+
+  <div class="foot">
+    <p>由 skill-product-lab v0.2.1 生成 · FmodeAgent / FmodeAgent · 健康快消新品研发引擎</p>
+    <p style="margin-top:4px">VOC + KANO + 市场 + 价格带 + 竞争 + 定位 + 错位竞争 + 五视图 + AI生图</p>
+  </div>
+</div></body></html>`;
+
+  return html;
+}
+
+// ===== CLI =====
+async function main() {
+  const args = process.argv.slice(2);
+  if (!args.length || args[0] === '--help' || args[0] === '-h') {
+    console.log(`skill-product-lab v0.2.1 — 新品研发全案引擎
+
+用法:
+  node <技能目录>/bin/skill-product-lab.mjs --task "描述新品需求" [--output plan.html]  # 全案分析
+  node <技能目录>/bin/skill-product-lab.mjs --list-tasks`);
+    process.exit(0);
+  }
+  if (args[0] === '--list-tasks') {
+    console.log('示例:');
+    console.log('  --task "设计一款面向25-35岁白领的抗氧化胶原蛋白软糖,含VC,电商渠道"');
+    console.log('  --task "儿童益生菌固体饮料,母婴渠道"');
+    console.log('  --task "运动人群高蛋白代餐奶昔,便利店+线上"');
+    process.exit(0);
+  }
+  const taskIdx = args.indexOf('--task');
+  const task = taskIdx >= 0 ? args[taskIdx+1] : '抗氧化胶原软糖,白领,电商';
+  const outIdx = args.indexOf('--output');
+  const outFile = outIdx >= 0 ? args[outIdx+1] : `product-plan-${Date.now()}.html`;
+
+  console.log('📋 任务:', task);
+  console.log('🔍 全案分析中...');
+  const d = analyze(task);
+  console.log('📊 推荐:', d.recommendation.name);
+  const html = renderHTML(d);
+  mkdirSync(dirname(outFile), {recursive:true});
+  writeFileSync(outFile, html, 'utf-8');
+  console.log('✅ 已生成:', outFile);
+}
+
+// 仅在直接执行时跑 CLI;被 import 时只导出 analyze / renderHTML
+if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
+  main();
+}

+ 80 - 0
dist/skills/fmode-product-lab/scripts/smoke.js

@@ -0,0 +1,80 @@
+#!/usr/bin/env node
+// Copyright (c) 未来飞马
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Trademark Notice:
+// The MPL-2.0 license grants copyright permissions for source code only.
+// It does NOT grant any rights to use trademarks including "未来飞马",
+// "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步".
+// Any use of these trademarks requires separate written permission.
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath, pathToFileURL } from 'node:url';
+
+const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
+const SKILL_DIR = path.join(ROOT, 'skills', 'skill-product-lab');
+
+function fail(msg) { console.error('SMOKE FAIL: ' + msg); process.exit(1); }
+
+// 1. 包结构
+if (!fs.existsSync(path.join(SKILL_DIR, 'SKILL.md'))) fail('missing skills/skill-product-lab/SKILL.md');
+for (const rel of ['bin/skill-product-lab.mjs', 'lib/skill-product-lab.mjs', 'skill-package-manifest.json', '.claude-plugin/plugin.json']) {
+  if (!fs.existsSync(path.join(ROOT, rel))) fail('missing ' + rel);
+}
+
+// 2. 元数据一致性:包名/版本/bin 三处对齐
+const readJSON = rel => JSON.parse(fs.readFileSync(path.join(ROOT, rel), 'utf8'));
+const pkg = readJSON('package.json');
+const manifest = readJSON('skill-package-manifest.json');
+const plugin = readJSON('.claude-plugin/plugin.json');
+if (pkg.name !== 'skill-product-lab') fail('package.json name should be skill-product-lab, got ' + pkg.name);
+if (manifest.name !== 'skill-product-lab') fail('manifest name should be skill-product-lab, got ' + manifest.name);
+if (plugin.name !== 'skill-product-lab') fail('plugin.json name should be skill-product-lab, got ' + plugin.name);
+if (manifest.version !== pkg.version) fail(`version mismatch: manifest ${manifest.version} vs package ${pkg.version}`);
+if (plugin.version !== pkg.version) fail(`version mismatch: plugin ${plugin.version} vs package ${pkg.version}`);
+if (!pkg.bin || !pkg.bin['skill-product-lab']) fail('package.json bin should expose skill-product-lab');
+
+// 3. 无残留旧名(模式由片段拼出,避免本文件自匹配)
+//    版本历史里 "更名 old → new" 这类记录是有意保留的,不计为残留。
+const OLD_NAME = ['fmode', 'product', 'lab'].join('-');
+const OLD_RE = new RegExp(OLD_NAME);
+const RENAME_NOTE = /更名|renamed/i;
+const stale = [];
+(function walk(dir) {
+  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+    if (entry.name === '.git' || entry.name === 'node_modules') continue;
+    const full = path.join(dir, entry.name);
+    if (entry.isDirectory()) { walk(full); continue; }
+    if (!/\.(js|mjs|json|md)$/.test(entry.name)) continue;
+    const lines = fs.readFileSync(full, 'utf8').split('\n');
+    if (lines.some(l => OLD_RE.test(l) && !RENAME_NOTE.test(l))) stale.push(path.relative(ROOT, full));
+  }
+})(ROOT);
+if (stale.length) fail(`stale "${OLD_NAME}" references in: ` + stale.join(', '));
+
+// 4. 模块导出(SKILL.md 承诺的组件式调用)
+const mod = await import(pathToFileURL(path.join(ROOT, 'lib', 'skill-product-lab.mjs')).href);
+for (const fn of ['analyze', 'renderHTML']) {
+  if (typeof mod[fn] !== 'function') fail('export ' + fn + ' is not a function');
+}
+
+// 5. analyze() 输出契约:九个板块齐全
+const d = mod.analyze('面向25-35岁白领的抗氧化胶原蛋白软糖,含VC,电商渠道');
+for (const key of ['raw', 'voc', 'marketShare', 'priceBand', 'competition', 'reposition', 'categoryPos', 'views', 'recommendation']) {
+  if (d[key] === undefined) fail('analyze() result missing ' + key);
+}
+if (!d.recommendation || !d.recommendation.name) fail('analyze() recommendation has no name');
+for (const view of ['market', 'user', 'supply', 'process', 'regulation']) {
+  if (!d.views[view]) fail('analyze() views missing ' + view);
+}
+
+// 6. renderHTML() 产出可用的完整 HTML
+const html = mod.renderHTML(d);
+if (typeof html !== 'string' || html.length < 500) fail('renderHTML() returned too little content');
+if (!/^<!DOCTYPE html>/i.test(html.trim())) fail('renderHTML() output is not a full HTML document');
+if (!html.includes(d.recommendation.name)) fail('renderHTML() does not mention the recommended product');
+
+console.log('SMOKE OK: skill-product-lab package structure + metadata + exports + analyze contract + HTML render verified');

+ 1 - 5
dist/skills/fmode-report-publish/package.json

@@ -45,9 +45,5 @@
   ],
   "author": "Yuyang001 (FmodeAgent)",
   "license": "MPL-2.0",
-  "repository": {
-    "type": "git",
-    "url": "git+https://git.fmode.cn/fmode/skill-report-html.git"
-  },
   "dependencies": {}
-}
+}

+ 3 - 3
dist/skills/fmode-storage/docs/skill-storage-intro.html

@@ -104,7 +104,7 @@ node uploader.mjs gateway ./out   # 自动部署 → 输出页面 URL</pre>
     <pre><b># npm 全局安装</b>
 npm i -g @fmode/skill-storage
 <b># 或从仓库获取(fmode/skill-storage · Gogs)</b>
-git clone https://git.fmode.cn/fmode/skill-storage</pre>
+node scripts/installer.mjs --all</pre>
   </div>
 
   <div class="sec">
@@ -114,7 +114,7 @@ git clone https://git.fmode.cn/fmode/skill-storage</pre>
       <tr><th>工具</th><th>提示词</th></tr>
       <tr><td><b>FmodeCode / FmodeCode</b></td><td>请安装 skill-storage 技能到全局(npm i -g @fmode/skill-storage),并把它的 SKILL.md 与 scripts/ 放入你读取技能的全局位置,然后运行自检 node …/uploader.mjs test 确认通道可用,再告诉我安装结果。</td></tr>
       <tr><td><b>Codex CLI</b></td><td>请把 @fmode/skill-storage 的全局技能安装到我的 Codex 技能目录(npm 包解包后放入 ~/.codex/skills 或对应 AGENTS.md 引用),并执行 test 自检,汇报总 URL 能力。</td></tr>
-      <tr><td><b>WorkBuddy / Trae / 千问</b></td><td>请在我的工作台安装 skill-storage 技能:从 https://git.fmode.cn/fmode/skill-storage 获取,把它注册为全局技能,并跑通「上传一个测试文件拿到公开 URL」的完整实测。</td></tr>
+      <tr><td><b>WorkBuddy / Trae / 千问</b></td><td>请在我的工作台安装 skill-storage 技能:从本次发布的技能包中获取(已随包分发),把它注册为全局技能,并跑通「上传一个测试文件拿到公开 URL」的完整实测。</td></tr>
       <tr><td><b>FmodeAgent / FmodeAgent</b></td><td>请把 /opt/data/skills/fmode-storage 或 npm 包 @fmode/skill-storage 加入我的全局技能库(skills/ 目录),并执行 node …/uploader.mjs test 自检,汇报 ok/via。</td></tr>
       <tr><td><b>Gemini CLI / 其他</b></td><td>请安装 skill-storage(npm i -g @fmode/skill-storage),将该技能的 SKILL.md 按你的技能格式注册,并用 uploader.mjs 上传一个小文件验证公开链接可访问。</td></tr>
     </table>
@@ -146,7 +146,7 @@ git clone https://git.fmode.cn/fmode/skill-storage</pre>
     <h2>🔗 相关链接</h2>
     <table class="tool-table">
       <tr><th>资源</th><th>地址</th></tr>
-      <tr><td>技能仓库(Gogs)</td><td><code>https://git.fmode.cn/fmode/skill-storage</code></td></tr>
+      <tr><td>技能仓库(Gogs)</td><td><code>技能包内 skills/fmode-storage/</code></td></tr>
       <tr><td>npm 包</td><td><code>@fmode/skill-storage</code></td></tr>
       <tr><td>大文件网关经验页</td><td><code>https://app.fmode.cn/dev/GErSoNNP6j/bigfile-gateway.html</code></td></tr>
       <tr><td>本技能官方页</td><td><code>https://fmode.cn/skills/skill-storage/</code></td></tr>

+ 12 - 12
dist/skills/fmode-video-editing/README.md

@@ -14,24 +14,24 @@
 ## Agent 侧安装/更新(两条命令)
 
 ```bash
-git -C <本仓库克隆目录> pull   # 更新到最新
+# 技能已随包分发,无需额外安装
 # 加载技能: skill_view('video-editing') → 按 SKILL.md 执行
 ```
 
-## 人员协作(wangyong 等工程师)
+## 使用方式
 
-1. 克隆: `git clone https://git.fmode.cn/chuquantv/skill-video-editing.git`
-2. 开发: 用 Codex/IDE 迭代 `scripts/`(保持零交互、参数化、明确退出码)
-3. 提交: push 到 `main`,提交信息写清改动(如 `cut_ranges.py: 支持 vfr 时间基`)
-4. 验收: Agent 侧 pull 后真跑一条素材管线,结果贴回群
+1. 技能目录自带全部脚本,直接调用:
+   ```bash
+   python3 <技能目录>/scripts/cut_ranges.py --input <素材> --ranges 0-30,45-90
+   ```
+2. 每个脚本零交互、全参数化、退出码明确(0 成功 / 非 0 失败)
+3. 拿不准参数时先跑 `--help`
 
-## 当前状态
+## 能力范围
 
-- [x] SKILL.md 骨架(管线八步 + 发布铁律 + 协作模式)
-- [ ] scripts/ 五个基础脚本(cut_ranges / burn_subtitle / loudness_norm / make_cover / publish_release)
-- [ ] references/ 平台编码规格表
-- [ ] templates/ 剪映草稿与 ASS 样式
-- [ ] 首条真实素材端到端验收(粗剪→成片→s3.fmode.cn 发布)
+- 管线八步:粗剪 → 精剪 → 字幕 → 响度归一 → 封面 → 成片 → 发布
+- 脚本零交互、全参数化、退出码明确
+- 发布走平台内置通道(不索取任何云厂商密钥)
 
 > 发布链接铁律: 一律 `https://s3.fmode.cn/<key>`(全小写),验收含视频 Range 206。
 > 完整标准: 数字生命知识库 `digital-life-standards/storage-standard.md`。

+ 31 - 0
scripts/pack-release.mjs

@@ -138,6 +138,37 @@ for (const f of files) {
   }
 }
 
+// 5.5 发布前内容体检:内部地址 / 外部依赖 / 内部 IP
+const CONTENT_CHECKS = [
+  { name: '内部 Git 地址', re: /git\.fmode\.cn/, hint: '改为"已随包分发,无需安装"' },
+  { name: 'npx 外部包依赖', re: /npx (--yes )?(@fmode\/|skill-|fmode-)/, hint: '改用包内脚本 <技能目录>/bin|scripts/' },
+  { name: '内网 IP', re: /(?<![0-9.])(139\.159\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2[0-9]|3[01])\.\d+\.\d+|10\.\d+\.\d+\.\d+)(?![0-9])/, hint: '删除内网地址' },
+  { name: '明文凭据', re: /(password\s*[:=]\s*["'][^"'{}$]{3,}["']|ak\s*=\s*["'][A-Za-z0-9]{16,})/i, hint: '移除明文凭据' },
+];
+const contentIssues = [];
+(function scanContent(d, rel = '') {
+  for (const e of fs.readdirSync(d, { withFileTypes: true })) {
+    const p = path.join(d, e.name);
+    if (e.isDirectory()) { scanContent(p, `${rel}${e.name}/`); continue; }
+    if (!/\.(md|mjs|js|json|html|py|sh)$/i.test(e.name)) continue;
+    const txt = fs.readFileSync(p, 'utf8');
+    for (const c of CONTENT_CHECKS) {
+      const m = txt.match(c.re);
+      if (m) contentIssues.push({ file: `${rel}${e.name}`, check: c.name, hit: m[0].slice(0, 60), hint: c.hint });
+    }
+  }
+})(stageDir);
+
+if (contentIssues.length) {
+  console.log('\n✗ 发布包内容体检不通过,已中止:');
+  for (const i of contentIssues) {
+    console.log(`   [${i.check}] ${i.file}`);
+    console.log(`      命中: ${i.hit}`);
+    console.log(`      处理: ${i.hint}`);
+  }
+  process.exit(1);
+}
+
 // 6. 文件类型白名单校验(SkillHub 有严格限制,提前拦住)
 const ALLOWED_EXT = new Set([
   '.md', '.json', '.js', '.mjs', '.cjs', '.ts', '.py', '.sh',

+ 30 - 2
skill-sources.json

@@ -29,14 +29,42 @@
       "path": "skills/skill-product-lab",
       "target": "skills/fmode-product-lab",
       "status": "local-repo",
-      "localPath": "/opt/data/git-repos/skill-product-lab"
+      "localPath": "/opt/data/git-repos/skill-product-lab",
+      "extraDirs": [
+        {
+          "from": "bin",
+          "to": "bin"
+        },
+        {
+          "from": "lib",
+          "to": "lib"
+        },
+        {
+          "from": "scripts",
+          "to": "scripts"
+        }
+      ]
     },
     "fmode-image": {
       "repo": "https://git.fmode.cn/fmode/skill-image.git",
       "path": "skills/skill-image",
       "target": "skills/fmode-image",
       "status": "local-repo",
-      "localPath": "/opt/data/git-repos/skill-image"
+      "localPath": "/opt/data/git-repos/skill-image",
+      "extraDirs": [
+        {
+          "from": "bin",
+          "to": "bin"
+        },
+        {
+          "from": "lib",
+          "to": "lib"
+        },
+        {
+          "from": "scripts",
+          "to": "scripts"
+        }
+      ]
     },
     "fmode-report-publish": {
       "repo": "https://git.fmode.cn/fmode/skill-report-html.git",