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_requests | JSON API エンドポイントをフィルター、URL パターンを記録 |
| 3. インタラクションをシミュレート | browser_click + browser_wait_for | 「字幕」「コメント」「フォロー」などのボタンをクリック |
| 4. 二次キャプチャ | browser_network_requests | ステップ 2 と比較して、新しくトリガーされた API を検出 |
| 5. API を検証 | browser_evaluate | fetch(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 | 推論される機能(hot、search、feed…)、信頼度と推奨パラメーター付き |
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.items、data.list)
1c. 高度な API 発見のショートカット法則(ヒューリスティック)
複雑なパケット キャプチャの相互作用に従う前に、以下の優先順位に従ってこれらを試してください:
- サフィックス ブルートフォース法 (
.json): Reddit のような複雑なウェブサイトでも、その URL に.jsonを追加するだけで(たとえば/r/all.json)、Cookie を携帯する状況で、fetchを直接利用して非常にクリーンな REST データを取得できます(Tier 2 Cookie 戦略で極速解決)。また、機能が充実した Xueqiu(雪球) のような場合も、この純 API 方式で取得でき、シンプルな TS パイプライン アダプターを構築する黄金の標準になります。 - グローバル状態検索法 (
__INITIAL_STATE__): 多くのサーバー サイド レンダリング (SSR) ウェブサイト(小紅書、Bilibili など)は、ホームページまたは詳細ページの完全なデータをグローバル window オブジェクトにマウントします。ネットワーク リクエストをインターセプトするのではなく、page.evaluate('() => window.__INITIAL_STATE__')を使用してデータツリー全体を取得する方が簡単です。 - アクティブ インタラクション トリガー法 (Active Interaction): 多くの深層 API(ビデオ字幕、コメント下の返信など)は遅延ロードです。静的キャプチャでデータが見つからない場合、
evaluateステップまたは手動ブレークポイント時に、ページ上の対応するボタン(「CC」、「すべて展開」など)をアクティブにクリックして、隠れたネットワーク fetch をトリガーすることを試してください。 - フレームワーク検出と Store Action 遮断: サイトが Vue + Pinia を使用している場合、
tapステップを使用してアクション を呼び出し、フロントエンド フレームワークに複雑な認証署名をカプセル化させることができます。 - 下位層 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 | 戦略 | 速度 | 複雑度 | ユースケース | 例 |
|---|---|---|---|---|---|
| 1 | public | ⚡ 約 1 秒 | 最小 | 公開 API、ログイン不要 | Hacker News、V2EX |
| 2 | cookie | 🔄 約 7 秒 | 簡単 | Cookie 認証で十分 | Bilibili、知乎、Reddit |
| 3 | header | 🔄 約 7 秒 | 中程度 | CSRF トークンまたは Bearer 必要 | Twitter GraphQL |
| 4 | intercept | 🔄 約 10 秒 | 高い | リクエストに複雑な署名あり | 小紅書(Pinia + XHR) |
| 5 | ui | 🐌 約 15 秒以上 | 最高 | API なし、純粋な DOM 解析 | レガシー ウェブサイト |
ステップ 2.5:準備作業(コードを書く前に)
まずテンプレートを探す:最も似ている既存のアダプターから始める
ゼロから始めないでください。まず同じサイトにある既存のアダプターを確認してください:
ls clis/<site>/ # 既存のものを確認
cat clis/<site>/feed.ts # 最も似ているものを読む
最も効率的な方法は、最も似ているアダプターをコピーして、3 つの場所を変更することです:
name→ 新しいコマンド名- API URL → ステップ 1 で発見したエンドポイント
- フィールド マッピング → 新しい 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.ts、hackernews/top.ts |
| navigate + evaluate(fetch) + データ マッピング | TS cli() + func() | zhihu/hot.ts |
| Pinia Store Action トリガー | TS cli() + func() | xiaohongshu/feed.ts、xiaohongshu/notifications.ts |
| 複雑な JS ロジック(state 読み取り、条件分岐) | TS cli() + func() | xiaohongshu/me.ts、bilibili/me.ts |
| XHR インターセプション + 署名 | TS cli() + func() | xiaohongshu/search.ts |
| GraphQL / ページング / Wbi 署名 | TS cli() + func() | bilibili/search.ts、twitter/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を使用してウェブサイト自身のXMLHttpRequestとfetchをハイジャック。ウェブサイトがリクエストを発行し、底層で解析済み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 で完了し、TSfunc()内でデータを直接処理。システム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.stringify | URL を 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.fetchとXMLHttpRequestを同時に patchContent-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,
}));
},
});
変換のポイント:
- URL の動的 ID(
procInsId、taskIdなど)をargsに抽出 captured.jsonの実データ構造を使用して正しいデータ パスを確定(content.operatorRecordsなど)- tae システムは統一的に
{ success, content, errorCode, errorMsg }で外層をラップ。データはcontent.*から取得 - 認証方式:cookie(
credentials: 'include')、追加ヘッダー不要 - ファイルを
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
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/joeseesun/opencli-skill / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。