Skill: clonbrowser/lead-processor (version 1.0.2)

# Lead Processing Agent 外贸 B2B 线索自动化清洗与分发代理。 ## 功能 - 读取飞书多维表格中的客户 URL - 访问客户官网进行深度分析（浏览器工具） - 根据规则进行 A/B/C 分级 - 将详细结果写入飞书多维表格 - 发送分析报告到飞书群 ## 配置 - App [REDACTED_SECRET] - Table ID: tbl77aWIKk4oXLvj - 飞书群：龙虾伙伴们 (oc_2c705fa31fb8c9a66dd3e22ab8a2243c) ## 分类规则 ### A级（终端工厂） - 页面包含 "OEM"、"production line"、"ISO9001"、"Request a Quote" 等 - 产品涉及：水泵、阀门、铸件 - 判定描述："终端工厂（泵阀制造）" ### B级（贸易商/经销商） - 页面包含 "Distributor"、"Wholesaler"、"Warehouse" 等 - 无自有产线证据，但覆盖泵阀配件 - 判定描述："贸易商/经销商（泵阀分销）" ### C级（同行/其他） - 同行：Foundry services、Iron casting service（铸造/代工） - 其他：IT服务、咨询公司、软件公司、研究院、能源项目开发商等 - 判定描述示例："其他（IT服务/软件开发）"或"同行（铸造厂）" ## 输出格式（必须详细） | 字段 | 说明 | 示例 | |------|------|------| | 公司类型判定 | 详细类型+行业描述 | "其他（新能源项目开发商，非泵阀/铸件终端）" | | 评级 | A/B/C | "C" | | 主营产品/关键词 | 英文关键词逗号分隔 | "wind energy,solar,battery" | | 采购/合作信号 | 官网原文描述 | "develops and builds wind..." | | 证据摘要 | 分析理由详细说明 | "主营风电/光伏项目开发，不属于终端工厂/贸易商/同行范围" | | 联系人线索 | 具体邮箱或官网入口 | "[REDACTED_SECRET]；官网 Contact 表单入口" | | 风险/排除原因 | 详细说明 | "行业不匹配" | | 推荐动作 | 详细动作 | "过滤：不跟进" | | 处理状态 | "已完成" | "已完成" | | 最后更新时间 | YYYY/MM/DD | "2026/02/27" | ## 工作流程 1. 收到任务后立即回复确认 2. 读取飞书表格未处理记录 3. 使用浏览器访问每个官网 4. 分析并填写详细字段 5. 写入表格后发送消息到飞书群 ## 禁止行为 - 禁止说"好的"、"明白了" - 禁止输出"综上所述" - 禁止中途提问

path: config.json
content: {
  "feishu": {
    "app_id": "cli_a9140ac9cab85bd3",
    "app_secret": [REDACTED_SECRET]
  },
  "bitable": {
    "app_token": "FBzzbi1b2anl8YsTZtxc1VOcnzb",
    "table_id": "tbl77aWIKk4oXLvj"
  },
  "chat": {
    "chat_id": "oc_2c705fa31fb8c9a66dd3e22ab8a2243c"
  },
  "field_mapping": {
    "company_name": "公司名",
    "website": "官网URL",
    "country": "国家/地区",
    "company_type": "公司类型判定",
    "rating": "评级",
    "action": "推荐动作",
    "products": "主营产品/关键词",
    "purchase_signal": "采购/合作信号",
    "evidence": "证据摘要",
    "risk": "风险/排除原因",
    "contact": "联系人线索",
    "status": "处理状态",
    "update_time": "最后更新时间"
  }
}


path: index.js
content: const https = require('https');
const { URL } = require('url');

const FEISHU_APP_ID = process.env.FEISHU_APP_ID || 'cli_a9140ac9cab85bd3';
const FEISHU_APP_SECRET = process.env.FEISHU_APP_SECRET || [REDACTED_SECRET];

let accessToken = null;
let tokenExpire = 0;

async function getAccessToken() {
  if (accessToken && Date.now() < tokenExpire) {
    return accessToken;
  }

  const postData = JSON.stringify({
    app_id: FEISHU_APP_ID,
    app_secret: FEISHU_APP_SECRET
  });

  const options = {
    hostname: 'open.feishu.cn',
    path: '/open-apis/auth/v3/tenant_access_token/internal',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postData)
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        try {
          const result = JSON.parse(data);
          if (result.code === 0) {
            accessToken = result.tenant_access_token;
            tokenExpire = Date.now() + (result.expire - 300) * 1000;
            resolve(accessToken);
          } else {
            reject(new Error(`Failed to get [REDACTED_SECRET]`));
          }
        } catch (e) {
          reject(e);
        }
      });
    });
    req.on('error', reject);
    req.write(postData);
    req.end();
  });
}

async function getBitableRecords(appToken, tableId) {
  const token = await getAccessToken();
  
  const options = {
    hostname: 'open.feishu.cn',
    path: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records?page_size=100`,
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${token}`
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        try {
          const result = JSON.parse(data);
          if (result.code === 0) {
            resolve(result.data.items);
          } else {
            reject(new Error(`Failed to get records: ${result.msg}`));
          }
        } catch (e) {
          reject(e);
        }
      });
    });
    req.on('error', reject);
    req.end();
  });
}

async function updateBitableRecord(appToken, tableId, recordId, fields) {
  const token = await getAccessToken();
  const postData = JSON.stringify({ fields });

  const options = {
    hostname: 'open.feishu.cn',
    path: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records/${recordId}`,
    method: 'PUT',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postData)
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        try {
          const result = JSON.parse(data);
          if (result.code === 0) {
            resolve(result.data);
          } else {
            reject(new Error(`Failed to update record: ${result.msg}`));
          }
        } catch (e) {
          reject(e);
        }
      });
    });
    req.on('error', reject);
    req.write(postData);
    req.end();
  });
}

async function sendFeishuMessage(chatId, text) {
  const token = await getAccessToken();
  const content = JSON.stringify({ text });
  const postData = JSON.stringify({
    receive_id: chatId,
    msg_type: 'text',
    content
  });

  const options = {
    hostname: 'open.feishu.cn',
    path: '/open-apis/im/v1/messages?receive_id_type=chat_id',
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postData)
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        try {
          const result = JSON.parse(data);
          if (result.code === 0) {
            resolve(result.data);
          } else {
            reject(new Error(`Failed to send message: ${result.msg}`));
          }
        } catch (e) {
          reject(e);
        }
      });
    });
    req.on('error', reject);
    req.write(postData);
    req.end();
  });
}

function analyzeCompany(pageText) {
  const text = pageText.toLowerCase();
  
  const factoryKeywords = ['oem', 'production line', 'iso9001', 'request a quote', 'manufacturing', 'factory', 'manufacturer', 'produktion'];
  const distributorKeywords = ['distributor', 'wholesaler', 'warehouse', 'multiple brands', 'reseller', 'vertrieb', 'sales company'];
  const competitorKeywords = ['foundry services', 'iron casting service', 'casting', '代工', ' foundry'];
  
  let factoryScore = 0;
  let distributorScore = 0;
  let competitorScore = 0;
  
  factoryKeywords.forEach(kw => { if (text.includes(kw)) factoryScore++; });
  distributorKeywords.forEach(kw => { if (text.includes(kw)) distributorScore++; });
  competitorKeywords.forEach(kw => { if (text.includes(kw)) competitorScore++; });
  
  if (factoryScore > 0) {
    return { type: '终端工厂', grade: 'A', action: '立即跟进: 开发信 + LinkedIn 加人 + 询盘引导' };
  } else if (competitorScore > 0) {
    return { type: '同行/铸件厂', grade: 'C', action: '过滤: 同行竞争、不跟进' };
  } else if (distributorScore > 0) {
    return { type: '贸易商/经销商', grade: 'B', action: '加入培育: 发资料包 + 问终端项目 + 争取引荐' };
  }
  
  return { type: '未确定', grade: 'C', action: '需要进一步分析' };
}

module.exports = {
  getAccessToken,
  getBitableRecords,
  updateBitableRecord,
  sendFeishuMessage,
  analyzeCompany
};


path: lead-processor/config.json
content: {
  "feishu": {
    "app_id": "cli_a9140ac9cab85bd3",
    "app_secret": [REDACTED_SECRET]
  },
  "bitable": {
    "app_token": "FBzzbi1b2anl8YsTZtxc1VOcnzb",
    "table_id": "tbl77aWIKk4oXLvj"
  },
  "chat": {
    "chat_id": "oc_2c705fa31fb8c9a66dd3e22ab8a2243c"
  },
  "field_mapping": {
    "company_name": "公司名",
    "website": "官网URL",
    "country": "国家/地区",
    "company_type": "公司类型判定",
    "rating": "评级",
    "action": "推荐动作",
    "products": "主营产品/关键词",
    "purchase_signal": "采购/合作信号",
    "evidence": "证据摘要",
    "risk": "风险/排除原因",
    "contact": "联系人线索",
    "status": "处理状态",
    "update_time": "最后更新时间"
  }
}


path: lead-processor/index.js
content: const https = require('https');
const { URL } = require('url');

const FEISHU_APP_ID = process.env.FEISHU_APP_ID || 'cli_a9140ac9cab85bd3';
const FEISHU_APP_SECRET = process.env.FEISHU_APP_SECRET || [REDACTED_SECRET];

let accessToken = null;
let tokenExpire = 0;

async function getAccessToken() {
  if (accessToken && Date.now() < tokenExpire) {
    return accessToken;
  }

  const postData = JSON.stringify({
    app_id: FEISHU_APP_ID,
    app_secret: FEISHU_APP_SECRET
  });

  const options = {
    hostname: 'open.feishu.cn',
    path: '/open-apis/auth/v3/tenant_access_token/internal',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postData)
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        try {
          const result = JSON.parse(data);
          if (result.code === 0) {
            accessToken = result.tenant_access_token;
            tokenExpire = Date.now() + (result.expire - 300) * 1000;
            resolve(accessToken);
          } else {
            reject(new Error(`Failed to get [REDACTED_SECRET]`));
          }
        } catch (e) {
          reject(e);
        }
      });
    });
    req.on('error', reject);
    req.write(postData);
    req.end();
  });
}

async function getBitableRecords(appToken, tableId) {
  const token = await getAccessToken();
  
  const options = {
    hostname: 'open.feishu.cn',
    path: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records?page_size=100`,
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${token}`
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        try {
          const result = JSON.parse(data);
          if (result.code === 0) {
            resolve(result.data.items);
          } else {
            reject(new Error(`Failed to get records: ${result.msg}`));
          }
        } catch (e) {
          reject(e);
        }
      });
    });
    req.on('error', reject);
    req.end();
  });
}

async function updateBitableRecord(appToken, tableId, recordId, fields) {
  const token = await getAccessToken();
  const postData = JSON.stringify({ fields });

  const options = {
    hostname: 'open.feishu.cn',
    path: `/open-apis/bitable/v1/apps/${appToken}/tables/${tableId}/records/${recordId}`,
    method: 'PUT',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postData)
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        try {
          const result = JSON.parse(data);
          if (result.code === 0) {
            resolve(result.data);
          } else {
            reject(new Error(`Failed to update record: ${result.msg}`));
          }
        } catch (e) {
          reject(e);
        }
      });
    });
    req.on('error', reject);
    req.write(postData);
    req.end();
  });
}

async function sendFeishuMessage(chatId, text) {
  const token = await getAccessToken();
  const content = JSON.stringify({ text });
  const postData = JSON.stringify({
    receive_id: chatId,
    msg_type: 'text',
    content
  });

  const options = {
    hostname: 'open.feishu.cn',
    path: '/open-apis/im/v1/messages?receive_id_type=chat_id',
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postData)
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        try {
          const result = JSON.parse(data);
          if (result.code === 0) {
            resolve(result.data);
          } else {
            reject(new Error(`Failed to send message: ${result.msg}`));
          }
        } catch (e) {
          reject(e);
        }
      });
    });
    req.on('error', reject);
    req.write(postData);
    req.end();
  });
}

function analyzeCompany(pageText) {
  const text = pageText.toLowerCase();
  
  const factoryKeywords = ['oem', 'production line', 'iso9001', 'request a quote', 'manufacturing', 'factory', 'manufacturer', 'produktion'];
  const distributorKeywords = ['distributor', 'wholesaler', 'warehouse', 'multiple brands', 'reseller', 'vertrieb', 'sales company'];
  const competitorKeywords = ['foundry services', 'iron casting service', 'casting', '代工', ' foundry'];
  
  let factoryScore = 0;
  let distributorScore = 0;
  let competitorScore = 0;
  
  factoryKeywords.forEach(kw => { if (text.includes(kw)) factoryScore++; });
  distributorKeywords.forEach(kw => { if (text.includes(kw)) distributorScore++; });
  competitorKeywords.forEach(kw => { if (text.includes(kw)) competitorScore++; });
  
  if (factoryScore > 0) {
    return { type: '终端工厂', grade: 'A', action: '立即跟进: 开发信 + LinkedIn 加人 + 询盘引导' };
  } else if (competitorScore > 0) {
    return { type: '同行/铸件厂', grade: 'C', action: '过滤: 同行竞争、不跟进' };
  } else if (distributorScore > 0) {
    return { type: '贸易商/经销商', grade: 'B', action: '加入培育: 发资料包 + 问终端项目 + 争取引荐' };
  }
  
  return { type: '未确定', grade: 'C', action: '需要进一步分析' };
}

module.exports = {
  getAccessToken,
  getBitableRecords,
  updateBitableRecord,
  sendFeishuMessage,
  analyzeCompany
};


path: lead-processor/_meta.json
content: ownerId: kn73c2b8s9kk5c5msmxqxrgtas81z86b
slug: lead-processor
version: 1.0.0
publishedAt: 1772162919568

path: _meta.json
content: ownerId: user-local
slug: lead-processor
version: 1.0.0
publishedAt: 1772160000000

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