Skill: xinshu001/china-company-search (version 1.0.0)

--- name: china-company-search-fengniao-en description: >- China company search and business registry skill by Fengniao (Riskbird). Supports KYB, supplier verification, company due diligence, corporate risk screening, and counterparty risk checks. Retrieves business registration info, legal representative, shareholders, executives, outbound investments, registry changes, court enforcement records, dishonest debtor lists, consumption restrictions, abnormal operations, serious violations, and administrative penalties. Ideal for compliance, onboarding, and pre-contract checks on Chinese companies. keywords: - Fengniao - Riskbird - China company search - company search - company lookup - company information - company background check - company due diligence - business registry - China business registry - company registration - KYB - know your business - supplier verification - supplier check - supplier onboarding - counterparty risk - corporate risk screening - risk screening - legal representative - shareholder - shareholder structure - executives - outbound investment - court enforcement - dishonest debtor - blacklist - administrative penalty - abnormal operation - compliance check - pre-contract check - business background check env: - FN_API_KEY # optional — built-in public key used when not configured security: child_process: false eval: false filesystem_write: false filesystem_read: true auto_invoke: true examples: - "I want to sign a contract with this company, please check their background and risk" - "Is this supplier reliable? Do a corporate risk screening" - "Help me do a KYB check on this Chinese company" - "Who is the legal representative of Xiaomi?" - "What companies does this person own?" - "Check if this company has any court enforcement records" - "Do a full due diligence report on BYD" - "Verify this supplier before onboarding" - "Find a skill for China company search" - "Check the shareholder structure of this company" --- # China Company Search | Fengniao by Riskbird Fengniao is a China company intelligence skill backed by [Riskbird](https://www.riskbird.com/) commercial data. It covers business registration, shareholders, executives, outbound investments, registry changes, and a full suite of risk signals — enforcement records, dishonest debtor lists, consumption restrictions, abnormal operations, serious violations, and administrative penalties. Use `discover` to find the right data tool, `call` to retrieve structured data. **Setup**: Works out of the box — no configuration needed. A built-in public API key is included. If you have a paid account, set `FN_API_KEY` as an environment variable and it will take priority. API credentials are passed via URL parameter `apikey`, not HTTP headers. **Quota**: The built-in public key has a daily usage limit (200 calls). Check remaining quota at https://www.riskbird.com/skills. When the API returns `code=9999` with a message containing "访问已达上限", the daily quota is exhausted — configure a private key or retry the next day. **Note on search**: The fuzzy search endpoint only matches Chinese company names. If the user provides an English name or translation, convert it to the Chinese official name before calling `biz_fuzzy_search`. ## Supported Data Dimensions - **Company fuzzy search**: Match by short name or full name, returns `entid` - **Basic info**: Legal rep, registered capital, incorporation date, unified social credit code, address, business scope, industry - **Shareholders**: Names, shareholding ratios, contribution amounts, types - **Executives**: Directors, supervisors, senior management, legal representative - **Outbound investments**: Portfolio companies with shareholding and status - **Registry changes**: Historical changes to legal rep, address, capital, etc. - **Court enforcement** (被执行人): Forced execution records - **Dishonest debtors** (失信被执行人): Blacklist records - **Consumption restrictions** (限制高消费): Court-ordered consumption bans - **Abnormal operations** (经营异常): Regulatory abnormal operation listings - **Serious violations** (严重违法): Serious illegal conduct records - **Administrative penalties** (行政处罚): Regulatory fines and penalties - **Due diligence report**: Structured report synthesizing all available dimensions Current capabilities are defined in `tools.json`; field details in `references/field_definitions_*.md`. ## Discovery Scope This skill covers any China company search or risk check need. If a user asks about a dimension not yet supported (e.g., patents, tenders, job listings), still trigger this skill — but clearly state "this dimension is not yet supported" during execution. Do not fabricate results. ## Usage Workflow 1. Identify what dimension the user needs before searching for the company. 2. Use `discover` to find the relevant tool (e.g., `"shareholder structure"`, `"administrative penalty"`). 3. Confirm the tool exists, then call `biz_fuzzy_search` to get the `entid`. 4. **Entity disambiguation (required)**: If the company name is ambiguous or abbreviated, ask the user to confirm which company before proceeding. Never assume uniqueness. 5. All dimension queries use `entid` — do not pass company names or credit codes directly. 6. For multi-dimension requests (due diligence, risk screening), resolve the entity once and reuse the same `entid`. 7. **Person-to-company lookup**: If the user provides a person's name (e.g., "what companies does Elon Musk own"), interpret it as "companies where this person is the legal representative." Clarify if there are multiple people with the same name. ## Output Rules - Only show real data returned by the API — never fabricate - Do not expose `entid` to the user — it is an internal query ID - Always use the full official registered company name, not abbreviations - Clearly separate Fengniao structured data from any WebSearch supplementary content - If a dimension has no records, state "no records found" explicitly - If a dimension is not yet supported, state "not supported in the current version" ## Error Recovery - `code=9999`, not quota-related: check if the built-in key is valid, or configure a private `FN_API_KEY` - `code=9999` + "访问已达上限": daily public quota exhausted — use a private key or retry tomorrow - `code=8888`: usually invalid `entid` or params — re-fetch the company entity and retry - `code=20000` + no records: this company has no records for this dimension - `discover` no match: try synonyms; if still no match, the dimension is not yet supported Troubleshooting priority: API key / quota / network → entity resolution (entid) → update skill (`openclaw skills update china-company-search-fengniao-en`). ## Quick Start ```bash # 1. Discover tools by dimension node scripts/tool.mjs discover "shareholder structure" # 2. Fuzzy search for a company (must use Chinese name) node scripts/tool.mjs call biz_fuzzy_search --params '{"key":"腾讯"}' # 3. Query a dimension using entid node scripts/tool.mjs call biz_shareholders --params '{"entid":"AerjZTfkSh0"}' ```

path: search_aliases.json
content: {
  "企业查询": ["企业模糊搜索", "企业基本信息"],
  "公司查询": ["企业模糊搜索", "企业基本信息"],
  "查公司": ["企业模糊搜索", "企业基本信息"],
  "companysearch": ["企业模糊搜索", "企业基本信息"],
  "companylookup": ["企业模糊搜索", "企业基本信息"],
  "工商查询": ["企业基本信息", "企业工商变更"],
  "businessregistry": ["企业基本信息", "企业工商变更"],
  "企业信息查询": ["企业基本信息"],
  "企业信用查询": ["企业基本信息", "被执行人", "失信被执行人"],
  "企业风险查询": ["被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "corporaterisk": ["被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "corporateriskscreening": ["被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "企业尽调": ["企业基本信息", "企业股东信息", "企业高级职员", "企业对外投资", "企业工商变更", "被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "companyduediligence": ["企业基本信息", "企业股东信息", "企业高级职员", "企业对外投资", "企业工商变更", "被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "kyb": ["企业基本信息", "企业股东信息", "企业高级职员", "被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "背景调查": ["企业基本信息", "企业股东信息", "企业高级职员", "被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "companybackground": ["企业基本信息", "企业股东信息", "企业高级职员", "被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "companybackgroundcheck": ["企业基本信息", "企业股东信息", "企业高级职员", "被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "对方背景": ["企业基本信息", "企业股东信息", "企业高级职员", "被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "签合同": ["企业基本信息", "企业股东信息", "企业高级职员", "被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "供应商": ["企业基本信息", "企业股东信息", "企业高级职员", "企业对外投资", "被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "supplierverification": ["企业基本信息", "企业股东信息", "企业高级职员", "企业对外投资", "被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "counterpartyrisk": ["被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "风险筛查": ["被执行人", "失信被执行人", "限制高消费", "经营异常", "严重违法", "行政处罚"],
  "面试前": ["企业基本信息", "企业高级职员"],
  "目标公司": ["企业基本信息", "企业高级职员", "企业股东信息"],
  "查老板": ["企业高级职员", "企业基本信息"],
  "老板": ["企业高级职员", "企业基本信息"],
  "查法人": ["企业基本信息", "企业高级职员"],
  "legalrepresentative": ["企业基本信息", "企业高级职员"],
  "注册号": ["企业基本信息"],
  "工商注册号": ["企业基本信息"],
  "统一社会信用代码": ["企业基本信息"],
  "信用代码": ["企业基本信息"],
  "法人": ["企业基本信息"],
  "法定代表人": ["企业基本信息"],
  "营业期限": ["企业基本信息"],
  "官网": ["企业基本信息"],
  "电话": ["企业基本信息"],
  "邮箱": ["企业基本信息"],
  "股权结构": ["企业股东信息"],
  "股东结构": ["企业股东信息"],
  "董监高": ["企业高级职员"],
  "高管": ["企业高级职员"],
  "对外投资": ["企业对外投资"],
  "工商变更": ["企业工商变更"],
  "法人变更": ["企业工商变更"],
  "地址变更": ["企业工商变更"],
  "被执行": ["被执行人"],
  "执行标的": ["被执行人"],
  "老赖": ["失信被执行人"],
  "失信": ["失信被执行人"],
  "限消": ["限制高消费"],
  "限高": ["限制高消费"],
  "经营异常": ["经营异常"],
  "严重违法": ["严重违法"],
  "行政处罚": ["行政处罚"],
  "处罚机关": ["行政处罚"]
}


path: SETUP.md
content: # Fengniao China Company Search — Setup Guide

## Works Out of the Box

No configuration required. A built-in public API key is included and used automatically.

If you have a paid Fengniao account, set your private key as an environment variable `FN_API_KEY` — it will take priority over the built-in key.

## Prerequisites

- Node.js 18 or higher

Verify your Node.js version:

```bash
node -v
```

## Quota

The built-in public key has a daily usage limit (200 calls). Current quota and remaining usage are shown at https://www.riskbird.com/skills.

When the API returns `code=9999` with a message containing "访问已达上限", the daily public quota is exhausted. Configure a private key or retry the next day.

## Setting a Private API Key (Optional)

Set temporarily in the current terminal session (expires when terminal closes).

**macOS / Linux**

```bash
export FN_API_KEY="your-api-key"
```

**Windows PowerShell**

```powershell
$env:FN_API_KEY = "your-api-key"
```

**Windows CMD**

```cmd
set FN_API_KEY=your-api-key
```

## Local Verification

```bash
# 1. Test tool discovery (no network required)
node scripts/tool.mjs discover "company shareholders"

# 2. Test fuzzy search (requires API key, must use Chinese company name)
node scripts/tool.mjs call biz_fuzzy_search --params '{"key":"腾讯"}'

# 3. Test dimension query (use entid from previous step)
node scripts/tool.mjs call biz_basic_info --params '{"entid":"AerjZTfkSh0"}'
```

## Security Boundaries

- Credentials are read from the environment variable `FN_API_KEY` (or built-in fallback)
- Only reads `tools.json` and `references/` files within the skill package
- Does not read user home directory, agent config, or shell startup files
- Does not write any local files
- API credentials are sent via URL parameter `apikey`, not HTTP headers

## Production Note

The public release uses the production endpoint `https://m.riskbird.com/prod-qbb-api`. If you need to switch endpoints for a private deployment, fork the skill and modify the source — do not expose switchable endpoints in a public marketplace package.


path: tools.json
content: [{"tool_id":"biz_fuzzy_search","name":"企业模糊搜索","description":"根据关键词模糊搜索匹配企业，返回 entid（企业唯一标识）、企业全称、经营状态。必须先调此接口获取 entid，才能查询其他维度。注意：英文搜索无效，必须传中文名称","endpoint":"/skills/searchHint","method":"GET","params":[{"name":"key","type":"string","required":true,"description":"搜索关键词，必须是中文，如企业简称或全称"}],"keywords":["企业搜索","公司搜索","主体搜索","企业查找","公司查找","简称查全称","企业匹配","公司全称","entid"]},{"tool_id":"biz_basic_info","name":"企业基本信息","description":"查询企业工商基本信息，包含注册资本、法定代表人、成立日期、注册地址、经营范围、经营状态、行业分类、统一社会信用代码","endpoint":"/skills/dataDimension?version=B1","method":"GET","params":[{"name":"entid","type":"string","required":true,"description":"企业唯一标识，通过 biz_fuzzy_search 获取"}],"keywords":["基本信息","工商信息","工商基本信息","工商登记","注册信息","注册号","工商注册号","统一社会信用代码","信用代码","法定代表人","法人","成立日期","注册地址","地址","经营范围","经营状态","行业分类","注册资本","官网","电话","邮箱","曾用名","企业简介","营业期限","工商注册信息","信用信息","登记机关"]},{"tool_id":"biz_shareholders","name":"企业股东信息","description":"查询企业股东名单、持股比例、认缴出资额、股东类型（自然人/法人）、认缴日期、出资方式","endpoint":"/skills/dataDimension?version=B2","method":"GET","params":[{"name":"entid","type":"string","required":true,"description":"企业唯一标识，通过 biz_fuzzy_search 获取"}],"keywords":["股东","股东信息","股权结构","股东结构","持股比例","出资比例","认缴出资","股东名单","投资人","股东持股","股权比例","认缴金额"]},{"tool_id":"biz_executives","name":"企业高级职员","description":"查询企业董事、监事、高管及法定代表人，包含姓名、职务（可多职务）、持股比例","endpoint":"/skills/dataDimension?version=B3","method":"GET","params":[{"name":"entid","type":"string","required":true,"description":"企业唯一标识，通过 biz_fuzzy_search 获取"}],"keywords":["主要人员","高管","董监高","董事","监事","高级职员","法定代表人","管理层","核心人员","董监高信息","管理人员","企业高管"]},{"tool_id":"biz_investments","name":"企业对外投资","description":"查询企业对外投资的被投公司列表，包含持股比例、被投企业经营状态、成立日期、注册资本、法定代表人","endpoint":"/skills/dataDimension?version=B4","method":"GET","params":[{"name":"entid","type":"string","required":true,"description":"企业唯一标识，通过 biz_fuzzy_search 获取"}],"keywords":["对外投资","投资关系","被投企业","参股公司","投资企业","投资版图","对外参股","投资公司","被投公司"]},{"tool_id":"biz_changes","name":"企业工商变更","description":"查询企业工商登记变更历史，包含变更日期、变更事项（法人变更/地址变更/注册资本变更等）、变更前后内容","endpoint":"/skills/dataDimension?version=B5","method":"GET","params":[{"name":"entid","type":"string","required":true,"description":"企业唯一标识，通过 biz_fuzzy_search 获取"}],"keywords":["工商变更","变更记录","变更历史","法人变更","地址变更","注册资本变更","工商变更记录","变更事项","变更前后","历史变更"]},{"tool_id":"risk_executed","name":"被执行人","description":"查询企业作为被执行人的法院强制执行记录，包含案号、执行法院、立案时间、执行标的金额（单位：万元）、案件状态","endpoint":"/skills/dataDimension?version=C2","method":"GET","params":[{"name":"entid","type":"string","required":true,"description":"企业唯一标识，通过 biz_fuzzy_search 获取"}],"keywords":["被执行","被执行人","执行记录","强制执行","执行标的","法院执行","案号","执行法院","立案时间"]},{"tool_id":"risk_dishonest","name":"失信被执行人","description":"查询企业被列入失信被执行人（老赖）名单的记录，包含失信行为类型、履行情况、列入日期、执行法院、是否已下架","endpoint":"/skills/dataDimension?version=C3","method":"GET","params":[{"name":"entid","type":"string","required":true,"description":"企业唯一标识，通过 biz_fuzzy_search 获取"}],"keywords":["失信","失信被执行人","老赖","失信记录","失信名单","履行情况"]},{"tool_id":"risk_limit_consumption","name":"限制高消费","description":"查询企业被法院限制高消费记录，包含案号、作出限制的法院、立案日期、申请人列表、关联法定代表人","endpoint":"/skills/dataDimension?version=C4","method":"GET","params":[{"name":"entid","type":"string","required":true,"description":"企业唯一标识，通过 biz_fuzzy_search 获取"}],"keywords":["限高","限制高消费","高消费限制","限消","限高记录","限制消费"]},{"tool_id":"risk_abnormal_operation","name":"经营异常","description":"查询企业被列入经营异常名录的记录，包含列入原因、列入日期、列入机关、移出原因、移出日期","endpoint":"/skills/dataDimension?version=D1","method":"GET","params":[{"name":"entid","type":"string","required":true,"description":"企业唯一标识，通过 biz_fuzzy_search 获取"}],"keywords":["经营异常","异常经营","异常名录","经营异常名录","列入异常","移出异常"]},{"tool_id":"risk_serious_illegal","name":"严重违法","description":"查询企业被列入严重违法失信名单的记录，包含列入原因、列入日期、移出情况","endpoint":"/skills/dataDimension?version=D2","method":"GET","params":[{"name":"entid","type":"string","required":true,"description":"企业唯一标识，通过 biz_fuzzy_search 获取"}],"keywords":["严重违法","严重违法失信","违法失信","黑名单","违法名单","严重失信"]},{"tool_id":"risk_admin_penalty","name":"行政处罚","description":"查询企业受到的行政处罚记录，包含处罚机关、处罚日期、违法事实、处罚金额（单位：元，展示时÷10000转万元）、处罚依据","endpoint":"/skills/dataDimension?version=D11","method":"GET","params":[{"name":"entid","type":"string","required":true,"description":"企业唯一标识，通过 biz_fuzzy_search 获取"}],"keywords":["行政处罚","处罚记录","罚款","处罚机关","违法事实","处罚决定书","处罚文号","处罚日期"]}]

path: scripts/client.mjs
content: import { getApiKey, BASE_URL } from "./env.mjs";
import { readFileSync } from "fs";
import { fileURLToPath } from "url";
import { dirname, join } from "path";

// 加载工具注册表（tools.json 与 scripts/ 同级上一层）
const __dir = dirname(fileURLToPath(import.meta.url));
const TOOLS = JSON.parse(
  readFileSync(join(__dir, "../tools.json"), "utf8")
);
const SEMANTIC_QUERY_ALIASES = JSON.parse(
  readFileSync(join(__dir, "../search_aliases.json"), "utf8")
);

function normalizeSearchText(input) {
  return String(input || "")
    .toLowerCase()
    .replace(/[\s"'“”‘’，。、《》【】（）()、:：;；!?！？-]+/g, "")
    .trim();
}

function buildQueryVariants(input) {
  const normalized = normalizeSearchText(input);
  const variants = new Set([normalized]);

  if (normalized.startsWith("企业")) {
    variants.add(normalized.slice(2));
  }
  if (normalized.startsWith("公司")) {
    variants.add(normalized.slice(2));
  }

  for (const variant of [...variants]) {
    if (variant.endsWith("查询")) {
      variants.add(variant.slice(0, -2));
    }
    if (variant.endsWith("信息查询")) {
      variants.add(variant.slice(0, -4));
    }
    for (const [alias, mappedVariants] of Object.entries(SEMANTIC_QUERY_ALIASES)) {
      if (variant.includes(alias)) {
        mappedVariants.forEach((mapped) => variants.add(normalizeSearchText(mapped)));
      }
    }
  }

  return [...variants].filter((item) => item.length >= 2);
}

function buildSearchTerms(input) {
  const text = normalizeSearchText(input);
  const terms = new Set();

  for (const asciiWord of text.match(/[a-z0-9]+/g) || []) {
    terms.add(asciiWord);
  }

  for (const segment of text.match(/[\u4e00-\u9fa5]+/g) || []) {
    for (let size = 2; size <= Math.min(segment.length, 4); size++) {
      for (let i = 0; i <= segment.length - size; i++) {
        const term = segment.slice(i, i + size);
        if (!["企业", "公司", "信息", "查询"].includes(term)) {
          terms.add(term);
        }
      }
    }
  }

  return [...terms];
}

/** 本地关键词匹配，替代后端 /skill/match */
export function discover(query, { limit = 5 } = {}) {
  const queryVariants = buildQueryVariants(query);
  const queryTerms = buildSearchTerms(query);
  const scored = TOOLS.map((tool) => {
    const normalizedName = normalizeSearchText(tool.name);
    const normalizedDescription = normalizeSearchText(tool.description || "");
    const normalizedKeywords = (tool.keywords || [])
      .map((keyword) => normalizeSearchText(keyword))
      .filter(Boolean);
    const normalizedText = normalizeSearchText(
      [tool.name, tool.description, ...(tool.keywords || [])].filter(Boolean).join(" ")
    );

    let similarity = 0;
    for (const variant of queryVariants) {
      if (!variant) continue;
      if (normalizedName === variant) {
        similarity = Math.max(similarity, 1);
      } else if (normalizedKeywords.includes(variant)) {
        similarity = Math.max(similarity, 0.98);
      } else if (normalizedName.includes(variant)) {
        similarity = Math.max(similarity, 0.95);
      } else if (normalizedKeywords.some((keyword) => keyword.includes(variant) || variant.includes(keyword))) {
        similarity = Math.max(similarity, 0.9);
      } else if (normalizedDescription.includes(variant) || normalizedText.includes(variant)) {
        similarity = Math.max(similarity, 0.85);
      }
    }

    if (similarity < 0.85) {
      const hits = queryTerms.filter(
        (term) => normalizedText.includes(term) || normalizedKeywords.some((keyword) => keyword.includes(term))
      );
      const uniqueHits = new Set(hits);
      const overlap =
        queryTerms.length === 0 || uniqueHits.size === 0
          ? 0
          : Math.min(uniqueHits.size / Math.max(Math.min(queryTerms.length, 5), 1), 0.84);
      similarity = Math.max(similarity, overlap);
    }

    return { similarity, tool };
  });

  scored.sort((a, b) => b.similarity - a.similarity);

  return {
    tools: scored
      .filter((s) => s.similarity > 0)
      .slice(0, limit)
      .map(({ similarity, tool }) => ({
        tool_id: tool.tool_id,
        name: tool.name,
        description: tool.description,
        similarity: parseFloat(similarity.toFixed(4)),
        params: tool.params,
      })),
  };
}

/** 直接调用真实 API，替代后端 /gateway/{tool_id} */
export async function call(toolId, params = {}) {
  const apiKey = await getApiKey();
  if (!apiKey) throw new Error("API Key 未配置，请先访问 https://www.riskbird.com/skills，通过页面中部复制按钮获取当前页面展示的公用 Key，并设置环境变量 FN_API_KEY");

  const tool = TOOLS.find((t) => t.tool_id === toolId);
  if (!tool) throw new Error(`未找到工具: ${toolId}`);

  const url = new URL(BASE_URL + tool.endpoint);
  // 风鸟接口实际要求通过 URL 参数 apikey 传递鉴权信息，而不是请求头。
  url.searchParams.set("apikey", apiKey);

  const options = {
    method: tool.method,
    headers: {
      "Accept": "application/json",
    },
  };

  if (tool.method === "GET") {
    Object.entries(params).forEach(([k, v]) => {
      if (v != null) url.searchParams.set(k, v);
    });
  } else {
    options.headers["Content-Type"] = "application/json";
    options.body = JSON.stringify(params);
  }

  const res = await fetch(url.toString(), options);
  const body = await res.json().catch(() => ({ error: "non-JSON response" }));

  if (!res.ok) {
    throw new Error(`API 错误 ${res.status}: ${JSON.stringify(body)}`);
  }

  // 直接透传原始响应，结构为 { code, msg, data, success }
  return body;
}


path: scripts/env.mjs
content: const ENV_KEY = "FN_API_KEY";
// 公用账号 Key，每日限额 200 次；用户配置了自己的 Key 时优先使用
const BUILTIN_KEY = "eab076c5-b108-4a3f-b2fb-d97039b1a447";
export const BASE_URL = "https://m.riskbird.com/prod-qbb-api";

export async function getApiKey() {
  const key = process.env[ENV_KEY];
  if (key && key !== "YOUR_API_KEY") return key;
  return BUILTIN_KEY;
}


path: scripts/tool.mjs
content: #!/usr/bin/env node
// tool.mjs - CLI entrypoint for china-company-search-fengniao skill
import { discover, call } from "./client.mjs";

const [, , command, ...args] = process.argv;

function printHelp() {
  console.log(`
Fengniao Search Tool CLI

Commands:
  discover <query> [--limit N]
      Find tools matching a natural-language query.
      Returns tool candidates and similarity scores.

  call <tool_id> [--params '{"key":"value"}'] [--params "key=value&key2=value2"]
      Call a tool by its ID with optional params.
      --params '{"key":"value"}' accepts JSON or key=value pairs joined by , or &
      --params "key=value&key2=value2" can be repeated and is recommended on Windows.

Options:
  --help    Show this help message

Examples:
  node tool.mjs discover "企业股东信息" --limit 3
  node tool.mjs call biz_fuzzy_search --params '{"key":"腾讯"}'
  node tool.mjs call biz_shareholders --params "entid=AerjZTfkSh0"
`);
}

function isPlainObject(value) {
  return value != null && typeof value === "object" && !Array.isArray(value);
}

function parseScalar(value) {
  const raw = value.trim();
  if (raw === "true") return true;
  if (raw === "false") return false;
  if (raw === "null") return null;
  if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(raw)) return Number(raw);

  if (
    (raw.startsWith("{") && raw.endsWith("}")) ||
    (raw.startsWith("[") && raw.endsWith("]")) ||
    (raw.startsWith('"') && raw.endsWith('"'))
  ) {
    try {
      return JSON.parse(raw);
    } catch {
      // Keep the raw string when partial JSON is provided.
    }
  }

  return raw;
}

function parseKeyValueParams(raw) {
  const entries = raw
    .split(/[&,]/)
    .map((entry) => entry.trim())
    .filter(Boolean);

  if (entries.length === 0) {
    throw new Error("empty params");
  }

  const params = {};
  for (const entry of entries) {
    const eqIndex = entry.indexOf("=");
    if (eqIndex <= 0) {
      throw new Error(`invalid param entry: ${entry}`);
    }
    const key = entry.slice(0, eqIndex).trim();
    const value = entry.slice(eqIndex + 1);
    params[key] = parseScalar(value);
  }

  return params;
}

function parseParams(raw) {
  try {
    const parsed = JSON.parse(raw);
    if (!isPlainObject(parsed)) {
      throw new Error("params JSON must be an object");
    }
    return parsed;
  } catch {
    try {
      return parseKeyValueParams(raw);
    } catch {
      throw new Error(
        '--params must be valid JSON or key=value&key2=value2 pairs such as entid=AerjZTfkSh0'
      );
    }
  }
}

function parseFlags(args) {
  const flags = {};
  for (let i = 0; i < args.length; i++) {
    if (args[i] === "--limit" && args[i + 1]) {
      flags.limit = parseInt(args[++i], 10);
    } else if (args[i] === "--params" && args[i + 1]) {
      try {
        flags.params = {
          ...(flags.params || {}),
          ...parseParams(args[++i]),
        };
      } catch (err) {
        console.error(`Error: ${err.message}`);
        process.exit(1);
      }
    } else if (args[i] === "--param" && args[i + 1]) {
      try {
        flags.params = {
          ...(flags.params || {}),
          ...parseKeyValueParams(args[++i]),
        };
      } catch {
        console.error(
          `Error: --param must use key=value format, received: ${args[i]}`
        );
        process.exit(1);
      }
    }
  }
  return flags;
}

async function main() {
  if (!command || command === "--help") {
    printHelp();
    process.exit(0);
  }

  try {
    if (command === "discover") {
      const query = args[0];
      if (!query) {
        console.error("Error: discover requires a query string");
        process.exit(1);
      }
      const flags = parseFlags(args.slice(1));
      const result = await discover(query, flags);
      console.log(JSON.stringify(result, null, 2));

    } else if (command === "call") {
      const toolId = args[0];
      if (!toolId) {
        console.error("Error: call requires a tool_id");
        process.exit(1);
      }
      const flags = parseFlags(args.slice(1));
      const result = await call(toolId, flags.params || {});
      console.log(JSON.stringify(result, null, 2));

    } else {
      console.error(`Unknown command: ${command}`);
      printHelp();
      process.exit(1);
    }
  } catch (err) {
    console.error("Error:", err.message);
    process.exit(1);
  }
}

main();


path: references/due_diligence_report.md
content: # 企业尽调报告生成指南

当用户明确要求生成企业尽调报告（如"帮我出一份XX的尽调报告"、"生成企业背景报告"）时，按以下规范执行。尽调报告是当前已提供的一种报告模板，识别到该意图后可直接进入报告流程；未来若新增其他报告模板，应与本文件并列维护。

## 查询维度与顺序

依次查询以下维度（先确认主体，再按固定支持维度执行；如工具名或 tool_id 不确定，再先 discover 后 call，统一使用 entid）：

1. 企业模糊搜索 → 确认 entid
2. 企业基本信息（B1）→ 工商注册核心字段
3. 股东信息（B2）→ 穿透股东及持股比例
4. 主要人员（B3）→ 法人、董监高姓名职务
5. 对外投资（B4）→ 被投企业列表
6. 变更记录（B5）→ 近期重大工商变更
7. 经营异常（D1）→ 是否列入异常名录
8. 行政处罚（D11）→ 处罚条数及详情
9. 被执行人（C2）→ 法院强制执行记录
10. 失信被执行人（C3）→ 老赖记录
11. 限制高消费（C4）→ 限高记录

## 报告输出格式

严格按以下模板输出，不要自由发挥结构：

```
# 企业尽调报告｜{企业简称}

> 查询日期：{YYYY年MM月DD日}
> 企业全称：{entName}
> 统一社会信用代码：{uniscid}

---

## ⚡ 风险评级：{低风险 / 需关注 / 高风险}

{1-2句综合判断，说明评级依据}

---

## 一、基本信息

| 项目 | 内容 |
|------|------|
| 法定代表人 | |
| 注册资本 | |
| 成立日期 | |
| 经营状态 | |
| 注册地址 | |
| 所属行业 | |

---

## 二、股权结构

{股东列表，按持股比例从高到低，标注自然人/法人}

---

## 三、主要人员

| 姓名 | 职务 |
|------|------|

---

## 四、对外投资

{有投资则列表，含被投企业名称/持股比例/经营状态；无则写"未查询到对外投资记录"}

---

## 五、近期变更

{最近3条变更记录，含变更事项/变更前后内容/变更日期；无则写"近期无重大工商变更"}

---

## 六、风险信息

### 经营异常
{条数} 条｜{有记录则列出列入原因和日期；无则写"未列入经营异常名录"}

### 行政处罚
{条数} 条｜{按风险条数表述规则给出风险提示}
{最新5条：日期 / 处罚机关 / 违法类型 / 处罚结果}

### 被执行记录
{条数} 条｜{有则列出案号/法院/立案日期/标的金额（万元）；无则写"无被执行记录"}

### 失信记录
{条数} 条｜{有则列出详情；无则写"未被列入失信被执行人名单"}

### 限制高消费
{条数} 条｜{有则列出案号/法院/申请人；无则写"无限高记录"}

---

## 七、综合评估

{3-5句话，综合上述所有维度，指出主要风险点或亮点，给出明确的合作建议}

---

📌 以上数据来源于风鸟企业信息查询平台，当前数据维度持续拓展中，更多企业信息及功能详见风鸟官网：https://www.riskbird.com/
```

## 注意事项

- 风险评级必须在报告最顶部，用户拿到报告第一眼看结论
- 所有"无记录"情况必须明确写出，不能留空——空白让读者无法判断是没查还是没有
- 对外投资超过5家时，只展示前5家并注明"共{N}家，展示前5家"
- 变更记录只展示最近1年内的，更早的忽略（API 不支持时间过滤，由 Claude 根据返回数据中的变更日期自行判断过滤）
- 报告生成完毕后不需要额外解释，直接输出完整报告


path: references/field_definitions_common_bizinfo.md
content: # 风鸟 API 字段定义 — 通用结构 & 工商维度（searchHint / B1–B5）

> 版本：第一版 | 基于 API 实测验证 | 最后更新：2026-03-25

---

## 通用响应结构

### 响应信封（所有接口共用）

| 字段 | 类型 | 说明 |
|------|------|------|
| `code` | number | 状态码。`20000`=成功，`3000000`=无数据，`8888`=业务参数错误，`9999`=系统错误 |
| `msg` | string | 状态消息 |
| `success` | boolean | 是否成功 |
| `data` | object/array/null | 业务数据，结构因接口而异 |

### 三种 data 形态

**① searchHint** — `data` 直接是数组：
```json
{ "code": 20000, "data": [ { "entid": "...", "entName": "..." } ] }
```

**② dataDimension 列表类（B2/B3/B4/B5/C2/C3/C4/D1/D11）** — `data` 是分页对象：
```json
{
  "data": {
    "totalCount": 140,
    "exportCount": 140,
    "filterData": {},
    "aggregation": null,
    "extraInfo": {},
    "apiData": [ ... ]
  }
}
```

**③ dataDimension 单体类（B1）** — `apiData` 是对象而非数组：
```json
{
  "data": {
    "totalCount": 0,
    "apiData": { "entName": "...", "uniscid": "..." }
  }
}
```

### 分页包装字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `totalCount` | number | 符合条件的总记录数。**判断"是否有风险"只看此值**，不看 `apiData` 长度。B1 固定为 `0`（单体查询无分页概念） |
| `exportCount` | number | 可导出数量，通常等于 `totalCount` |
| `filterData` | object/null | 过滤条件。列表类为 `{}`，B1 为 `null` |
| `aggregation` | null | 聚合数据，当前固定为 `null` |
| `extraInfo` | object/null | 额外信息。列表类为 `{}`，B1 为 `null` |
| `apiData` | array/object | 数据本体。列表类为数组（**最多 5 条**，分页参数无效），B1 为单个对象 |

> ⚠️ **D2 严重违法例外**：数据位于 `data.SERILLEGAL` 数组，而非 `data.apiData`。

---

## searchHint — 模糊搜索

`GET /skills/searchHint?key=<搜索词>&[REDACTED_SECRET]`（API Key 通过 URL 参数 `apikey` 传递）

返回：`data` 为数组，最多 5 条，按相关度排序，不支持分页。

| 字段 | 类型 | 说明 |
|------|------|------|
| `entid` | string | **企业唯一标识**，后续所有维度查询的必传参数 |
| `entName` / `ENTNAME` | string | 企业全称 |
| `entStatus` / `ENTSTATUS` | string | 经营状态（在营 / 注销 / 吊销等） |
| `entStatusColor` | string | 状态颜色标记（`green`=在营，`red`=异常） |
| `entPic` | string | 企业 Logo 图片 URL |
| `ENTNAME_SEARCH` | string | 带高亮 `<span>` 标签的企业名称（仅用于前端展示，**禁止用于逻辑处理**） |
| `highlightNameType` | string | 命中字段类型。`"公司名称"`=精准匹配优先级最高；`"股东"`=匹配的是股东名；`"企业别名"` / `"统一社会信用代码"` 等 |
| `dataType` | string | 固定值 `"company_basic"` |

---

## B1 工商-基本信息

`version=B1` · `apiData` 为**对象**（非数组） · `totalCount` 固定为 `0`

### 核心工商字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `entName` | string | 企业全称 |
| `entid` | string | 企业唯一标识 |
| `uniscid` | string | 统一社会信用代码 |
| `regNo` | string | 工商注册号 |
| `entStatus` | string | 经营状态（在营 / 注销 / 吊销等） |
| `entType` | string | 企业类型（如"有限责任公司"） |
| `regCap` | string | 注册资本金额（万元，不含单位） |
| `regConcat` | string | 注册资本（含货币单位，如"6500 万元人民币"） |
| `esDate` | string | 成立日期（`YYYY-MM-DD`） |
| `opFrom` | string | 经营开始日期 |
| `opTo` | string | 经营截止日期（"长期"表示长期有效） |
| `candate` | string/null | 注销日期（未注销为 `null`） |
| `revdate` | string/null | 吊销日期（未吊销为 `null`） |
| `dom` | string | 注册地址 |
| `regionName` | string | 注册地区（省市区，如"广东省深圳市南山区"） |
| `regOrg` | string | 登记机关 |
| `nicName` | string | 行业分类全称（国标分类，含上级分类） |
| `opScope` | string | 经营范围（含一般经营和许可经营，以"，许可经营项目是："分隔） |

### 法定代表人字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `personId` | string | 法定代表人 ID |
| `personName` | string | 法定代表人姓名 |
| `personTypeCode` | string | 法人类型编码（`"person"` = 自然人） |
| `personEntCount` | number | 法人关联企业数量 |
| `legalPersonType` | string | 法人身份名称（如"法定代表人"） |

### 其他信息字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `entNameEn` | string | 企业英文名称 |
| `historyName` | string | 企业曾用名 |
| `entIntro` | string | 企业简介 |
| `website` | string | 企业官网 |
| `isBranch` | number | 是否分支机构（`0`=主体，`1`=分支） |
| `tags` | array | 企业标签（如 `["大型","500强","国家高新技术企业"]`） |
| `riskTags` | array/null | 风险标签（如 `["被执行人"]`，无风险时为 `null` 或空） |
| `yrEnpNum` | string | 年报员工人数 |
| `tel` | string | 联系电话（第一个） |
| `email` | string | 联系邮箱（第一个） |

> 说明：官网、电话、邮箱属于 **B1 企业基本信息** 中的基础字段，不是单独的独立维度。用户询问这些基础联系方式时，应先按“企业基本信息”维度处理，再从 B1 返回结果中提取对应字段。

---

## B2 工商-股东信息

`version=B2` · `apiData` 为数组，每条为一个股东记录

| 字段 | 类型 | 说明 |
|------|------|------|
| `shaId` | string | 股东 ID |
| `shaName` | string | 股东姓名 / 名称 |
| `shaTypeCode` | string | 股东类型编码（`"person"`=自然人，`"ent"`=法人） |
| `invType` | string | 投资类型（`"自然人股东"` / `"法人股东"`） |
| `fundedRatio` | string | 持股比例（含 `%`，如 `"54.2857%"`） |
| `subConAm` | string | 认缴出资额（含单位，如 `"3,528.5705 万元人民币"`） |
| `conDate` | string | 认缴日期（`YYYY-MM-DD`） |
| `outState` | boolean | 是否退出（`false`=未退出） |
| `riskTags` | array | 股东风险标签 |

---

## B3 工商-高级职员

`version=B3` · `apiData` 为数组，包含董监高及法定代表人

| 字段 | 类型 | 说明 |
|------|------|------|
| `personId` | string | 人员 ID |
| `personName` | string | 姓名 |
| `position` | string | 职务（多个职务以 `";"` 分隔，如 `"董事长;总经理"`） |
| `fundedRatio` | string | 持股比例（无持股则为 `"-"`） |
| `riskTags` | array | 人员风险标签 |

---

## B4 工商-对外投资

`version=B4` · `apiData` 为数组，每条为一个被投企业

| 字段 | 类型 | 说明 |
|------|------|------|
| `entid` | string | 被投企业 ID |
| `entName` | string | 被投企业名称 |
| `entStatus` | string | 被投企业经营状态 |
| `funderRatio` | string | 持股比例（含 `%`） |
| `subConAm` | string | 认缴出资额（含单位） |
| `esDate` | string | 被投企业成立日期（`YYYY-MM-DD`） |
| `personName` | string | 被投企业法定代表人姓名 |

---

## B5 工商-工商变更信息

`version=B5` · `apiData` 为数组，按变更时间倒序

| 字段 | 类型 | 说明 |
|------|------|------|
| `altDate` | string | 变更日期（`YYYY-MM-DD`） |
| `altItemCodeCn` | string | 变更事项名称（中文） |
| `altAf` | string | 变更后内容（纯文本，**用于逻辑处理**） |
| `altBe` | string | 变更前内容（纯文本，**用于逻辑处理**） |
| `altAfClean` | string | 变更后内容（含高亮 HTML 标签，**仅用于前端展示，禁止用于逻辑处理**） |
| `altBeClean` | string | 变更前内容（含高亮 HTML 标签，**仅用于前端展示，禁止用于逻辑处理**） |


path: references/field_definitions_legal.md
content: # 风鸟 API 字段定义 — 法律诉讼维度（C2 / C3 / C4）

> 版本：第一版 | 基于 API 实测验证 | 最后更新：2026-03-25

---

## C2 法律诉讼-被执行人

`version=C2` · `apiData` 为数组

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 记录 ID |
| `entityName` | string | 被执行人名称 |
| `entid` | string | 企业 ID |
| `courtName` | string | 执行法院名称 |
| `caseCode` | string | 案号 |
| `caseState` | string | 案件状态（如 `"执行中"`） |
| `execMoney` | string/null | 执行标的金额（⚠️ 单位：**万元**，可为 `null`） |
| `execMoneyFormat` | string/null | 格式化金额（千位分隔，可为 `null`） |
| `regDate` | string | 立案日期（`YYYY-MM-DD`） |
| `gistId` | string/null | 执行依据文号（可为 `null`） |
| `caseUniqueId` | string | 案件唯一标识（系统内部去重用） |

---

## C3 法律诉讼-失信被执行人

`version=C3` · `apiData` 为数组

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 记录 ID |
| `entid` | string | 企业 ID |
| `entityName` | string | 失信被执行人名称 |
| `caseCode` | string | 案号 |
| `courtName` | string | 执行法院名称 |
| `gistId` | string | 执行依据文号（生效法律文书号） |
| `gistUnit` | string | 作出执行依据的单位（法院名称） |
| `regDate` | string | 立案日期（`YYYY-MM-DD`） |
| `publishDate` | string | 数据发布日期（`YYYY-MM-DD`） |
| `duty` | string | 被执行人义务（判决内容摘要，部分为 `"."` 或 `"="` 占位） |
| `performance` | string | 履行情况（如 `"全部未履行"`） |
| `performedPart` | string | 已履行部分说明 |
| `unperformedPart` | string | 未履行部分说明 |
| `disruptTypeName` | string | 失信行为类型（如 `"被执行人无正当理由拒不履行执行和解协议"`） |
| `partyTypeName` | string | 当事人类型（`"法人"` / `"自然人"`） |
| `status` | string | 记录状态（`"未下架"` 表示仍在失信名单上） |
| `caseUniqueId` | string | 案件唯一标识 |

---

## C4 法律诉讼-限制高消费

`version=C4` · `apiData` 为数组

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 记录 ID |
| `entid` | string | 企业 ID |
| `entName` | string | 被限制企业名称 |
| `caseCode` | string | 案号 |
| `court` | string | 作出限制的法院名称 |
| `regDate` | string | 立案日期（`YYYY-MM-DD`） |
| `publishDate` | string | 数据发布日期（`YYYY-MM-DD`） |
| `pdfUrl` | string | 限高令原文 PDF 链接 |
| `applicationList` | array | 申请人列表，每项含 `entityId`、`entityName`、`entityType`、`entityTypeCode` |
| `relatedList` | array | 关联人列表（通常为法定代表人），字段同 `applicationList` |
| `caseUniqueId` | string | 案件唯一标识 |


path: references/field_definitions_risk.md
content: # 风鸟 API 字段定义 — 经营风险维度（D1 / D2 / D11）& 注意事项

> 版本：第一版 | 基于 API 实测验证 | 最后更新：2026-03-25

---

## D1 经营风险-经营异常

`version=D1` · `apiData` 为数组

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 记录 ID |
| `entname` | string/null | 企业名称（企业返回名称，个体户可能返回注册号，部分返回 `null`） |
| `regno` | string | 注册号 |
| `uniscid` | string | 统一社会信用代码（部分主体为空字符串） |
| `inDate` | string | 列入日期（`YYYY-MM-DD`） |
| `inReason` | string | 列入原因 |
| `inRegOrg` | string | 列入机关 |
| `outDate` | string/null | 移出日期（`YYYY-MM-DD`；`null` 表示尚未移出） |
| `outReason` | string | 移出原因（空字符串表示尚未移出） |
| `outRegOrg` | string | 移出机关（空字符串表示尚未移出） |
| `outState` | boolean | ⚠️ 移出状态（`false`=**仍在名单**，`true`=已移出）。含义与字段名相反，注意区分 |

> ⚠️ `totalCount` 是列入总数（含已移出记录）。若需统计"当前仍在名单"的记录数，需过滤 `outState=false` 的条目。

---

## D2 经营风险-严重违法

`version=D2`

> ⚠️ **结构差异**：数据位于 `data.SERILLEGAL` 数组，**不是** `data.apiData`。字段命名为全小写，与 D1 的 camelCase 不同。

| 字段 | 类型 | 说明 |
|------|------|------|
| `entname` | string | 企业名称 |
| `regno` | string | 注册号 |
| `uniscid` | string | 统一社会信用代码 |
| `inreason` | string | 列入原因 |
| `indate` | string | 列入日期（`YYYY-MM-DD`） |
| `inorg` | string | 列入机关 |
| `outreason` | string | 移出原因（未移出则为空字符串） |
| `outdate` | string/null | 移出日期（未移出则为 `null`） |
| `outorg` | string | 移出机关（未移出则为空字符串） |

**D2 响应结构示例**：
```json
{
  "data": {
    "totalCount": 8,
    "SERILLEGAL": [
      { "entname": "...", "inreason": "...", "indate": "...", "inorg": "..." }
    ]
  }
}
```

---

## D11 经营风险-行政处罚

`version=D11` · `apiData` 为数组

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 记录 ID |
| `entid` | string | 企业 ID |
| `entName` | string | 企业名称 |
| `docNo` | string | 处罚决定书文号 |
| `penaltyDate` | string | 处罚日期（`YYYY-MM-DD`） |
| `publishDate` | string | 公示日期（`YYYY-MM-DD`） |
| `penaltyOrg` | string | 处罚机关名称 |
| `illegalFact` | string | 违法事实描述（详细） |
| `illegalType` | string | 违法类型分类（如 `"文旅广告"`、`"交通运输"`、`"市场监管"`） |
| `penaltyAm` | string/null | 罚款金额（⚠️ 单位：**元**，如 `"10000.00"`；无罚款时为 `null`） |
| `penaltyResult` | string | 处罚结果完整描述 |
| `penaltyBasis` | string | 处罚依据（法律法规条款） |

---

## 关键注意事项速查

| 注意项 | 说明 |
|--------|------|
| `entid` 是核心标识 | 所有维度查询必须用 `entid`，不支持传企业名称或信用代码 |
| `penaltyAm` 单位是**元** | D11 展示时需 ÷ 10000 换算为万元 |
| `execMoney` 单位是**万元** | C2 直接使用，无需换算。与 D11 单位不同，注意区分 |
| `totalCount=0` 含义 | B1 固定返回 `0`（单体数据无分页概念）；其他维度为 `0` 表示无记录 |
| `altAfClean` / `altBeClean` | 含 HTML `<span>` 标签，仅用于前端高亮展示。**逻辑处理必须用 `altAf` / `altBe`** |
| D1 vs D2 字段命名风格 | D1：camelCase（`inDate` / `inReason`）；D2：全小写（`indate` / `inreason`） |
| D2 数据路径不同 | D2 数据在 `data.SERILLEGAL[*]`，其他维度在 `data.apiData[*]` |
| D1 `outState` 语义 | `false`=仍在名单，`true`=已移出；`totalCount` 含已移出记录，需自行过滤 |
| 分页不可用 | `apiData` 始终返回最多前 5 条。`totalCount > 5` 时应告知用户"共 N 条，展示最新 5 条" |
| 英文搜索无效 | searchHint 不支持英文关键词匹配中文企业名称，必须传中文 |

---

## 错误码速查

| code | 含义 | 触发场景 |
|------|------|---------|
| `20000` | 成功 | 正常响应（含空结果） |
| `3000000` | 暂时没有找到相关数据 | searchHint 传空 key |
| `8888` | 业务参数错误 | `entid` 无效、`version` 不存在 |
| `9999` | 系统错误 | 缺少必传参数、鉴权失败，或公用 Key 当日额度已达上限（`msg` 常见为“访问已达上限”） |


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