qiaomu-opencli-oneshot について
qiaomu-opencli-oneshotは、URLと短い目的の説明からOpenCLIのコマンドを1つ作る開発者向けガイドです。ブラウザーでのAPI通信の取得からエンドポイント検証、TypeScriptアダプターの作成へ進み、ビルドと実行テストも行います。
主な機能
- 01URLと目的から単一コマンドを作成
- 02ブラウザー通信からAPIを特定
- 03Cookie、ヘッダー、インターセプト方式の検証
- 04TypeScriptアダプターテンプレート
- 05ビルド、登録、実行テスト
活用シーン
- 01Webページ用のCLIコマンドを追加
- 02認証付きAPIリクエストの再現可否を確認
- 03制限されたリクエストにインターセプト方式を選択
- 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 をご覧ください。
入力
| 項目 | 例 |
|---|---|
| URL | https://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 つ見つけます。次のフィールドを確認します。
| フィールド | 確認する内容 |
|---|---|
| URL | API パスのパターン(例:/i/api/graphql/xxx/ListsManagePinTimeline) |
| Method | GET / POST |
| Headers | Cookie、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 をご覧ください。
コメント
Markdown 対応