Skill: yingyangdao/qianfan-tools (version 1.1.0)

--- name: qianfan-tools description: 百度千帆工具箱 - 集成百度千帆平台多种免费 API | Baidu Qianfan Tools - Integrate Baidu Qianfan Platform APIs metadata: author: 寇助理 openclaw: emoji: 🔧 category: tools tags: [qianfan, baidu, search, ai, tools, text-generation] requires: env: [BAIDU_API_KEY] primaryEnv: BAIDU_API_KEY --- # 百度千帆工具箱 | Baidu Qianfan Tools > 集成百度千帆平台 API，一个技能搞定多个工具！ > Integrate Baidu Qianfan Platform APIs - one skill for multiple tools! ## 📋 功能列表 | Features ### ✅ 已测试可用 | Tested & Working | 功能 | 脚本 | 描述 | |------|------|------| | 智能搜索 | `ai-search.js` | 搜索 + AI 总结，返回结构化结果 | | 百度热搜 | `hotword.js` | 获取实时热搜榜单 | ### 🔧 支持的 API (需开通) | Supported APIs (Requires Activation) | 功能 | 脚本 | 描述 | 赠送额度 | |------|------|------|---------| | 文本生成 | `chat.js` | 文心大模型对话 | - | | 续写 | `continue.js` | 文本续写 | - | | 图像生成 | `image.js` | AI 绘图 | - | | 通用 OCR | `ocr.js` | 图片文字识别 | - | | 作文识别 | `ocr-essay.js` | 手写作文 OCR | 10次/天 | | PPT生成 | `ppt.js` | 智能 PPT | 5次/天 | | 百度学术 | `academic.js` | 学术搜索 | 1000次/天 | ## 🚀 快速开始 | Quick Start ### 1. 配置 API Key | Configure API Key 在 Skills 配置页面填写 `BAIDU_API_KEY`，或编辑 `~/.openclaw/openclaw.json`： ```json { "skills": { "entries": { "qianfan-tools": { "env": { "BAIDU_API_KEY": "你的百度千帆 API Key" } } } } } ``` 本地使用 | Local usage: ```bash cp config.example.json config.json # 编辑 config.json 填入 apiKey ``` ### 2. 使用方式 | Usage ```bash cd skills/qianfan-tools export BAIDU_API_KEY="你的APIKey" # 智能搜索 | Smart Search node scripts/ai-search.js "查询内容" [数量] # 百度热搜 | Baidu Hot Search node scripts/hotword.js # 文本对话 (需开通) | Text Chat (requires activation) node scripts/chat.js "你好" # 图像生成 (需开通) | Image Generation (requires activation) node scripts/image.js "一只可爱的猫" # OCR 文字识别 (需开通) | OCR (requires activation) node scripts/ocr.js <图片URL> # PPT生成 (需开通) | PPT Generation (requires activation) node scripts/ppt.js "主题内容" # 百度学术 (需开通) | Academic Search (requires activation) node scripts/academic.js "关键词" [数量] ``` ## 📊 API 额度参考 | API Quota Reference | API | 每日赠送 | 基础定价 | |-----|---------|---------| | 智能搜索生成 | 100次 | 0.036元/次 | | 百度搜索 | 1000次 | 0.036元/次 | | 百度百科 | 1000次 | 0.036元/次 | | 百度热搜 | 10次 | 0.06元/次 | | PPT生成 | 5次 | 2.5元/次 | | 初中作文识别 | 10次 | 0.03元/次 | | 高中作文识别 | 10次 | 0.05元/次 | | 百度学术 | 1000次 | 0.07-0.66元/次 | ## ⚠️ 注意事项 | Notes 1. **API Key 获取**: 访问 [百度千帆控制台](https://console.bce.baidu.com/qianfan/) 创建 API Key 2. **额度查询**: 部分 API 需要在千帆平台开通才能使用 3. **免费额度**: 每日赠送额度用完后将按量计费 ## 📁 项目结构 | Project Structure ``` qianfan-tools/ ├── SKILL.md # 本文档 ├── _meta.json # 元数据 ├── package.json # 依赖 ├── config.example.json # 配置模板 └── scripts/ ├── shared.js # 共享工具 ├── ai-search.js # 智能搜索 ✅ ├── hotword.js # 百度热搜 ✅ ├── chat.js # 文本对话 ├── continue.js # 文本续写 ├── image.js # 图像生成 ├── ocr.js # 通用 OCR ├── ocr-essay.js # 作文 OCR ├── ppt.js # PPT生成 └── academic.js # 百度学术 ``` ## 🔗 相关链接 | Links - [百度千帆控制台](https://console.bce.baidu.com/qianfan/) - [API 文档](https://cloud.baidu.com/doc/qianfan-api/s/3m9b5lqft) - [ClawHub 技能市场](https://clawhub.com/skill/qianfan-tools) --- **提示**: 本技能复用一个 API Key，统一管理更方便！ **Tip**: This skill uses one API Key for all tools - unified management!

path: _meta.json
content: {
  "author": "寇助理",
  "description": "百度千帆工具箱 - 集成百度千帆平台多种免费 API",
  "id": "qianfan-tools",
  "name": "qianfan-tools",
  "tags": ["qianfan", "baidu", "search", "ai", "tools", "text-generation"],
  "version": "1.1.0"
}


path: config.example.json

path: package.json
content: {
  "name": "qianfan-tools",
  "version": "1.1.0",
  "description": "百度千帆工具箱 - 集成百度千帆平台多种免费 API",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": ["baidu", "qianfan", "search", "api", "openclaw", "ai"],
  "author": "寇助理",
  "license": "MIT",
  "dependencies": {
    "axios": "^1.6.0"
  }
}


path: scripts/academic.js
content: #!/usr/bin/env node

/**
 * 百度千帆 - 百度学术搜索
 */

const axios = require('axios');
const { resolveApiKey, emptyResult, successResult, BASE_URL, TIMEOUT_MS } = require('./shared');

const ACADEMIC_URL = `${BASE_URL}/search/academic`;

async function academic(query, numResults = 10) {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    return emptyResult('未配置 BAIDU_API_KEY');
  }

  if (!query) {
    return emptyResult('搜索关键词不能为空。');
  }

  try {
    const body = {
      query,
      num: Math.min(numResults, 20)
    };

    const { data } = await axios.post(ACADEMIC_URL, body, {
      timeout: TIMEOUT_MS,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
    });

    if (data && data.code) {
      return emptyResult(`学术搜索服务错误: ${data.message || '需开通百度学术 API'}`);
    }

    const items = data?.result?.items || data?.result?.data || [];
    
    const papers = items.map(item => ({
      title: item?.title || '',
      authors: item?.authors || item?.author || [],
      abstract: item?.abstract || item?.summary || '',
      venue: item?.venue || item?.journal || '',
      year: item?.year || null,
      citations: item?.citations || 0,
      url: item?.url || ''
    }));

    return successResult({
      query,
      total: papers.length,
      papers,
      engine: 'qianfan-academic'
    });

  } catch (e) {
    return emptyResult(`学术搜索失败: ${e.message} (需开通百度学术 API)`);
  }
}

async function main() {
  const args = process.argv.slice(2);
  const query = args[0];
  const numResults = Number(args[1]) || 10;

  if (!query) {
    console.log(JSON.stringify(emptyResult('用法: node academic.js <关键词> [返回数量]')));
    process.exit(1);
  }

  const result = await academic(query, numResults);
  console.log(JSON.stringify(result, null, 2));
}

main();


path: scripts/ai-search.js
content: #!/usr/bin/env node

const axios = require('axios');
const { resolveApiKey, emptyResult, successResult, BASE_URL, TIMEOUT_MS } = require('./shared');

const SEARCH_URL = `${BASE_URL}/ai_search/web_search`;
const DEFAULT_NUM = 10;
const MAX_NUM = 50;

async function aiSearch(query, numResults = DEFAULT_NUM) {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    return emptyResult('未配置 BAIDU_API_KEY，请在 OpenClaw Skills 配置页面填入，或本地编辑 config.json。');
  }

  try {
    const body = {
      messages: [{ role: 'user', content: query }],
      search_source: 'baidu_search_v2',
      resource_type_filter: [{ type: 'web', top_k: Math.min(numResults, MAX_NUM) }],
    };

    const { data } = await axios.post(SEARCH_URL, body, {
      timeout: TIMEOUT_MS,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
    });

    if (data && data.code) {
      return emptyResult(`搜索服务错误: ${data.message || '未知错误'}`);
    }

    const refs = data?.references || [];
    const results = refs
      .filter(r => r && r.type === 'web')
      .map(r => ({
        title: r.title || '',
        url: r.url || '',
        snippet: r.snippet ?? r.content ?? '',
        date: r.date || null
      }));

    return successResult({
      query,
      total: results.length,
      results,
      engine: 'baidu-ai-search'
    });

  } catch (e) {
    return emptyResult(`搜索失败: ${e.message}`);
  }
}

async function main() {
  const args = process.argv.slice(2);
  const query = (args[0] || '').trim();
  const numResults = Math.min(Math.max(Number(args[1]) || DEFAULT_NUM, 1), MAX_NUM);

  if (!query) {
    console.log(JSON.stringify(emptyResult('搜索查询不能为空。')));
    process.exit(1);
  }

  const result = await aiSearch(query, numResults);
  console.log(JSON.stringify(result, null, 2));
}

main();


path: scripts/baike.js
content: #!/usr/bin/env node

const axios = require('axios');
const { resolveApiKey, emptyResult, successResult, BASE_URL, TIMEOUT_MS } = require('./shared');

// 尝试不同的百科端点
const BAIKE_ENDPOINTS = [
  `${BASE_URL}/baike/baike_summary`,
  `${BASE_URL}/baike/summary`,
  `${BASE_URL}/baike/entry`,
];

async function baike(query) {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    return emptyResult('未配置 BAIDU_API_KEY，请在 OpenClaw Skills 配置页面填入，或本地编辑 config.json。');
  }

  if (!query) {
    return emptyResult('词条名称不能为空。');
  }

  for (const url of BAIKE_ENDPOINTS) {
    try {
      const body = { query };
      const { data } = await axios.post(url, body, {
        timeout: TIMEOUT_MS,
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${apiKey}`,
        },
      });

      if (data && data.code) continue;

      // 解析百科结果
      const result = data?.result || data?.data || {};
      
      return successResult({
        query,
        title: result?.title || query,
        summary: result?.summary || result?.answer || '无摘要',
        image: result?.image || null,
        url: result?.url || null,
        engine: 'baidu-baike'
      });

    } catch (e) {
      continue;
    }
  }

  // 如果所有端点都失败，返回提示
  return emptyResult('百科服务暂时不可用或 API 端点已变更，请联系开发者。');
}

async function main() {
  const args = process.argv.slice(2);
  const query = args.join(' ').trim();

  const result = await baike(query);
  console.log(JSON.stringify(result, null, 2));
}

main();


path: scripts/chat.js
content: #!/usr/bin/env node

/**
 * 百度千帆 - 文本对话 (文心大模型)
 */

const axios = require('axios');
const { resolveApiKey, emptyResult, successResult, BASE_URL, TIMEOUT_MS } = require('./shared');

const CHAT_URL = `${BASE_URL}/text/chatcompletion`;

async function chat(prompt, model = 'ernie-4.0-8k') {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    return emptyResult('未配置 BAIDU_API_KEY，请在 OpenClaw Skills 配置页面填入，或本地编辑 config.json。');
  }

  if (!prompt) {
    return emptyResult('对话内容不能为空。');
  }

  try {
    const body = {
      model,
      messages: [
        { role: 'user', content: prompt }
      ]
    };

    const { data } = await axios.post(CHAT_URL, body, {
      timeout: TIMEOUT_MS,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
    });

    if (data && data.code) {
      return emptyResult(`对话服务错误: ${data.message || '未知错误'}`);
    }

    const result = data?.result || data?.choices?.[0]?.message?.content;
    
    return successResult({
      prompt,
      response: result,
      model: data?.model || model,
      usage: data?.usage,
      engine: 'qianfan-chat'
    });

  } catch (e) {
    // 尝试其他端点
    try {
      const altBody = {
        prompt,
        temperature: 0.7,
        top_p: 0.9
      };
      
      const { data } = await axios.post(`${BASE_URL}/text/chatprompt`, altBody, {
        timeout: TIMEOUT_MS,
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${apiKey}`,
        },
      });
      
      if (data && data.code) {
        return emptyResult(`对话服务错误: ${data.message || '需开通文心大模型 API'}`);
      }

      return successResult({
        prompt,
        response: data?.result || data?.text || '无响应',
        engine: 'qianfan-chat-alt'
      });
    } catch (e2) {
      return emptyResult(`对话失败: ${e.message} (需开通文心大模型 API)`);
    }
  }
}

async function main() {
  const args = process.argv.slice(2);
  const prompt = args.join(' ').trim();

  const result = await chat(prompt);
  console.log(JSON.stringify(result, null, 2));
}

main();


path: scripts/continue.js
content: #!/usr/bin/env node

/**
 * 百度千帆 - 文本续写
 */

const axios = require('axios');
const { resolveApiKey, emptyResult, successResult, BASE_URL, TIMEOUT_MS } = require('./shared');

const CONTINUE_URL = `${BASE_URL}/text/continuation`;

async function continueText(prompt, model = 'ernie-3.5-8k') {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    return emptyResult('未配置 BAIDU_API_KEY');
  }

  if (!prompt) {
    return emptyResult('续写内容不能为空。');
  }

  try {
    const body = {
      prompt,
      model,
      temperature: 0.8,
      top_p: 0.9,
      penalty: 1.0
    };

    const { data } = await axios.post(CONTINUE_URL, body, {
      timeout: TIMEOUT_MS,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
    });

    if (data && data.code) {
      return emptyResult(`续写服务错误: ${data.message || '需开通续写 API'}`);
    }

    return successResult({
      prompt,
      continuation: data?.result || data?.text || '无响应',
      model: data?.model || model,
      engine: 'qianfan-continue'
    });

  } catch (e) {
    return emptyResult(`续写失败: ${e.message} (需开通续写 API)`);
  }
}

async function main() {
  const args = process.argv.slice(2);
  const prompt = args.join(' ').trim();

  if (!prompt) {
    console.log(JSON.stringify(emptyResult('用法: node continue.js <续写内容>')));
    process.exit(1);
  }

  const result = await continueText(prompt);
  console.log(JSON.stringify(result, null, 2));
}

main();


path: scripts/hotword.js
content: #!/usr/bin/env node

/**
 * 百度热搜 - 使用百度搜索结果
 * 通过搜索"百度热搜"获取当前热门话题
 */

const axios = require('axios');
const { resolveApiKey, emptyResult, successResult, BASE_URL, TIMEOUT_MS } = require('./shared');

const SEARCH_URL = `${BASE_URL}/ai_search/web_search`;

async function hotword() {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    return emptyResult('未配置 BAIDU_API_KEY');
  }

  try {
    const body = {
      messages: [{ role: 'user', content: '百度热搜榜单前20' }],
      search_source: 'baidu_search_v2',
      resource_type_filter: [{ type: 'web', top_k: 10 }],
    };

    const { data } = await axios.post(SEARCH_URL, body, {
      timeout: TIMEOUT_MS,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
    });

    const refs = data?.references || [];
    const hotwords = refs
      .filter(r => r && r.type === 'web')
      .slice(0, 20)
      .map((r, i) => ({
        rank: i + 1,
        title: r.title || '',
        url: r.url || '',
        snippet: r.snippet || ''
      }));

    return successResult({
      total: hotwords.length,
      hotwords,
      engine: 'baidu-hotword-via-search'
    });

  } catch (e) {
    return emptyResult(`获取热搜失败: ${e.message}`);
  }
}

async function main() {
  const result = await hotword();
  console.log(JSON.stringify(result, null, 2));
}

main();


path: scripts/image.js
content: #!/usr/bin/env node

/**
 * 百度千帆 - 图像生成
 */

const axios = require('axios');
const { resolveApiKey, emptyResult, successResult, BASE_URL, TIMEOUT_MS } = require('./shared');

const IMAGE_URL = `${BASE_URL}/text2image/sd`;

// 图像生成是异步的，需要轮询
async function pollForResult(url, apiKey, maxAttempts = 30) {
  for (let i = 0; i < maxAttempts; i++) {
    try {
      const { data } = await axios.get(url, {
        headers: { Authorization: `Bearer ${apiKey}` },
        timeout: TIMEOUT_MS
      });
      
      if (data?.result?.image) {
        return data.result;
      } else if (data?.result?.status === 'failed') {
        throw new Error(data.result?.message || '生成失败');
      }
      
      await new Promise(r => setTimeout(r, 2000));
    } catch (e) {
      if (i === maxAttempts - 1) throw e;
      await new Promise(r => setTimeout(r, 2000));
    }
  }
  throw new Error('生成超时');
}

async function generateImage(prompt, style = '二次元') {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    return emptyResult('未配置 BAIDU_API_KEY');
  }

  if (!prompt) {
    return emptyResult('图像描述不能为空。');
  }

  try {
    const body = {
      prompt,
      style,
      size: '1024x1024',
      num: 1
    };

    const { data } = await axios.post(IMAGE_URL, body, {
      timeout: TIMEOUT_MS,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
    });

    if (data && data.code) {
      return emptyResult(`图像服务错误: ${data.message || '需开通图像生成 API'}`);
    }

    const taskId = data?.result?.task_id;
    if (!taskId) {
      // 同步返回
      return successResult({
        prompt,
        images: data?.result?.image || [],
        engine: 'qianfan-image'
      });
    }

    // 轮询获取结果
    const resultUrl = `${IMAGE_URL}/status/${taskId}`;
    const result = await pollForResult(resultUrl, apiKey);

    return successResult({
      prompt,
      taskId,
      images: result?.image ? [result.image] : [],
      engine: 'qianfan-image'
    });

  } catch (e) {
    return emptyResult(`图像生成失败: ${e.message} (需开通图像生成 API)`);
  }
}

async function main() {
  const args = process.argv.slice(2);
  const prompt = args.join(' ').trim();

  if (!prompt) {
    console.log(JSON.stringify(emptyResult('用法: node image.js <图像描述> [风格]')));
    console.log('示例: node image.js "一只可爱的猫咪" 二次元');
    process.exit(1);
  }

  const result = await generateImage(prompt);
  console.log(JSON.stringify(result, null, 2));
}

main();


path: scripts/ocr-essay.js
content: #!/usr/bin/env node

const axios = require('axios');
const { resolveApiKey, emptyResult, successResult, BASE_URL, TIMEOUT_MS } = require('./shared');

const OCR_URL = `${BASE_URL}/wenku/ocr_essay`;

async function ocrEssay(imageUrl, level = '高中') {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    return emptyResult('未配置 BAIDU_API_KEY，请在 OpenClaw Skills 配置页面填入，或本地编辑 config.json。');
  }

  if (!imageUrl) {
    return emptyResult('图片URL不能为空。');
  }

  try {
    const body = {
      image_url: imageUrl,
      level: level === '初中' ? 'middle' : 'high'
    };

    const { data } = await axios.post(OCR_URL, body, {
      timeout: TIMEOUT_MS,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
    });

    if (data && data.code) {
      return emptyResult(`OCR服务错误: ${data.message || '未知错误'}`);
    }

    const result = data?.result || {};
    
    return successResult({
      title: result?.title || '无标题',
      content: result?.content || result?.text || '未能识别内容',
      level,
      wordCount: result?.word_count,
      engine: 'baidu-ocr-essay'
    });

  } catch (e) {
    return emptyResult(`作文识别失败: ${e.message}`);
  }
}

async function main() {
  const args = process.argv.slice(2);
  const imageUrl = args[0];
  const level = args[1] || '高中';

  if (!imageUrl) {
    console.log(JSON.stringify(emptyResult('用法: node ocr-essay.js <图片URL> [初中|高中]')));
    process.exit(1);
  }

  const result = await ocrEssay(imageUrl, level);
  console.log(JSON.stringify(result, null, 2));
}

main();


path: scripts/ocr.js
content: #!/usr/bin/env node

/**
 * 百度千帆 - 通用 OCR 文字识别
 */

const axios = require('axios');
const { resolveApiKey, emptyResult, successResult, BASE_URL, TIMEOUT_MS } = require('./shared');

const OCR_URL = `${BASE_URL}/ocr/general`;

async function ocr(imageUrl) {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    return emptyResult('未配置 BAIDU_API_KEY');
  }

  if (!imageUrl) {
    return emptyResult('图片URL不能为空。');
  }

  try {
    const body = {
      url: imageUrl
    };

    const { data } = await axios.post(OCR_URL, body, {
      timeout: TIMEOUT_MS,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
    });

    if (data && data.code) {
      return emptyResult(`OCR服务错误: ${data.message || '需开通 OCR API'}`);
    }

    const words = data?.result?.words_list || [];
    const text = words.map(w => w?.words || '').join('\n');

    return successResult({
      imageUrl,
      texts: words.map(w => ({
        text: w?.words || '',
        confidence: w?.confidence
      })),
      fullText: text,
      count: words.length,
      engine: 'qianfan-ocr'
    });

  } catch (e) {
    return emptyResult(`OCR识别失败: ${e.message} (需开通 OCR API)`);
  }
}

async function main() {
  const args = process.argv.slice(2);
  const imageUrl = args[0];

  if (!imageUrl) {
    console.log(JSON.stringify(emptyResult('用法: node ocr.js <图片URL>')));
    process.exit(1);
  }

  const result = await ocr(imageUrl);
  console.log(JSON.stringify(result, null, 2));
}

main();


path: scripts/ppt.js
content: #!/usr/bin/env node

const axios = require('axios');
const { resolveApiKey, emptyResult, successResult, BASE_URL, TIMEOUT_MS } = require('./shared');

const PPT_URL = `${BASE_URL}/wenku/ppt`;

// PPT 生成是异步的，需要轮询
async function pollForResult(url, apiKey, maxAttempts = 30) {
  for (let i = 0; i < maxAttempts; i++) {
    try {
      const { data } = await axios.get(url, {
        headers: { Authorization: `Bearer ${apiKey}` },
        timeout: TIMEOUT_MS
      });
      
      if (data?.result?.status === 'success') {
        return data.result;
      } else if (data?.result?.status === 'failed') {
        throw new Error(data.result?.message || 'PPT生成失败');
      }
      
      // 等待后继续轮询
      await new Promise(r => setTimeout(r, 2000));
    } catch (e) {
      if (i === maxAttempts - 1) throw e;
      await new Promise(r => setTimeout(r, 2000));
    }
  }
  throw new Error('PPT生成超时');
}

async function generatePpt(topic) {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    return emptyResult('未配置 BAIDU_API_KEY，请在 OpenClaw Skills 配置页面填入，或本地编辑 config.json。');
  }

  if (!topic) {
    return emptyResult('PPT主题不能为空。');
  }

  try {
    const body = {
      query: topic,
      style: 'general',
      pages: 10
    };

    const { data } = await axios.post(PPT_URL, body, {
      timeout: TIMEOUT_MS,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
    });

    if (data && data.code) {
      return emptyResult(`PPT服务错误: ${data.message || '未知错误'}`);
    }

    const taskId = data?.result?.task_id;
    if (!taskId) {
      return emptyResult('未能获取任务ID');
    }

    // 轮询获取结果
    const resultUrl = `${PPT_URL}/status/${taskId}`;
    const result = await pollForResult(resultUrl, apiKey);

    return successResult({
      topic,
      taskId,
      downloadUrl: result?.download_url || result?.url,
      previewUrl: result?.preview_url,
      pages: result?.pages || 10,
      engine: 'baidu-ppt'
    });

  } catch (e) {
    return emptyResult(`PPT生成失败: ${e.message}`);
  }
}

async function main() {
  const args = process.argv.slice(2);
  const topic = args.join(' ').trim();

  const result = await generatePpt(topic);
  console.log(JSON.stringify(result, null, 2));
}

main();


path: scripts/product-title.js
content: #!/usr/bin/env node

const axios = require('axios');
const { resolveApiKey, emptyResult, successResult, BASE_URL, TIMEOUT_MS } = require('./shared');

const PRODUCT_URL = `${BASE_URL}/wenku/product_title`;

async function productTitle(imageUrl, description = '') {
  const [REDACTED_SECRET]);
  if (!apiKey) {
    return emptyResult('未配置 BAIDU_API_KEY，请在 OpenClaw Skills 配置页面填入，或本地编辑 config.json。');
  }

  if (!imageUrl) {
    return emptyResult('商品图片URL不能为空。');
  }

  try {
    const body = {
      image_url: imageUrl
    };
    
    if (description) {
      body.description = description;
    }

    const { data } = await axios.post(PRODUCT_URL, body, {
      timeout: TIMEOUT_MS,
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
    });

    if (data && data.code) {
      return emptyResult(`服务错误: ${data.message || '未知错误'}`);
    }

    const result = data?.result || {};
    const titles = result?.titles || result?.result || [];
    
    return successResult({
      imageUrl,
      description,
      titles: Array.isArray(titles) ? titles : [titles],
      count: titles.length || 1,
      engine: 'baidu-product-title'
    });

  } catch (e) {
    return emptyResult(`商品标题生成失败: ${e.message}`);
  }
}

async function main() {
  const args = process.argv.slice(2);
  const imageUrl = args[0];
  const description = args.slice(1).join(' ');

  if (!imageUrl) {
    console.log(JSON.stringify(emptyResult('用法: node product-title.js <商品图片URL> [商品描述]')));
    process.exit(1);
  }

  const result = await productTitle(imageUrl, description);
  console.log(JSON.stringify(result, null, 2));
}

main();


path: scripts/shared.js
content: const fs = require('fs');
const path = require('path');

const SKILL_ROOT = path.resolve(__dirname, '..');
const CONFIG_PATH = path.join(SKILL_ROOT, 'config.json');

/**
 * Resolve apiKey with priority:
 * 1. BAIDU_API_KEY env var (injected by OpenClaw/ClawHub platform)
 * 2. Local config.json (for local / self-hosted use)
 */
function resolveApiKey() {
  const envKey = (process.env.BAIDU_API_KEY || '').trim();
  if (envKey) return envKey;

  if (!fs.existsSync(CONFIG_PATH)) return '';
  try {
    const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
    return (cfg.apiKey || '').trim();
  } catch (e) {
    return '';
  }
}

function emptyResult(errorMsg) {
  return { error: errorMsg };
}

function successResult(data) {
  return { success: true, ...data };
}

module.exports = {
  resolveApiKey,
  emptyResult,
  successResult,
  BASE_URL: 'https://qianfan.baidubce.com/v2',
  TIMEOUT_MS: 15000
};


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)