mcpField
Q
qiaomu-opencli-oneshot

qiaomu-opencli-oneshot

github.com/joeseesun开发9971.0k 次安装29 周前更新
代码仓库

关于 qiaomu-opencli-oneshot

qiaomu-opencli-oneshot 是帮助开发者根据一个 URL 和一句目标描述,为 OpenCLI 快速创建单个 CLI 命令的指南。流程从浏览器抓取 API 请求开始,经接口验证后生成 TypeScript 适配器,并要求构建和实测。

主要功能

  1. 01按 URL 与目标生成单个命令
  2. 02通过浏览器网络请求定位 API
  3. 03验证 Cookie、Header 或拦截策略
  4. 04提供 TypeScript 适配器模板
  5. 05包含构建、注册与运行测试

适用场景

  1. 01为网站页面增加一条 CLI 命令
  2. 02确认登录态 API 请求能否复现
  3. 03为受限请求选择拦截策略
  4. 04测试新适配器是否已注册并可运行

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/joeseesun/qiaomu-opencli-skills --skill qiaomu-opencli-oneshot

安装后在 Claude Code 中输入/qiaomu-opencli-oneshot确认已加载。

详细介绍

CLI-ONESHOT — 单点快速 CLI 生成

给一个 URL + 一句话描述,4 步生成一个 CLI 命令。 完整探索式开发请看 opencli-explorer skill。


输入

项目示例
URLhttps://x.com/jakevin7/lists
目标获取我的 Twitter Lists

流程

步骤 1:打开页面 + 抓包
1. browser_navigate → 打开目标 URL
2. 等待 3-5 秒(让页面加载完、API 请求触发)
3. browser_network_requests → 筛选 JSON API

关键:只关注返回 application/json 的请求,忽略静态资源。 如果没有自动触发 API,手动点击目标按钮/标签再抓一次。

步骤 2:锁定一个接口

从抓包结果中找到那个目标 API。看这几个字段:

字段关注什么
URLAPI 路径模式(如 /i/api/graphql/xxx/ListsManagePinTimeline)
方法GET / POST
请求头有 Cookie? Bearer? CSRF? 自定义签名?
响应数据在哪个路径(如 data.list.lists)
步骤 3:验证接口能复现

在 browser_evaluate 中用 fetch 复现请求:

// Tier 2 (Cookie): 大多数情况
fetch('/api/endpoint', { credentials: 'include' }).then(r => r.json())

// Tier 3 (Header): 如 Twitter 需要额外 header
const ct0 = document.cookie.match(/ct0=([^;]+)/)?.[1];
fetch('/api/endpoint', {
  headers: { 'Authorization': 'Bearer ...', 'X-Csrf-Token': ct0 },
  credentials: 'include'
}).then(r => r.json())

如果 fetch 能拿到数据 → 用 TS 适配器(cli() pipeline 或 func())。 如果 fetch 拿不到(签名/风控)→ 用拦截策略(TS func() + installInterceptor)。

步骤 4:套模板,生成适配器

根据步骤 3 判定的策略,选一个模板生成文件。


认证速查

fetch(url) 直接能拿到?              → Tier 1: public   (TS pipeline, browser: false)
fetch(url, {credentials:'include'})? → Tier 2: cookie   (TS pipeline 或 func())
加 Bearer/CSRF header 后拿到?        → Tier 3: header   (TS func())
都不行,但页面自己能请求成功?          → Tier 4: intercept (TS func(), installInterceptor)

模板

TS — Cookie/公共(最简,func() 模式)
// clis/<site>/<name>.ts
import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'mysite',
  name: 'mycommand',
  description: '一句话描述',
  domain: 'www.example.com',
  strategy: Strategy.COOKIE,   // 或 Strategy.PUBLIC (加 browser: false)
  browser: true,
  args: [
    { name: 'limit', type: 'int', default: 20 },
  ],
  columns: ['rank', 'title', 'value'],
  func: async (page, kwargs) => {
    await page.goto('https://www.example.com/target-page');
    const data = await page.evaluate(`(async () => {
      const res = await fetch('/api/target', { credentials: 'include' });
      const d = await res.json();
      return (d.data?.items || []).map(item => ({
        title: item.title,
        value: item.value,
      }));
    })()`);
    return (data as any[]).slice(0, kwargs.limit).map((item, i) => ({
      rank: i + 1,
      title: item.title || '',
      value: item.value || '',
    }));
  },
});
TS — 拦截(抓包模式)
// clis/<site>/<name>.ts
import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'mysite',
  name: 'mycommand',
  description: '一句话描述',
  domain: 'www.example.com',
  strategy: Strategy.INTERCEPT,
  browser: true,
  args: [
    { name: 'limit', type: 'int', default: 20 },
  ],
  columns: ['rank', 'title', 'value'],
  func: async (page, kwargs) => {
    // 1. 导航
    await page.goto('https://www.example.com/target-page');
    await page.wait(3);

    // 2. 注入拦截器(URL 子串匹配)
    await page.installInterceptor('target-api-keyword');

    // 3. 触发 API(滚动/点击)
    await page.autoScroll({ times: 2, delayMs: 2000 });

    // 4. 读取拦截的响应
    const requests = await page.getInterceptedRequests();
    if (!requests?.length) return [];

    let results: any[] = [];
    for (const req of requests) {
      const items = req.data?.data?.items || [];
      results.push(...items);
    }

    return results.slice(0, kwargs.limit).map((item, i) => ({
      rank: i + 1,
      title: item.title || '',
      value: item.value || '',
    }));
  },
});
TS — 请求头(如 Twitter GraphQL)
import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'twitter',
  name: 'mycommand',
  description: '一句话描述',
  domain: 'x.com',
  strategy: Strategy.HEADER,
  browser: true,
  args: [
    { name: 'limit', type: 'int', default: 20 },
  ],
  columns: ['rank', 'name', 'value'],
  func: async (page, kwargs) => {
    await page.goto('https://x.com');
    const data = await page.evaluate(`(async () => {
      const ct0 = document.cookie.match(/ct0=([^;]+)/)?.[1];
      if (!ct0) return { error: 'Not logged in' };
      const bearer = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D...';
      const res = await fetch('/i/api/graphql/QUERY_ID/Endpoint', {
        headers: {
          'Authorization': 'Bearer ' + decodeURIComponent(bearer),
          'X-Csrf-Token': ct0,
          'X-Twitter-Auth-Type': 'OAuth2Session',
        },
        credentials: 'include',
      });
      return res.json();
    })()`);
    // 解析 data...
    return [];
  },
});

测试(必做)

npm run build                              # 语法检查
opencli list | grep mysite                 # 确认注册
opencli mysite mycommand --limit 3 -v      # 实际运行

就这样,没了

写完文件 → build → run → 提交。有问题再看 opencli-explorer skill。

评论

支持 Markdown