mcpField
Q
qiaomu-opencli-oneshot

qiaomu-opencli-oneshot

github.com/joeseesun開発9971.0k 回インストール29 週間前に更新
リポジトリ

qiaomu-opencli-oneshot について

qiaomu-opencli-oneshotは、URLと短い目的の説明からOpenCLIのコマンドを1つ作る開発者向けガイドです。ブラウザーでのAPI通信の取得からエンドポイント検証、TypeScriptアダプターの作成へ進み、ビルドと実行テストも行います。

主な機能

  1. 01URLと目的から単一コマンドを作成
  2. 02ブラウザー通信からAPIを特定
  3. 03Cookie、ヘッダー、インターセプト方式の検証
  4. 04TypeScriptアダプターテンプレート
  5. 05ビルド、登録、実行テスト

活用シーン

  1. 01Webページ用の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: 1 つのエンドポイントに絞り込む

キャプチャした結果から、対象の API を1 つ見つけます。次のフィールドを確認します。

フィールド確認する内容
URLAPI パスのパターン(例:/i/api/graphql/xxx/ListsManagePinTimeline)
MethodGET / POST
HeadersCookie、Bearer、CSRF、カスタム署名の有無
Responseデータの場所(例: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() パイプラインまたは func())を使います。 fetch でデータを取得できない場合(署名/不正利用対策)→ intercept 戦略(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/Public(最も簡単な 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 — Intercept(リクエストキャプチャモード)
// 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 — Header(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 をご覧ください。

コメント