Agent Skills by ALSEL
Anthropic Claudeソフトウェア開発⭐ リポ 0品質スコア 50/100

qiaomu-opencli-explorer

新しいOpenCLIアダプターをゼロから作成する際や、新しいWebサイト・プラットフォームへの対応を追加する際に使用します。ブラウザのDevToolsを使ったAPIエンドポイントの調査から、認証戦略の選定、TypeScriptアダプターの実装、テストまでの一連のワークフローをカバーし、「特定サイトのCLIを自動生成したい」というリクエストにも対応します。

description の原文を見る

Use when creating a new OpenCLI adapter from scratch, adding support for a new website or platform, exploring a site's API endpoints via browser DevTools, or when a user asks to automatically generate a CLI for a website (e.g. "帮我生成 xxx.com 的 cli"). Covers automated generation, API discovery workflow, authentication strategy selection, TS adapter writing, and testing.

SKILL.md 本文

CLI-EXPLORER — アダプター探索型開発完全ガイド

このドキュメントは、OpenCLI に新しいウェブサイトのコマンドを追加する方法(あなた、または AI Agent)を教えます。
ゼロから公開まで、API 発見、戦略選択、アダプター作成、テスト検証の全流程をカバーしています。

[!TIP] 特定のページのための 1 つのコマンドを素早く生成したいだけですか? opencli-oneshot スキル(約 150 行、4 ステップで完了)を参照してください。 このドキュメントは新しいサイトを最初からゼロで探索するための完全なワークフロー向けです。

[!TIP] 完全に自動生成したいですか? opencli generate <url> [--goal <goal>] を実行してください。内部的には自動的に explore → synthesize → cascade → verify の全流程を実行します。 SkillOutput(success / blocked / needs-human-check)を返します。自動生成が失敗するか人工的な介入が必要な場合は、このドキュメントの手動ワークフローで続行してください。


AI Agent 開発者向け必読:ブラウザーを使用した探索

[!CAUTION] あなた(AI Agent)はターゲットウェブサイトをブラウザーで開いて探索する必要があります!
opencli explore コマンドまたは静的分析だけに頼って API を発見しないでください。
ブラウザーツールを持っています。それらを積極的に使用してウェブページを閲覧し、ネットワークリクエストを監視し、ユーザーインタラクションをシミュレートします。

なぜですか?

多くの API は遅延ロードです(ユーザーはボタン/タブをクリックする必要があります、それが初めてネットワークリクエストをトリガーします)。字幕、コメント、フォロー リストなどの深層データは、ページの最初の読み込み時にネットワーク パネルに表示されません。ページを積極的に閲覧および対話しない場合、これらの API を発見することはできません。

AI Agent 探索ワークフロー(必須)

ステップツール実行内容
0. ブラウザーを開くbrowser_navigateターゲットページに移動
1. ページを観察browser_snapshotインタラクティブな要素(ボタン/タブ/リンク)を観察
2. 初回キャプチャbrowser_network_requestsJSON API エンドポイントをフィルター、URL パターンを記録
3. インタラクションをシミュレートbrowser_click + browser_wait_for「字幕」「コメント」「フォロー」などのボタンをクリック
4. 二次キャプチャbrowser_network_requestsステップ 2 と比較して、新しくトリガーされた API を検出
5. API を検証browser_evaluatefetch(url, {credentials:'include'}) を使用して返却構造をテスト
6. コードを作成確認された API に基づいてアダプターを作成

よくある間違い

❌ 間違ったやり方✅ 正しいやり方
opencli explore コマンドのみを使用して、結果を自動的に出力されるまで待つブラウザーツールを使用してページを開き、積極的に閲覧
コードで直接 fetch(url) を実行し、ブラウザーの実際のリクエストを確認しない最初にブラウザーで API が機能することを確認してから、コードを作成
ページを開いた直後にキャプチャし、すべての API が表示されることを期待クリック インタラクションをシミュレート(コメントを展開/タブを切り替え/さらに読み込む)
HTTP 200 だが空のデータで放棄Wbi 署名または Cookie 認証が必要かどうかを確認
__INITIAL_STATE__ に完全に依存して全データを取得__INITIAL_STATE__ には最初の画面データのみが含まれます。深層データは API を呼び出す必要があります

実践的な成功事例:5 分で「フォロー リスト」アダプターを実装

以下は、上記のワークフローを使用して Bilibili のフォロー リスト API を実際に発見するための完全なプロセスです:

1. browser_navigate → https://space.bilibili.com/{uid}/fans/follow
2. browser_network_requests → 検出:
   GET /x/relation/followings?vmid={uid}&pn=1&ps=24  →  [200]
   GET /x/relation/stat?vmid={uid}                    →  [200]
3. browser_evaluate → API を検証:
   fetch('/x/relation/followings?vmid=137702077&pn=1&ps=5', {credentials:'include'})
   → { code: 0, data: { total: 1342, list: [{mid, uname, sign, ...}] } }
4. 結論:標準 Cookie API、Wbi 署名不要
5. following.ts を作成 → 一度で構築成功

重要な判断点

  • fans/follow ページに直接アクセス(ホームページではなく)。ページ読み込み時に following API がトリガーされる
  • URL に /wbi/ がない → 署名不要 → fetchJson を直接使用、apiGet ではない
  • API は code: 0 を返す + 空でない list → Tier 2 Cookie 戦略確認

コア フロー

 ┌─────────────┐     ┌─────────────┐     ┌──────────────┐     ┌────────┐
 │ 1. API 発見  │ ──▶ │ 2. 戦略選択  │ ──▶ │ 3. アダプター作成│ ──▶ │ 4. テスト │
 └─────────────┘     └─────────────┘     └──────────────┘     └────────┘
   explore             cascade             TS (cli() API)       run + verify

ステップ 1:API を発見

1a. 自動探索(推奨)

OpenCLI は Deep Explore を組み込んでおり、ウェブサイトのネットワーク リクエストを自動的に分析します:

opencli explore https://www.example.com --site mysite

.opencli/explore/mysite/ に出力:

ファイル内容
manifest.jsonサイト メタデータ、フレームワーク検出(Vue2/3、React、Next.js、Pinia、Vuex)
endpoints.json検出された API エンドポイント、スコア順、URL パターン、メソッド、応答タイプを含む
capabilities.json推論される機能(hotsearchfeed…)、信頼度と推奨パラメーター付き
auth.json認証方式検出(Cookie/Header/認証なし)、戦略候補リスト

1b. 手動でキャプチャ検証

Explore の自動分析は完璧でない可能性があるため、verbose モードで手動確認:

# ブラウザーでターゲット ページを開き、ネットワーク リクエストを観察
opencli explore https://www.example.com --site mysite -v

# または、evaluate を使用して API をテスト
opencli bilibili hot -v   # 既存のコマンドの pipeline 各ステップのデータフローを確認

キャプチャ結果の重要な情報に注目:

  • URL パターン: /api/v2/hot?limit=20 → これが呼び出すエンドポイント
  • メソッド: GET / POST
  • リクエスト ヘッダー: Cookie? Bearer? カスタム署名ヘッダー(X-s、X-t)?
  • レスポンス ボディ: JSON 構造、特にデータがどのパスにあるか(data.itemsdata.list

1c. 高度な API 発見のショートカット法則(ヒューリスティック)

複雑なパケット キャプチャの相互作用に従う前に、以下の優先順位に従ってこれらを試してください:

  1. サフィックス ブルートフォース法 (.json): Reddit のような複雑なウェブサイトでも、その URL に .json を追加するだけで(たとえば /r/all.json)、Cookie を携帯する状況で、fetch を直接利用して非常にクリーンな REST データを取得できます(Tier 2 Cookie 戦略で極速解決)。また、機能が充実した Xueqiu(雪球) のような場合も、この純 API 方式で取得でき、シンプルな TS パイプライン アダプターを構築する黄金の標準になります。
  2. グローバル状態検索法 (__INITIAL_STATE__): 多くのサーバー サイド レンダリング (SSR) ウェブサイト(小紅書、Bilibili など)は、ホームページまたは詳細ページの完全なデータをグローバル window オブジェクトにマウントします。ネットワーク リクエストをインターセプトするのではなく、page.evaluate('() => window.__INITIAL_STATE__') を使用してデータツリー全体を取得する方が簡単です。
  3. アクティブ インタラクション トリガー法 (Active Interaction): 多くの深層 API(ビデオ字幕、コメント下の返信など)は遅延ロードです。静的キャプチャでデータが見つからない場合、evaluate ステップまたは手動ブレークポイント時に、ページ上の対応するボタン(「CC」、「すべて展開」など)をアクティブにクリックして、隠れたネットワーク fetch をトリガーすることを試してください。
  4. フレームワーク検出と Store Action 遮断: サイトが Vue + Pinia を使用している場合、tap ステップを使用してアクション を呼び出し、フロントエンド フレームワークに複雑な認証署名をカプセル化させることができます。
  5. 下位層 XHR/Fetch インターセプション: 最後の手段。上記のすべてが機能しない場合、TypeScript アダプターで非侵襲的なリクエスト キャプチャを実行します。

1d. フレームワーク検出

Explore がフロントエンド フレームワークを自動検出します。手動で確認する場合:

# ターゲット ウェブサイトが既に開いている場合
opencli evaluate "(()=>{
  const vue3 = !!document.querySelector('#app')?.__vue_app__;
  const vue2 = !!document.querySelector('#app')?.__vue__;
  const react = !!window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
  const pinia = vue3 && !!document.querySelector('#app').__vue_app__.config.globalProperties.\$pinia;
  return JSON.stringify({vue3, vue2, react, pinia});
})()"

Vue + Pinia のサイト(小紅書など)は Store Action を通じて署名をバイパスできます。


ステップ 2:認証戦略を選択

OpenCLI は 5 段階の認証戦略を提供しています。cascade コマンドで自動検出:

opencli cascade https://api.example.com/hot

戦略決定木

fetch(url) でデータを取得できますか?
  → ✅ Tier 1: public(公開 API、ブラウザー不要)
  → ❌ fetch(url, {credentials:'include'}) で Cookie を携帯して取得できますか?
       → ✅ Tier 2: cookie(最も一般的、evaluate ステップ内の fetch)
       → ❌ → Bearer / CSRF ヘッダーを追加した後で取得できますか?
              → ✅ Tier 3: header(Twitter ct0 + Bearer など)
              → ❌ → ウェブサイトは Pinia/Vuex Store を持っていますか?
                     → ✅ Tier 4: intercept(Store Action + XHR インターセプション)
                     → ❌ Tier 5: ui(UI 自動化、最後の手段)

各戦略の比較

Tier戦略速度複雑度ユースケース
1public⚡ 約 1 秒最小公開 API、ログイン不要Hacker News、V2EX
2cookie🔄 約 7 秒簡単Cookie 認証で十分Bilibili、知乎、Reddit
3header🔄 約 7 秒中程度CSRF トークンまたは Bearer 必要Twitter GraphQL
4intercept🔄 約 10 秒高いリクエストに複雑な署名あり小紅書(Pinia + XHR)
5ui🐌 約 15 秒以上最高API なし、純粋な DOM 解析レガシー ウェブサイト

ステップ 2.5:準備作業(コードを書く前に)

まずテンプレートを探す:最も似ている既存のアダプターから始める

ゼロから始めないでください。まず同じサイトにある既存のアダプターを確認してください:

ls clis/<site>/    # 既存のものを確認
cat clis/<site>/feed.ts   # 最も似ているものを読む

最も効率的な方法は、最も似ているアダプターをコピーして、3 つの場所を変更することです:

  1. name → 新しいコマンド名
  2. API URL → ステップ 1 で発見したエンドポイント
  3. フィールド マッピング → 新しい API に対応

プラットフォーム SDK クイックリファレンス

TS アダプターを作成する前に、ターゲット サイトに既存のヘルパー関数があるか確認してください:

Bilibili (clis/bilibili/utils.ts)

関数用途使用時期
fetchJson(page, url)Cookie 付き fetch + JSON パース通常の Cookie 層 API
apiGet(page, path, {signed, params})Wbi 署名付き API 呼び出しURL に /wbi/ が含まれるエンドポイント
getSelfUid(page)現在ログインしているユーザーの UID を取得「マイ xxx」タイプのコマンド
resolveUid(page, input)ユーザー入力の UID を解析(数字/URL サポート)--uid パラメーターの処理
wbiSign(page, params)下位層 Wbi 署名生成通常は直接使用しない、apiGet がカプセル化済み
stripHtml(s)HTML タグを削除リッチテキスト フィールドをクリーンアップ

Wbi apiGet が必要かどうかの判断方法?ネットワーク リクエスト URL を確認:

  • /wbi/ を含む、または w_rid= → 必ず apiGet(..., { signed: true }) を使用
  • 含まない → fetchJson を直接使用

その他のサイト(Twitter、小紅書など)は専用 SDK がないため、page.evaluate + fetch を直接使用します。


ステップ 3:アダプターを作成

アダプター形式:TypeScript を統一使用

すべてのアダプターは TypeScript cli() API を統一して使用します。YAML アダプター形式は廃止されました。

すべての新しいアダプター → TypeScript (clis/<site>/<name>.ts)、保存すると自動動的に登録される
シーンモード
シンプル fetch + データ マッピングTS cli() + func()v2ex/hot.tshackernews/top.ts
navigate + evaluate(fetch) + データ マッピングTS cli() + func()zhihu/hot.ts
Pinia Store Action トリガーTS cli() + func()xiaohongshu/feed.tsxiaohongshu/notifications.ts
複雑な JS ロジック(state 読み取り、条件分岐)TS cli() + func()xiaohongshu/me.tsbilibili/me.ts
XHR インターセプション + 署名TS cli() + func()xiaohongshu/search.ts
GraphQL / ページング / Wbi 署名TS cli() + func()bilibili/search.tstwitter/search.ts

共通パターン:ページング API

多くの API は pn(ページ数)+ ps(ページあたりの件数)ページングを使用します。標準処理パターン:

args: [
  { name: 'page', type: 'int', required: false, default: 1, help: 'ページ数' },
  { name: 'limit', type: 'int', required: false, default: 50, help: 'ページあたりの件数(最大 50)' },
],
func: async (page, kwargs) => {
  const pn = kwargs.page ?? 1;
  const ps = Math.min(kwargs.limit ?? 50, 50); // API の ps 上限を尊重
  const payload = await fetchJson(page,
    `https://api.example.com/list?pn=${pn}&ps=${ps}`
  );
  return payload.data?.list || [];
},

ほとんどのサイトの ps 上限は 20~50 です。超過するとサイレントに切り詰められるか、エラーが返されます。

パターン A: TS cli() + func()(標準モード、推奨)

ファイル パス: clis/<site>/<name>.ts、配置すると自動登録されます。

Tier 1 — 公開 API テンプレート

// clis/v2ex/hot.ts
import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'v2ex',
  name: 'hot',
  description: 'V2EX ホット トピック',
  domain: 'www.v2ex.com',
  strategy: Strategy.PUBLIC,
  browser: false,
  args: [
    { name: 'limit', type: 'int', default: 20 },
  ],
  columns: ['rank', 'title', 'replies'],
  func: async (_page, kwargs) => {
    const res = await fetch('https://www.v2ex.com/api/topics/hot.json');
    const data = await res.json();
    return data.slice(0, kwargs.limit).map((item: any, i: number) => ({
      rank: i + 1,
      title: item.title,
      replies: item.replies,
    }));
  },
});

Tier 2 — Cookie 認証テンプレート(最も一般的)

// clis/zhihu/hot.ts
import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'zhihu',
  name: 'hot',
  description: '知乎ホット リスト',
  domain: 'www.zhihu.com',
  strategy: Strategy.COOKIE,
  browser: true,
  args: [
    { name: 'limit', type: 'int', default: 50 },
  ],
  columns: ['rank', 'title', 'heat', 'answers'],
  func: async (page, kwargs) => {
    await page.goto('https://www.zhihu.com');  // まずページを読み込んでセッションを確立
    const data = await page.evaluate(`(async () => {
      const res = await fetch('/api/v3/feed/topstory/hot-lists/total?limit=50', {
        credentials: 'include'
      });
      const d = await res.json();
      return (d?.data || []).map(item => {
        const t = item.target || {};
        return {
          title: t.title,
          heat: item.detail_text || '',
          answers: t.answer_count,
        };
      });
    })()`);
    return (data as any[]).slice(0, kwargs.limit).map((item, i) => ({
      rank: i + 1,
      title: item.title,
      heat: item.heat,
      answers: item.answers,
    }));
  },
});

キー: page.evaluate 内の fetch はブラウザー ページ内で実行され、credentials: 'include' を自動的に携帯し、Cookie を手動で処理する必要はありません。

高度な — 検索パラメーター付き

// clis/zhihu/search.ts
import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'zhihu',
  name: 'search',
  description: '知乎検索',
  domain: 'www.zhihu.com',
  strategy: Strategy.COOKIE,
  browser: true,
  args: [
    { name: 'query', type: 'str', required: true, positional: true, help: '検索クエリ' },
    { name: 'limit', type: 'int', default: 10 },
  ],
  columns: ['rank', 'title', 'type', 'author', 'votes'],
  func: async (page, kwargs) => {
    await page.goto('https://www.zhihu.com');
    const data = await page.evaluate(`(async () => {
      const q = encodeURIComponent('${kwargs.query}');
      const res = await fetch('/api/v4/search_v3?q=' + q + '&t=general&limit=${kwargs.limit}', {
        credentials: 'include'
      });
      const d = await res.json();
      return (d?.data || [])
        .filter(item => item.type === 'search_result')
        .map(item => ({
          title: (item.object?.title || '').replace(/<[^>]+>/g, ''),
          type: item.object?.type || '',
          author: item.object?.author?.name || '',
          votes: item.object?.voteup_count || 0,
        }));
    })()`);
    return (data as any[]).slice(0, kwargs.limit).map((item, i) => ({
      rank: i + 1,
      ...item,
    }));
  },
});

Tier 4 — Store Action + インターセプター(intercept 戦略)

Vue + Pinia/Vuex のサイト(小紅書など)に適用、installInterceptor を使用して store action がトリガーしたリクエストをキャプチャ:

// clis/xiaohongshu/notifications.ts
import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'xiaohongshu',
  name: 'notifications',
  description: '小紅書通知',
  domain: 'www.xiaohongshu.com',
  strategy: Strategy.INTERCEPT,
  browser: true,
  args: [
    { name: 'type', type: 'str', default: 'mentions', help: '通知タイプ: mentions、likes、connections' },
    { name: 'limit', type: 'int', default: 20 },
  ],
  columns: ['rank', 'user', 'action', 'content', 'note', 'time'],
  func: async (page, kwargs) => {
    await page.goto('https://www.xiaohongshu.com/notification');
    await page.wait(3);

    // `/you/` を含むリクエストをキャプチャするインターセプターをインストール
    await page.installInterceptor('/you/');

    // Pinia store action を通じて API リクエストをトリガー
    await page.evaluate(`(async () => {
      const app = document.querySelector('#app')?.__vue_app__;
      const pinia = app?.config?.globalProperties?.$pinia;
      const store = pinia?._s?.get('notification');
      if (store?.getNotification) {
        await store.getNotification('${kwargs.type}');
      }
    })()`);

    // キャプチャされたリクエストを取得
    const requests = await page.getInterceptedRequests();
    if (!requests?.length) return [];

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

    return results.slice(0, kwargs.limit).map((item, i) => ({
      rank: i + 1,
      user: item.user_info?.nickname || '',
      action: item.title || '',
      content: item.comment_info?.content || '',
    }));
  },
});

パターン B: TypeScript アダプター(高度なモード)

Pinia state 読み取り、XHR インターセプション、GraphQL、ページング、複雑なデータ変換などが必要なシーンに適用。

ファイル パス: clis/<site>/<name>.ts。ファイルは実行時に動的にスキャンされ登録されます(index.ts で手動 import しないでください)。

Tier 3 — ヘッダー認証(Twitter)

// clis/twitter/search.ts
import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'twitter',
  name: 'search',
  description: 'ツイートを検索',
  strategy: Strategy.HEADER,
  args: [{ name: 'query', required: true, positional: true }],
  columns: ['rank', 'author', 'text', 'likes'],
  func: async (page, kwargs) => {
    await page.goto('https://x.com');
    const data = await page.evaluate(`
      (async () => {
        // Cookie から CSRF トークンを抽出
        const ct0 = document.cookie.split(';')
          .map(c => c.trim())
          .find(c => c.startsWith('ct0='))?.split('=')[1];
        if (!ct0) return { error: 'ログインしていません' };

        const bearer = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D...';
        const headers = {
          'Authorization': 'Bearer ' + decodeURIComponent(bearer),
          'X-Csrf-Token': ct0,
          'X-Twitter-Auth-Type': 'OAuth2Session',
        };

        const variables = JSON.stringify({ rawQuery: '${kwargs.query}', count: 20 });
        const url = '/i/api/graphql/xxx/SearchTimeline?variables=' + encodeURIComponent(variables);
        const res = await fetch(url, { headers, credentials: 'include' });
        return await res.json();
      })()
    `);
    // ... data を解析
  },
});

Tier 4 — XHR/Fetch 双重インターセプション(Twitter/小紅書共通パターン)

// clis/xiaohongshu/user.ts
import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'xiaohongshu',
  name: 'user',
  description: 'ユーザーノートを取得',
  strategy: Strategy.INTERCEPT,
  args: [{ name: 'id', required: true }],
  columns: ['rank', 'title', 'likes', 'url'],
  func: async (page, kwargs) => {
    await page.goto(`https://www.xiaohongshu.com/user/profile/${kwargs.id}`);
    await page.wait(5);

    // XHR/Fetch 底層インターセプション:「v1/user/posted」を含むすべてのリクエストをキャプチャ
    await page.installInterceptor('v1/user/posted');

    // バックエンド API をトリガー:ユーザーが下にスクロール 2 回を模擬
    await page.autoScroll({ times: 2, delayMs: 2000 });

    // キャプチャされたすべての JSON レスポンス本文を抽出
    const requests = await page.getInterceptedRequests();
    if (!requests || requests.length === 0) return [];

    let results = [];
    for (const req of requests) {
      if (req.data?.data?.notes) {
        for (const note of req.data.data.notes) {
           results.push({
             title: note.display_title || '',
             likes: note.interact_info?.liked_count || '0',
             url: `https://explore/${note.note_id || note.id}`
           });
        }
      }
    }

    return results.slice(0, 20).map((item, i) => ({
      rank: i + 1, ...item,
    }));
  },
});

インターセプション コア概念:署名を自分で構築するのではなく、installInterceptor を使用してウェブサイト自身の XMLHttpRequestfetch をハイジャック。ウェブサイトがリクエストを発行し、底層で解析済み response.json() を取得します。

カスケード リクエスト(BVID→CID→字幕など)の完全なテンプレートと重要な点については、下の高度なモード:カスケード リクエストセクションを参照してください。


ステップ 4:テスト

構築成功 ≠ 機能正常npm run build は TypeScript 構文のみを検証し、実行時動作は検証しません。
各新しいコマンドは実際に実行され、出力が正しいことを確認した後に完了です。

実施必須チェックリスト

# 1. 構築(構文に問題がないことを確認)
npm run build

# 2. コマンドが登録されたか確認
opencli list | grep mysite

# 3. コマンドを実際に実行(最重要!)
opencli mysite hot --limit 3 -v        # verbose で各ステップのデータフローを確認
opencli mysite hot --limit 3 -f json   # JSON 出力でフィールドが完全であることを確認

tap ステップ デバッグ(intercept 戦略専用)

Store 名 / Action 名を推測しないでください。evaluate で探索してから TS アダプターを作成してください。

ステップ 1:すべての Pinia store をリスト

ターゲット ウェブサイトがブラウザーで開いている後:

opencli evaluate "(() => {
  const app = document.querySelector('#app')?.__vue_app__;
  const pinia = app?.config?.globalProperties?.\$pinia;
  return [...pinia._s.keys()];
})()"
# 出力: ["user", "feed", "search", "notification", ...]

ステップ 2:Store の action 名を確認

意図的に間違った action 名を書くと、tap は利用可能な action をすべて返します:

⚠  tap: Action not found: wrongName on store notification
💡 Available: getNotification, replyComment, getNotificationCount, reset

ステップ 3:network requests で capture モードを確認

# ブラウザーでターゲット ページを開き、ネットワーク リクエストを確認
# ターゲット API の URL 特性を探す(「/you/mentions」、「homefeed」など)

完全なフロー

 ┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌────────┐
 │ 1. navigate  │ ──▶ │ 2. store を   │ ──▶ │ 3. TS を     │ ──▶ │ 4. テスト │
 │  ターゲット側へ │     │  探索 name/  │     │  interceptor  │     │  実行確認 │
 │              │     │  action      │     │  を記述      │     │        │
 └──────────────┘     └──────────────┘     └──────────────┘     └────────┘

Verbose モード & 出力検証

opencli bilibili hot --limit 1 -v          # pipeline の各ステップのデータフローを確認
opencli mysite hot -f json | jq '.[0]'     # JSON が解析可能であることを確認
opencli mysite hot -f csv > data.csv       # CSV をインポート可能であることを確認

ステップ 5:発行・公開

ファイルを clis/<site>/ に配置すると自動登録されます(TS ファイルは手動 import 不要)。その後:

opencli list | grep mysite                            # 登録を確認
git add clis/mysite/ && git commit -m "feat(mysite): add hot" && git push

アーキテクチャ理念:OpenCLI は組み込み ゼロ依存 jq データフロー を備えています — すべての解析は evaluate 内のネイティブ JS で完了し、TS func() 内でデータを直接処理。システム jq バイナリの依存は不要。


高度なモード:カスケード リクエスト(Cascading Requests)

ターゲット データが複数ステップの API チェーン取得が必要な場合(BVID → CID → 字幕リスト → 字幕内容など)、TS func() 内でステップバイステップで連続させるだけです。

テンプレート コード

import { cli, Strategy } from '@jackwener/opencli/registry';
import type { IPage } from '@jackwener/opencli/types';
import { apiGet } from './utils.js'; // プラットフォーム SDK を再利用

cli({
  site: 'bilibili',
  name: 'subtitle',
  strategy: Strategy.COOKIE,
  args: [{ name: 'bvid', required: true }],
  columns: ['index', 'from', 'to', 'content'],
  func: async (page: IPage | null, kwargs: any) => {
    if (!page) throw new Error('ブラウザーが必要です');

    // ステップ 1:セッションを確立
    await page.goto(`https://www.bilibili.com/video/${kwargs.bvid}/`);

    // ステップ 2:ページから中間 ID を抽出(__INITIAL_STATE__)
    const cid = await page.evaluate(`(async () => {
      return window.__INITIAL_STATE__?.videoData?.cid;
    })()`);
    if (!cid) throw new Error('CID を抽出できません');

    // ステップ 3:中間 ID で次レベルの API を呼び出し(自動 Wbi 署名)
    const payload = await apiGet(page, '/x/player/wbi/v2', {
      params: { bvid: kwargs.bvid, cid },
      signed: true, // ← w_rid を自動生成
    });

    // ステップ 4:リスク制御ダウングレードを検出(空値判定)
    const subtitles = payload.data?.subtitle?.subtitles || [];
    const url = subtitles[0]?.subtitle_url;
    if (!url) throw new Error('subtitle_url が空、リスク制御ダウングレード疑い');

    // ステップ 5:最終データを取得(CDN JSON)
    const items = await page.evaluate(`(async () => {
      const res = await fetch(${JSON.stringify('https:' + url)});
      const json = await res.json();
      return { data: json.body || json };
    })()`);

    return items.data.map((item, idx) => ({ ... }));
  },
});

キーポイント

ステップ注意事項
中間 ID を抽出優先的に __INITIAL_STATE__ から取得、余分な API 呼び出しを回避
Wbi 署名B 站 /wbi/ エンドポイント強制検証 w_rid、純粋 fetch は 403 返却
空値判定HTTP 200 でも、コア フィールドが空文字列の可能性あり(リスク制御ダウングレード)
CDN URL// で開始することが多く、https: を忘れずに補足
JSON.stringifyURL を evaluate に埋め込む時、注入を避けるため必ず使用

よくある落とし穴

落とし穴現象解決策
navigate が欠けるevaluate が Target page context エラーを返すevaluate 前に page.goto() ステップを追加
ネストフィールド アクセス${{ item.node?.title }} が機能しないevaluate 内でデータをフラット化、テンプレートで optional chaining を使用しない
strategy: public が欠ける公開 API もブラウザーを起動、7 秒 → 1 秒公開 API に strategy: public + browser: false を追加
evaluate が文字列を返すmap ステップが配列ではなく "" を受信pipeline が自動解析しますが、evaluate 内で .map() をして整形することをお勧め
検索パラメーターが二重エンコード${{ args.query }} がブラウザーで二重エンコードevaluate 内で encodeURIComponent() で手動エンコード
Cookie 期限切れ401 / 空データを返すブラウザーでターゲット サイトに再度ログイン
Extension tab 残留Chrome に chrome-extension:// tab が増える自動クリーンアップされました。残留の場合、手動で閉じてください
TS evaluate 形式() => {}result is not a function を返すTS の page.evaluate() は IIFE を必ず使用:(async () => { ... })()
ページが非同期読み込みevaluate が空データを取得(store state がまだ更新中)evaluate 内でポーリングでデータ出現を待つ、または wait 時間を増加
evaluate に大量の JS 埋め込みデバッグが困難、文字列エスケープ問題ロジックを func() 内に配置、ネイティブ TS で記述
リスク制御ブロック(偽 200)取得した JSON のコア データが "" (空文字列)極めて誤判定しやすい。必ず断言を追加!コア データなしの場合、すぐに認証 Tier のアップグレードを要求し Cookie を再設定
API が見つからないexplore が出力した全て のスコアが深層データを取得不可ページ上のボタンをクリックして遅延ロード データをトリガー、getInterceptedRequests と組み合わせて取得

AI Agent で自動的にアダプターを生成

最速の方法は AI Agent に全流程を完了させることです:

# 1 キー:探索 → 分析 → 合成 → 登録
opencli generate https://www.example.com --goal "hot"

# または分步実行:
opencli explore https://www.example.com --site mysite           # API を発見
opencli explore https://www.example.com --auto --click "字幕,CC"  # クリックシミュレーションで遅延ロード API をトリガー
opencli synthesize mysite                                        # 候補 TS を生成
opencli verify mysite/hot --smoke                                # スモーク テスト

生成された候補 TS は .opencli/explore/mysite/candidates/ に保存され、clis/mysite/ に直接コピーして微調整できます。

Record ワークフロー

record は「explore で自動発見できない」ページ(ログイン操作、複雑なインタラクション、SPA 内ルーティングが必要)向けの手動録画スキームです。

動作原理

opencli record <url>
  → automation ウィンドウを開きターゲット URL に移動
  → すべての tab に fetch/XHR インターセプターを注入(べき等、再注入可能)
  → 2 秒ごとにポーリング:新しい tab を自動注入、すべての tab のキャプチャ バッファを drain
  → タイムアウト(デフォルト 60 秒)または Enter キー押下で停止
  → キャプチャ JSON リクエストを分析:重複排除 → スコア → 候補 TS を生成

インターセプター特性

  • window.fetchXMLHttpRequest を同時に patch
  • Content-Type: application/json のレスポンスのみキャプチャ
  • オブジェクト キー 2 個未満の純粋なオブジェクト レスポンスをフィルター(トラッキング/ping を避ける)
  • クロス tab 分離:各 tab は独立したバッファ、ポーリング時に個別 drain
  • べき等注入:同じ tab への 2 度目の注入時は元の関数をまず restore してから再度 patch、キャプチャ済みデータの紛失なし

使用ステップ

# 1. 録画を開始(--timeout で操作時間に余裕を持たせることをお勧め)
opencli record "https://example.com/page" --timeout 120000

# 2. ポップアップした automation ウィンドウでページを正常に操作:
#    - リストを開く、検索、エントリをクリック、Tab を切り替え
#    - ネットワーク リクエストをトリガーするすべての操作が記録されます

# 3. 操作完了後 Enter キーを押して停止(またはタイムアウト自動停止待機)

# 4. 結果を確認
cat .opencli/record/<site>/captured.json        # 生キャプチャ
ls  .opencli/record/<site>/candidates/          # 候補 TS

ページタイプと キャプチャ期待値

ページタイプキャプチャ予想量説明
リスト/検索ページ多数(5~20+)検索/ページング のたびに新しいリクエストがトリガー
詳細ページ(読み取り専用)少数(1~5)最初の画面データが一度に返却、以降の操作は form/redirect を使用
SPA 内ルーティング切り替え中程度ルート切り替え時に新しいインターフェースをトリガー、ただし最初の画面リクエストは注入前に発出
ログイン必須のページ操作により異なるChrome が既にターゲット サイトでログインしていることを確認

注意:ページがナビゲーション完了前にほとんどのリクエストを発出した場合(サーバー サイド レンダリング / SSR ハイドレーション)、インターセプターはこれらのリクエストを逃します。 解決策:ページ読み込み完了後、新しいリクエストを生成する操作を手動でトリガー(検索、ページング、Tab 切り替え、折りたたみ項目の展開など)。

候補 TS の微調整

生成された候補 TS は出発点です。通常は調整が必要です(特に tae などの内部システム):

候補構造(自動生成、調整可能):

// 自動生成された候補、微調整が必要
// site: tae
// name: getList          # URL path から推定される名前
// strategy: cookie
browser: true
pipeline:
  - navigate: https://...
  - evaluate: |
      (async () => {
        const res = await fetch('/approval/getList.json?procInsId=...', { credentials: 'include' });
        const data = await res.json();
        return (data?.content?.operatorRecords || []).map(item => ({ ... }));
      })()

TS CLI に変換clis/tae/add-expense.ts スタイルを参考):

import { cli, Strategy } from '@jackwener/opencli/registry';

cli({
  site: 'tae',
  name: 'get-approval',
  description: '領収書返金申請の承認フロー及び操作ログを確認',
  domain: 'tae.alibaba-inc.com',
  strategy: Strategy.COOKIE,
  browser: true,
  args: [
    { name: 'proc_ins_id', type: 'string', required: true, positional: true, help: 'プロセス インスタンス ID(procInsId)' },
  ],
  columns: ['step', 'operator', 'action', 'time'],
  func: async (page, kwargs) => {
    await page.goto('https://tae.alibaba-inc.com/expense/pc.html?_authType=SAML');
    await page.wait(2);
    const result = await page.evaluate(`(async () => {
      const res = await fetch('/approval/getList.json?taskId=&procInsId=${kwargs.proc_ins_id}', {
        credentials: 'include'
      });
      const data = await res.json();
      return data?.content?.operatorRecords || [];
    })()`);
    return (result as any[]).map((r, i) => ({
      step: i + 1,
      operator: r.operatorName || r.userId,
      action: r.operationType,
      time: r.operateTime,
    }));
  },
});

変換のポイント

  1. URL の動的 ID(procInsIdtaskId など)を args に抽出
  2. captured.json の実データ構造を使用して正しいデータ パスを確定(content.operatorRecords など)
  3. tae システムは統一的に { success, content, errorCode, errorMsg } で外層をラップ。データは content.* から取得
  4. 認証方式:cookie(credentials: 'include')、追加ヘッダー不要
  5. ファイルを clis/<site>/ に配置。手動登録不要。npm run build で自動検出

トラブルシューティング

現象原因対策
キャプチャ 0 件インターセプター注入失敗、またはページに JSON API なしdaemon 実行確認:curl localhost:19825/status
キャプチャ量少(1~3 件)ページが読み取り専用詳細ページ、最初の画面データは注入前に発出手動で更多リクエストをトリガー(検索/ページング)、またはリスト ページに切り替え
候補 TS が 0キャプチャされた JSON にすべて array 構造なしcaptured.json を直接確認して TS CLI を手書き
新規オープンの tab がインターセプトされないポーリング間隔内で tab が既に閉じられた--poll 500 で短縮
2 度目の record 実行でデータが不連続正常。毎回 record 起動で新 automation ウィンドウ処理不要

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
joeseesun
リポジトリ
joeseesun/opencli-skill
ライセンス
MIT
最終更新
不明

Source: https://github.com/joeseesun/opencli-skill / ライセンス: MIT

関連スキル

汎用ソフトウェア開発⭐ リポ 39,967

doubt-driven-development

重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 1,175

apprun-skills

TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。

by yysun
OpenAIソフトウェア開発⭐ リポ 797

desloppify

コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。

by Git-on-my-level
汎用ソフトウェア開発⭐ リポ 39,967

debugging-and-error-recovery

テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

test-driven-development

テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

incremental-implementation

変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。

by addyosmani
本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: joeseesun · joeseesun/opencli-skill · ライセンス: MIT