Merge pull request #193 from Panniantong/feat/twitter-bird-migration

feat(twitter): migrate from xreach to bird CLI
This commit is contained in:
Pnant 2026-03-23 15:52:04 +08:00 committed by GitHub
commit 90bb4e0266
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 199 additions and 193 deletions

View file

@ -56,7 +56,7 @@ AI Agent 已经能帮你写代码、改文档、管项目——但你让它去
|---|---|
| 💰 **完全免费** | 所有工具开源、所有 API 免费。唯一可能花钱的是服务器代理($1/月),本地电脑不需要 |
| 🔒 **隐私安全** | Cookie 只存在你本地,不上传不外传。代码完全开源,随时可审查 |
| 🔄 **持续更新** | 底层工具yt-dlp、xreach、Jina Reader 等)定期追踪更新到最新版,你不用自己盯 |
| 🔄 **持续更新** | 底层工具yt-dlp、bird、Jina Reader 等)定期追踪更新到最新版,你不用自己盯 |
| 🤖 **兼容所有 Agent** | Claude Code、OpenClaw、Cursor、Windsurf……任何能跑命令行的 Agent 都能用 |
| 🩺 **自带诊断** | `agent-reach doctor` 一条命令告诉你哪个通、哪个不通、怎么修 |
@ -95,7 +95,7 @@ AI Agent 已经能帮你写代码、改文档、管项目——但你让它去
> ⚠️ **OpenClaw 用户请先确认 exec 权限已开启**
>
> Agent Reach 依赖 Agent 执行 shell 命令(`pip install``mcporter``xreach` 等)。如果你的 OpenClaw 使用了默认的 `messaging` 工具配置Agent 将无法执行命令。**安装前请先开启 exec 权限**
> Agent Reach 依赖 Agent 执行 shell 命令(`pip install``mcporter``bird` 等)。如果你的 OpenClaw 使用了默认的 `messaging` 工具配置Agent 将无法执行命令。**安装前请先开启 exec 权限**
>
> ```bash
> openclaw config set tools.profile "coding"
@ -126,7 +126,7 @@ AI Agent 已经能帮你写代码、改文档、管项目——但你让它去
<summary>它会做什么?(点击展开)</summary>
1. **安装 CLI 工具**`pip install` 装好 `agent-reach` 命令行
2. **安装系统依赖** — 自动检测并安装 Node.js、gh CLI、mcporter、xreach
2. **安装系统依赖** — 自动检测并安装 Node.js、gh CLI、mcporter、bird
3. **配置搜索引擎** — 通过 MCP 接入 Exa免费无需 API Key
4. **检测环境** — 判断是本地电脑还是服务器,给出对应的配置建议
5. **注册 SKILL.md** — 在 Agent 的 skills 目录安装使用指南,以后 Agent 遇到"搜推特"、"看视频"这类需求,会自动知道该调哪个上游工具
@ -143,7 +143,7 @@ AI Agent 已经能帮你写代码、改文档、管项目——但你让它去
- "帮我看看这个链接" → `curl https://r.jina.ai/URL` 读任意网页
- "这个 GitHub 仓库是做什么的" → `gh repo view owner/repo`
- "这个视频讲了什么" → `yt-dlp --dump-json URL` 提取字幕
- "帮我看看这条推文" → `xreach tweet URL --json`
- "帮我看看这条推文" → `bird read URL`
- "订阅这个 RSS" → `feedparser` 解析
- "搜一下 GitHub 上有什么 LLM 框架" → `gh search repos "LLM framework"`
@ -159,7 +159,7 @@ AI Agent 已经能帮你写代码、改文档、管项目——但你让它去
Agent Reach 做的事情很简单:**帮你把这些选型和配置的活儿做完了。**
安装完成后Agent 直接调用上游工具(xreach CLI、yt-dlp、mcporter、gh CLI 等),不需要经过 Agent Reach 的包装层。
安装完成后Agent 直接调用上游工具(bird CLI、yt-dlp、mcporter、gh CLI 等),不需要经过 Agent Reach 的包装层。
### 🔌 每个渠道都是可插拔的
@ -168,7 +168,7 @@ Agent Reach 做的事情很简单:**帮你把这些选型和配置的活儿做
```
channels/
├── web.py → Jina Reader ← 可以换成 Firecrawl、Crawl4AI……
├── twitter.py → xreach ← 可以换成 Nitter、官方 API……
├── twitter.py → bird ← 可以换成 Nitter、官方 API……
├── youtube.py → yt-dlp ← 可以换成 YouTube API、Whisper……
├── github.py → gh CLI ← 可以换成 REST API、PyGithub……
├── bilibili.py → yt-dlp ← 可以换成 bilibili-api……
@ -189,7 +189,7 @@ channels/
| 场景 | 选型 | 为什么选它 |
|------|------|-----------|
| 读网页 | [Jina Reader](https://github.com/jina-ai/reader) | 9.8K Star免费不需要 API Key |
| 读推特 | [xreach](https://www.npmjs.com/package/xreach-cli) | Cookie 登录,免费。官方 API 按量付费(读一条 $0.005 |
| 读推特 | [bird](https://www.npmjs.com/package/@steipete/bird) | Cookie 登录,免费。官方 API 按量付费(读一条 $0.005 |
| 视频字幕 + 搜索 | [yt-dlp](https://github.com/yt-dlp/yt-dlp) | 148K StarYouTube + B站 + 1800 站通吃 |
| 搜全网 | [Exa](https://exa.ai) via [mcporter](https://github.com/steipete/mcporter) | AI 语义搜索MCP 接入免 Key |
| GitHub | [gh CLI](https://cli.github.com) | 官方工具,认证后完整 API 能力 |
@ -282,13 +282,13 @@ Star 一下,下次需要的时候能找到。⭐
<details>
<summary><strong>AI Agent 怎么搜索 Twitter / X不想付 API 费用</strong></summary>
Agent Reach 使用 [xreach CLI](https://www.npmjs.com/package/xreach-cli) 通过 Cookie 认证访问 Twitter完全免费。安装 Agent Reach 后,用 Cookie-Editor 导出你的 Twitter Cookie运行 `agent-reach configure twitter-cookies "your_cookies"` 即可。之后 Agent 就可以用 `xreach search "关键词" --json` 搜索推文了。
Agent Reach 使用 [bird CLI](https://www.npmjs.com/package/@steipete/bird) 通过 Cookie 认证访问 Twitter完全免费。安装 Agent Reach 后,用 Cookie-Editor 导出你的 Twitter Cookie运行 `agent-reach configure twitter-cookies "your_cookies"` 即可。之后 Agent 就可以用 `bird search "关键词"` 搜索推文了。
</details>
<details>
<summary><strong>How to search Twitter/X with AI agent for free (no API)?</strong></summary>
Agent Reach uses the xreach CLI with cookie auth — zero API fees. After installing, export your Twitter cookies with the Cookie-Editor extension, run `agent-reach configure twitter-cookies "your_cookies"`, then your agent can search with `xreach search "query" --json`.
Agent Reach uses the bird CLI with cookie auth — zero API fees. After installing, export your Twitter cookies with the Cookie-Editor extension, run `agent-reach configure twitter-cookies "your_cookies"`, then your agent can search with `bird search "query"`.
</details>
<details>
@ -326,14 +326,14 @@ Yes! Agent Reach is an installer + configuration tool — any AI coding agent th
<details>
<summary><strong>Is this free? Any API costs?</strong></summary>
100% free. All backends are open-source tools (xreach CLI, yt-dlp, Jina Reader, Exa, etc.) that don't require paid API keys. The only optional cost is a residential proxy (~$1/month) if you need Reddit/Bilibili access from a server.
100% free. All backends are open-source tools (bird CLI, yt-dlp, Jina Reader, Exa, etc.) that don't require paid API keys. The only optional cost is a residential proxy (~$1/month) if you need Reddit/Bilibili access from a server.
</details>
---
## 致谢
[Jina Reader](https://github.com/jina-ai/reader) · [yt-dlp](https://github.com/yt-dlp/yt-dlp) · [xreach](https://www.npmjs.com/package/xreach-cli) · [Exa](https://exa.ai) · [mcporter](https://github.com/steipete/mcporter) · [feedparser](https://github.com/kurtmckee/feedparser) · [xiaohongshu-mcp](https://github.com/xpzouying/xiaohongshu-mcp) · [douyin-mcp-server](https://github.com/yzfly/douyin-mcp-server) · [linkedin-scraper-mcp](https://github.com/stickerdaniel/linkedin-mcp-server)
[Jina Reader](https://github.com/jina-ai/reader) · [yt-dlp](https://github.com/yt-dlp/yt-dlp) · [bird](https://www.npmjs.com/package/@steipete/bird) · [Exa](https://exa.ai) · [mcporter](https://github.com/steipete/mcporter) · [feedparser](https://github.com/kurtmckee/feedparser) · [xiaohongshu-mcp](https://github.com/xpzouying/xiaohongshu-mcp) · [douyin-mcp-server](https://github.com/yzfly/douyin-mcp-server) · [linkedin-scraper-mcp](https://github.com/stickerdaniel/linkedin-mcp-server)
## 联系

View file

@ -1,76 +1,15 @@
# -*- coding: utf-8 -*-
"""Twitter/X — check if xreach CLI is available."""
"""Twitter/X — check if bird CLI (@steipete/bird) is available."""
import json
import shutil
import subprocess
from .base import Channel
# Minimum xreach-cli version with longform tweet and X Article support.
# v0.3.2 added: extractTweetText() preferring note_tweet for long tweets (#aad6a16)
# and X Article URL support (/article/ path, #2e05825).
_MIN_XREACH_VERSION = (0, 3, 2)
def _parse_version(ver_str: str) -> tuple[int, ...]:
"""Parse a semver string like '0.3.2' into a tuple (0, 3, 2)."""
try:
return tuple(int(x) for x in ver_str.strip().split(".")[:3])
except (ValueError, AttributeError):
return (0, 0, 0)
def _detect_xreach_version(xreach_path: str) -> str:
"""Best-effort xreach version detection.
Some xreach-cli releases ship package.json@0.3.2 while `xreach --version`
still prints 0.3.0 because the embedded dist version file was not updated.
Prefer the newer of:
1) `xreach --version`
2) `npm list -g xreach-cli --json --depth=0`
"""
versions: list[str] = []
try:
ver_result = subprocess.run(
[xreach_path, "--version"], capture_output=True,
encoding="utf-8", errors="replace", timeout=5
)
version_str = (ver_result.stdout or ver_result.stderr).strip()
if version_str:
versions.append(version_str)
except Exception:
pass
npm = shutil.which("npm")
if npm:
try:
npm_result = subprocess.run(
[npm, "list", "-g", "xreach-cli", "--json", "--depth=0"],
capture_output=True, encoding="utf-8", errors="replace", timeout=10,
)
if npm_result.returncode == 0 and npm_result.stdout:
data = json.loads(npm_result.stdout)
npm_ver = (
data.get("dependencies", {})
.get("xreach-cli", {})
.get("version", "")
.strip()
)
if npm_ver:
versions.append(npm_ver)
except Exception:
pass
if not versions:
return ""
return max(versions, key=_parse_version)
class TwitterChannel(Channel):
name = "twitter"
description = "Twitter/X 推文"
backends = ["xreach CLI"]
backends = ["bird CLI"]
tier = 1
def can_handle(self, url: str) -> bool:
@ -79,36 +18,33 @@ class TwitterChannel(Channel):
return "x.com" in d or "twitter.com" in d
def check(self, config=None):
xreach = shutil.which("xreach")
if not xreach:
bird = shutil.which("bird") or shutil.which("birdx")
if not bird:
return "warn", (
"xreach CLI 未安装。搜索可通过 Exa 替代。安装:\n"
" npm install -g xreach-cli"
"bird CLI 未安装。搜索可通过 Exa 替代。安装:\n"
" npm install -g @steipete/bird"
)
# Check version — longform tweet support requires >= 0.3.2
try:
version_str = _detect_xreach_version(xreach)
version_tuple = _parse_version(version_str)
if version_str and version_tuple < _MIN_XREACH_VERSION:
min_str = ".".join(str(x) for x in _MIN_XREACH_VERSION)
return "warn", (
f"xreach CLI 版本过旧(当前 {version_str},需 >= {min_str})。"
f"旧版本无法读取长文推文note_tweet和 X Article。升级\n"
f" npm install -g xreach-cli@latest"
)
except Exception:
pass # version check failure is non-fatal; proceed to auth check
try:
r = subprocess.run(
[xreach, "auth", "check"], capture_output=True,
[bird, "check"], capture_output=True,
encoding="utf-8", errors="replace", timeout=10
)
output = (r.stdout or "") + (r.stderr or "")
if r.returncode == 0:
return "ok", "完整可用(读取、搜索推文,含长文/X Article"
# bird check returns 1 when auth is missing
if "Missing credentials" in output or "missing" in output.lower():
return "warn", (
"bird CLI 已安装但未配置认证。设置环境变量:\n"
" export AUTH_TOKEN=\"xxx\"\n"
" export CT0=\"yyy\"\n"
"或运行:\n"
" agent-reach configure twitter-cookies \"auth_token=xxx; ct0=yyy\""
)
return "warn", (
"xreach CLI 已安装但未配置 Cookie。运行\n"
"bird CLI 已安装但认证检查失败。运行:\n"
" agent-reach configure twitter-cookies \"auth_token=xxx; ct0=yyy\""
)
except Exception:
return "warn", "xreach CLI 已安装但连接失败"
return "warn", "bird CLI 已安装但连接失败"

View file

@ -395,24 +395,24 @@ def _install_system_deps():
except Exception:
print(" [!] Node.js install failed. Try: apt install nodejs npm, or nvm install 22, or download from https://nodejs.org")
# ── xreach CLI (for Twitter search) ──
if shutil.which("xreach"):
print("xreach CLI already installed")
# ── bird CLI (for Twitter search) ──
if shutil.which("bird") or shutil.which("birdx"):
print("bird CLI already installed")
else:
if shutil.which("npm"):
try:
subprocess.run(
["npm", "install", "-g", "xreach-cli"],
["npm", "install", "-g", "@steipete/bird"],
capture_output=True, encoding="utf-8", errors="replace", timeout=120,
)
if shutil.which("xreach"):
print("xreach CLI installed (Twitter search + timeline)")
if shutil.which("bird") or shutil.which("birdx"):
print("bird CLI installed (Twitter search + timeline)")
else:
print(" -- xreach CLI install failed (optional — Twitter reading still works via Jina)")
print(" -- bird CLI install failed (optional — Twitter reading still works via Jina)")
except Exception:
print(" -- xreach CLI install failed (optional — Twitter reading still works via Jina)")
print(" -- bird CLI install failed (optional — Twitter reading still works via Jina)")
else:
print(" -- xreach CLI requires Node.js (optional — Twitter reading still works via Jina)")
print(" -- bird CLI requires Node.js (optional — Twitter reading still works via Jina)")
# ── undici (proxy support for Node.js fetch) ──
npm_cmd = shutil.which("npm")
@ -426,7 +426,7 @@ def _install_system_deps():
subprocess.run([npm_cmd, "install", "-g", "undici"], capture_output=True, encoding="utf-8", errors="replace", timeout=60)
print(" ✅ undici installed (Node.js proxy support)")
except Exception:
print(" -- undici install failed (optional — xreach may not work behind proxies)")
print(" -- undici install failed (optional — bird may not work behind proxies)")
# ── yt-dlp JS runtime config (YouTube requires external JS runtime) ──
if shutil.which("node"):
@ -626,7 +626,7 @@ def _install_system_deps_safe():
deps = [
("gh", ["gh"], "GitHub CLI", "https://cli.github.com — or: apt install gh / brew install gh"),
("node", ["node", "npm"], "Node.js", "https://nodejs.org — or: apt install nodejs npm"),
("xreach", ["xreach"], "xreach CLI (Twitter)", "npm install -g xreach-cli"),
("bird", ["bird", "birdx"], "bird CLI (Twitter)", "npm install -g @steipete/bird"),
]
missing = []
@ -679,7 +679,7 @@ def _install_system_deps_dryrun():
checks = [
("gh CLI", ["gh"], "apt install gh / brew install gh"),
("Node.js", ["node"], "curl NodeSource setup | bash + apt install nodejs"),
("xreach CLI", ["xreach"], "npm install -g xreach-cli"),
("bird CLI", ["bird", "birdx"], "npm install -g @steipete/bird"),
]
for label, binaries, method in checks:
@ -924,9 +924,10 @@ def _cmd_configure(args):
config.set("twitter_auth_token", auth_token)
config.set("twitter_ct0", ct0)
# Sync credentials to xreach's session.json so xreach auth check works
# Sync credentials to bird CLI env
try:
import json
# Legacy: sync to xfetch session.json for backward compat
xfetch_dir = os.path.join(os.path.expanduser("~"), ".config", "xfetch")
os.makedirs(xfetch_dir, exist_ok=True)
session_path = os.path.join(xfetch_dir, "session.json")
@ -939,24 +940,34 @@ def _cmd_configure(args):
with open(session_path, "w", encoding="utf-8") as sf:
json.dump(session_data, sf, indent=2)
os.chmod(session_path, 0o600)
print("✅ Twitter cookies configured (synced to xreach)!")
# bird CLI: write shell-sourceable credentials.env
bird_dir = os.path.join(os.path.expanduser("~"), ".config", "bird")
os.makedirs(bird_dir, exist_ok=True)
env_path = os.path.join(bird_dir, "credentials.env")
with open(env_path, "w", encoding="utf-8") as f:
f.write(f'AUTH_TOKEN="{auth_token}"\n')
f.write(f'CT0="{ct0}"\n')
os.chmod(env_path, 0o600)
print("✅ Twitter cookies configured (synced to bird)!")
except Exception as e:
print("✅ Twitter cookies configured!")
print(f"[!] Could not sync to xreach session.json: {e}")
print(f"[!] Could not sync to bird credentials: {e}")
print("Testing Twitter access...", end=" ")
try:
import subprocess
xreach = shutil.which("xreach")
if not xreach:
print("[!] xreach CLI not installed. Run: npm install -g xreach-cli")
bird = shutil.which("bird") or shutil.which("birdx")
if not bird:
print("[!] bird CLI not installed. Run: npm install -g @steipete/bird")
else:
import os
env = os.environ.copy()
env["AUTH_TOKEN"] = auth_token
env["CT0"] = ct0
result = subprocess.run(
[xreach, "search", "test", "-n", "1"],
[bird, "search", "test", "-n", "1"],
capture_output=True, encoding="utf-8", errors="replace", timeout=15,
env=env,
)
@ -1259,7 +1270,7 @@ def _cmd_uninstall(args):
print()
print("Optional: remove tools installed by Agent Reach:")
print(" npm uninstall -g mcporter")
print(" npm uninstall -g xreach-cli")
print(" npm uninstall -g @steipete/bird")
print(" npm uninstall -g undici")

View file

@ -22,7 +22,7 @@ class Config:
FEATURE_REQUIREMENTS = {
"exa_search": ["exa_api_key"],
"reddit_proxy": ["reddit_proxy"],
"twitter_xreach": ["twitter_auth_token", "twitter_ct0"],
"twitter_xreach": ["twitter_auth_token", "twitter_ct0"], # legacy key name; used by bird CLI
"groq_whisper": ["groq_api_key"],
"github_token": ["github_token"],
}

View file

@ -113,7 +113,7 @@ def extract_all(browser: str = "chrome") -> Dict[str, dict]:
def _sync_xfetch_session(auth_token: str, ct0: str) -> None:
"""Sync Twitter credentials to ~/.config/xfetch/session.json for xreach CLI."""
"""Sync Twitter credentials to ~/.config/xfetch/session.json (legacy xreach compat)."""
import json
import os
@ -138,6 +138,31 @@ def _sync_xfetch_session(auth_token: str, ct0: str) -> None:
pass
def _sync_bird_env(auth_token: str, ct0: str) -> None:
"""Write Twitter credentials to ~/.config/bird/credentials.env for bird CLI.
bird reads AUTH_TOKEN and CT0 from environment variables. This writes a
shell-sourceable file so users can `source ~/.config/bird/credentials.env`.
"""
import os
try:
bird_dir = os.path.join(os.path.expanduser("~"), ".config", "bird")
os.makedirs(bird_dir, exist_ok=True)
env_path = os.path.join(bird_dir, "credentials.env")
with open(env_path, "w", encoding="utf-8") as f:
f.write(f'AUTH_TOKEN="{auth_token}"\n')
f.write(f'CT0="{ct0}"\n')
os.chmod(env_path, 0o600)
except Exception:
# Non-fatal: agent-reach config is the source of truth, bird env sync is best-effort
pass
# Alias for callers expecting the name _sync_bird_credentials
_sync_bird_credentials = _sync_bird_env
def configure_from_browser(browser: str, config) -> List[Tuple[str, bool, str]]:
"""
Extract cookies and configure all found platforms.
@ -162,7 +187,8 @@ def configure_from_browser(browser: str, config) -> List[Tuple[str, bool, str]]:
if "auth_token" in tc and "ct0" in tc:
config.set("twitter_auth_token", tc["auth_token"])
config.set("twitter_ct0", tc["ct0"])
# Sync credentials to xreach's session.json so `xreach auth check` works
# Sync credentials to bird CLI env and legacy xfetch session.json
_sync_bird_env(tc["auth_token"], tc["ct0"])
_sync_xfetch_session(tc["auth_token"], tc["ct0"])
results_list.append(("Twitter/X", True, "auth_token + ct0"))
else:

View file

@ -3,7 +3,7 @@
AgentReach installer, doctor, and configuration tool.
Agent Reach helps AI agents install and configure upstream platform tools
(xreach CLI, yt-dlp, mcporter, gh CLI, etc.). After installation, agents
(bird CLI, yt-dlp, mcporter, gh CLI, etc.). After installation, agents
call the upstream tools directly no wrapper layer needed.
Usage:

View file

@ -1,33 +1,35 @@
# Twitter 高级功能配置指南(xreach CLI
# Twitter 高级功能配置指南(bird CLI
Twitter 基础阅读通过 Jina Reader 免费可用,无需配置。
高级功能需要 xreach CLI
高级功能需要 bird CLI@steipete/bird
- 搜索推文(`xreach search`
- 读取完整推文和对话链(`xreach tweet`、`xreach thread`
- 用户时间线(`xreach tweets`
- 搜索推文(`bird search`
- 读取完整推文和对话链(`bird read`、`bird thread`
- 用户时间线(`bird user-tweets`
xreach 是免费开源工具npm 包 xreach-cli),但需要你的 Twitter 账号 cookie。
bird 是免费开源工具npm 包 @steipete/bird),但需要你的 Twitter 账号 cookie。
## 快速配置
1. 检查 xreach 是否安装:
1. 检查 bird 是否安装:
```bash
which xreach && echo "installed" || echo "not installed"
which bird && echo "installed" || echo "not installed"
```
2. 安装 xreach
2. 安装 bird
```bash
npm install -g xreach-cli
npm install -g @steipete/bird
```
> 备选包:`npm install -g @connormartin/bird`
3. 测试是否配置好:
```bash
AUTH_TOKEN="xxx" CT0="yyy" xreach search "test" -n 1
AUTH_TOKEN="xxx" CT0="yyy" bird search "test" -n 1
```
## 获取 CookieCookie-Editor 方式,推荐)
@ -47,7 +49,7 @@ agent-reach configure twitter-cookies "粘贴的 cookie JSON"
如果你已经知道 `auth_token``ct0`
1. 安装 xreach如果没装`npm install -g xreach-cli`
1. 安装 bird如果没装`npm install -g @steipete/bird`
2. 设置环境变量:
@ -59,19 +61,21 @@ export CT0="你的ct0"
3. 测试:
```bash
xreach search "test" --auth-token "$AUTH_TOKEN" --ct0 "$CT0" -n 1
bird search "test" -n 1
```
## 代理配置
> xreach CLI 内置代理支持,通过 `--proxy` 参数传入
> bird CLI 支持通过环境变量设置代理
```bash
xreach search "test" --auth-token "$AUTH_TOKEN" --ct0 "$CT0" --proxy "http://user:pass@host:port"
export HTTP_PROXY="http://user:pass@host:port"
export HTTPS_PROXY="http://user:pass@host:port"
bird search "test" -n 1
```
支持代理轮换文件
可以使用全局代理工具
```bash
xreach search "test" --auth-token "$AUTH_TOKEN" --ct0 "$CT0" --proxy-file proxies.txt
proxychains bird search "test" -n 1
```

View file

@ -5,7 +5,7 @@ Agent Reach MCP Server — expose doctor/status as MCP tool.
Run: python -m agent_reach.integrations.mcp_server
Agent Reach is an installer + doctor tool. For actual reading/searching,
agents should call upstream tools directly (xreach, yt-dlp, mcporter, etc.).
agents should call upstream tools directly (bird, yt-dlp, mcporter, etc.).
"""
import asyncio

View file

@ -39,13 +39,13 @@ mcporter call 'exa.web_search_exa(query: "query", numResults: 5)'
mcporter call 'exa.get_code_context_exa(query: "code question", tokensNum: 3000)'
```
## Twitter/X (xreach)
## Twitter/X (bird)
```bash
xreach search "query" -n 10 --json # search
xreach tweet URL_OR_ID --json # read tweet (supports /status/ and /article/ URLs)
xreach tweets @username -n 20 --json # user timeline
xreach thread URL_OR_ID --json # full thread
bird search "query" -n 10 # search
bird read URL_OR_ID # read tweet (supports /status/ and /article/ URLs)
bird user-tweets @username -n 20 # user timeline
bird thread URL_OR_ID # full thread
```
## YouTube (yt-dlp)

View file

@ -51,7 +51,7 @@ Update Agent Reach: https://raw.githubusercontent.com/Panniantong/agent-reach/ma
|---|---|
| 💰 **Completely free** | All tools are open source, all APIs are free. The only possible cost is a server proxy ($1/month) — local computers don't need one |
| 🔒 **Privacy safe** | Cookies stay local. Never uploaded. Fully open source — audit anytime |
| 🔄 **Kept up to date** | Upstream tools (yt-dlp, xreach, Jina Reader, etc.) are tracked and updated regularly |
| 🔄 **Kept up to date** | Upstream tools (yt-dlp, bird, Jina Reader, etc.) are tracked and updated regularly |
| 🤖 **Works with any Agent** | Claude Code, OpenClaw, Cursor, Windsurf… any Agent that can run commands |
| 🩺 **Built-in diagnostics** | `agent-reach doctor` — one command shows what works, what doesn't, and how to fix it |
@ -62,7 +62,7 @@ Update Agent Reach: https://raw.githubusercontent.com/Panniantong/agent-reach/ma
| Platform | Capabilities | Setup | Notes |
|----------|-------------|:-----:|-------|
| 🌐 **Web** | Read | Zero config | Any URL → clean Markdown ([Jina Reader](https://github.com/jina-ai/reader) ⭐9.8K) |
| 🐦 **Twitter/X** | Read · Search | Zero config / Cookie | Single tweets readable out of the box. Cookie unlocks search, timeline, posting ([xreach](https://www.npmjs.com/package/xreach-cli)) |
| 🐦 **Twitter/X** | Read · Search | Zero config / Cookie | Single tweets readable out of the box. Cookie unlocks search, timeline, posting ([bird](https://www.npmjs.com/package/@steipete/bird)) |
| 📕 **XiaoHongShu** | Read · Search · **Post · Comment · Like** | mcporter | Via [xiaohongshu-mcp](https://github.com/user/xiaohongshu-mcp) internal API, install and go |
| 🎵 **Douyin** | Video parsing · Watermark-free download | mcporter | Via [douyin-mcp-server](https://github.com/yzfly/douyin-mcp-server), no login needed |
| 💼 **LinkedIn** | Jina Reader (public pages) | Full profiles, companies, job search | Tell your Agent "help me set up LinkedIn" |
@ -126,7 +126,7 @@ No configuration needed — just tell your Agent:
- "Read this link" → `curl https://r.jina.ai/URL` for any web page
- "What's this GitHub repo about?" → `gh repo view owner/repo`
- "What does this video cover?" → `yt-dlp --dump-json URL` for subtitles
- "Read this tweet" → `xreach tweet URL --json`
- "Read this tweet" → `bird read URL`
- "Subscribe to this RSS" → `feedparser` to parse feeds
- "Search GitHub for LLM frameworks" → `gh search repos "LLM framework"`
@ -186,7 +186,7 @@ Every time you spin up a new Agent, you spend time finding tools, installing dep
Agent Reach does one simple thing: **it makes those tool selection and configuration decisions for you.**
After installation, your Agent calls the upstream tools directly (xreach CLI, yt-dlp, mcporter, gh CLI, etc.) — no wrapper layer in between.
After installation, your Agent calls the upstream tools directly (bird CLI, yt-dlp, mcporter, gh CLI, etc.) — no wrapper layer in between.
### 🔌 Every Channel is Pluggable
@ -195,7 +195,7 @@ Each platform maps to an upstream tool. **Don't like one? Swap it out.**
```
channels/
├── web.py → Jina Reader ← swap to Firecrawl, Crawl4AI…
├── twitter.py → xreach ← swap to Nitter, official API…
├── twitter.py → bird CLI ← swap to Nitter, official API…
├── youtube.py → yt-dlp ← swap to YouTube API, Whisper…
├── github.py → gh CLI ← swap to REST API, PyGithub…
├── bilibili.py → yt-dlp ← swap to bilibili-api…
@ -215,7 +215,7 @@ Each channel file only checks whether its upstream tool is installed and working
| Scenario | Tool | Why |
|----------|------|-----|
| Read web pages | [Jina Reader](https://github.com/jina-ai/reader) | 9.8K stars, free, no API key needed |
| Read tweets | [xreach](https://www.npmjs.com/package/xreach-cli) | Cookie auth, free. Official API is pay-per-use ($0.005/post read) |
| Read tweets | [bird](https://www.npmjs.com/package/@steipete/bird) | Cookie auth, free. Official API is pay-per-use ($0.005/post read) |
| Video subtitles + search | [yt-dlp](https://github.com/yt-dlp/yt-dlp) | 148K stars, YouTube + Bilibili + 1800 sites |
| Search the web | [Exa](https://exa.ai) via [mcporter](https://github.com/nicepkg/mcporter) | AI semantic search, MCP integration, no API key |
| GitHub | [gh CLI](https://cli.github.com) | Official tool, full API after auth |
@ -248,7 +248,7 @@ This project was entirely vibe-coded 🎸 There might be rough edges here and th
<details>
<summary><strong>How to search Twitter/X with AI agent without paying for API?</strong></summary>
Agent Reach uses the [xreach CLI](https://www.npmjs.com/package/xreach-cli) with cookie-based authentication — completely free, no Twitter API subscription needed. After installing Agent Reach, export your Twitter cookies using the Cookie-Editor Chrome extension, run `agent-reach configure twitter-cookies "your_cookies"`, and your agent can search with `xreach search "query" --json`.
Agent Reach uses the [bird CLI](https://www.npmjs.com/package/@steipete/bird) with cookie-based authentication — completely free, no Twitter API subscription needed. After installing Agent Reach, export your Twitter cookies using the Cookie-Editor Chrome extension, run `agent-reach configure twitter-cookies "your_cookies"`, and your agent can search with `bird search "query" -n 10`.
</details>
<details>
@ -272,13 +272,13 @@ Yes! Agent Reach is an installer + configuration tool. Any AI coding agent that
<details>
<summary><strong>Is Agent Reach free? Any API costs?</strong></summary>
100% free and open source. All backends (xreach CLI, yt-dlp, Jina Reader, Exa) are free tools that don't require paid API keys. The only optional cost is a residential proxy (~$1/month) if you need Reddit/Bilibili access from a server.
100% free and open source. All backends (bird CLI, yt-dlp, Jina Reader, Exa) are free tools that don't require paid API keys. The only optional cost is a residential proxy (~$1/month) if you need Reddit/Bilibili access from a server.
</details>
<details>
<summary><strong>Free alternative to Twitter API for web scraping?</strong></summary>
Agent Reach uses xreach CLI which accesses Twitter via cookie auth — same as your browser session. No API fees, no rate limit tiers, no developer account needed. Supports search, read tweets, read profiles, and timelines.
Agent Reach uses bird CLI which accesses Twitter via cookie auth — same as your browser session. No API fees, no rate limit tiers, no developer account needed. Supports search, read tweets, read profiles, and timelines.
</details>
<details>
@ -297,7 +297,7 @@ Install douyin-mcp-server, then your agent can use `mcporter call 'douyin.parse_
## Credits
[Jina Reader](https://github.com/jina-ai/reader) · [yt-dlp](https://github.com/yt-dlp/yt-dlp) · [xreach](https://www.npmjs.com/package/xreach-cli) · [Exa](https://exa.ai) · [feedparser](https://github.com/kurtmckee/feedparser) · [douyin-mcp-server](https://github.com/yzfly/douyin-mcp-server) · [linkedin-scraper-mcp](https://github.com/stickerdaniel/linkedin-mcp-server)
[Jina Reader](https://github.com/jina-ai/reader) · [yt-dlp](https://github.com/yt-dlp/yt-dlp) · [bird](https://www.npmjs.com/package/@steipete/bird) · [Exa](https://exa.ai) · [feedparser](https://github.com/kurtmckee/feedparser) · [douyin-mcp-server](https://github.com/yzfly/douyin-mcp-server) · [linkedin-scraper-mcp](https://github.com/stickerdaniel/linkedin-mcp-server)
## Contact

View file

@ -20,7 +20,7 @@ Copy this to your AI Agent:
### Goal
Install Agent Reach and all upstream tools so your user's agent has full internet access. After installation, you'll use the upstream tools directly (xreach CLI, yt-dlp, mcporter, gh CLI, etc.) — Agent Reach is just the installer and health checker.
Install Agent Reach and all upstream tools so your user's agent has full internet access. After installation, you'll use the upstream tools directly (bird CLI, yt-dlp, mcporter, gh CLI, etc.) — Agent Reach is just the installer and health checker.
### ⚠️ Boundaries
@ -63,7 +63,7 @@ pip install https://github.com/Panniantong/agent-reach/archive/main.zip
agent-reach install --env=auto
```
This auto-installs system dependencies (gh CLI, Node.js, mcporter, xreach), configures Exa search, detects environment, and tests all channels.
This auto-installs system dependencies (gh CLI, Node.js, mcporter, bird), configures Exa search, detects environment, and tests all channels.
> 💡 **macOS / Homebrew Python 提示 `externally-managed-environment`**
> 这是 PEP 668 保护,不是 Agent Reach 本身的问题。优先用 `pipx install ...`,或先创建 `venv` 再安装。
@ -129,13 +129,12 @@ agent-reach configure twitter-cookies "PASTED_STRING"
> **代理说明(中国大陆等需要翻墙的网络环境):**
>
> xreach CLI 使用 Node.js 原生 `fetch()`,默认不走系统代理(`HTTP_PROXY`/`HTTPS_PROXY`)。
> Agent Reach 已内置解决方案:安装时会自动安装 `undici``npm install -g undici`
> 运行 xreach 时自动注入 `EnvHttpProxyAgent`,让 fetch 走用户配置的代理。
> bird CLI 使用 Node.js在需要代理的网络环境下可通过环境变量配置代理。
> Agent Reach 安装时会自动安装 `undici``npm install -g undici`)以提供代理支持。
>
> **你Agent需要做的**
> 1. 确认 `undici` 已全局安装:`npm list -g undici`,没有就装:`npm install -g undici`
> 2. 确认用户配了代理:`agent-reach configure proxy http://user:pass@ip:port`
> 1. 确认用户配了代理:`agent-reach configure proxy http://user:pass@ip:port`
> 2. 设置环境变量:`export HTTP_PROXY="..." HTTPS_PROXY="..."`
> 3. Agent Reach 会自动处理剩下的,不需要用户做额外操作
>
> 如果用户报告 "fetch failed",参考 [troubleshooting.md](troubleshooting.md)
@ -325,7 +324,7 @@ After installation, use upstream tools directly. See SKILL.md for the full comma
| Platform | Upstream Tool | Example |
|----------|--------------|---------|
| Twitter/X | `xreach` | `xreach search "query" --json` |
| Twitter/X | `bird` | `bird search "query" -n 10` |
| YouTube | `yt-dlp` | `yt-dlp --dump-json URL` |
| Bilibili | `yt-dlp` | `yt-dlp --dump-json URL` |
| Reddit | `curl` | `curl -s "https://reddit.com/r/xxx.json"` |

View file

@ -1,44 +1,43 @@
# 常见问题排查
## Twitter/X: xreach CLI "fetch failed"
## Twitter/X: bird CLI 连接失败
**症状:** `xreach search` 或其他命令返回 "fetch failed"
**症状:** `bird search` 或其他命令返回错误
**原因:** xreach CLI 使用 Node.js 的 `undici` 库发请求。如果你的网络环境需要代理才能访问 x.com需要明确传入代理参数
**原因:** bird CLI 需要 AUTH_TOKEN 和 CT0 环境变量才能访问 Twitter API。如果你的网络环境需要代理才能访问 x.com需要配置代理
**解决方案:**
### 方案 1使用 --proxy 参数
### 方案 1设置环境变量代理
```bash
xreach search "test" --auth-token "$AUTH_TOKEN" --ct0 "$CT0" --proxy "http://user:pass@host:port"
export HTTP_PROXY="http://user:pass@host:port"
export HTTPS_PROXY="http://user:pass@host:port"
bird search "test" -n 1
```
### 方案 2使用全局代理工具
让代理工具接管所有网络流量,这样 xreach 的请求也会走代理:
让代理工具接管所有网络流量,这样 bird 的请求也会走代理:
```bash
# macOS — ClashX / Surge 开启"增强模式"
# Linux — proxychains 或 tun2socks
proxychains xreach search "test" -n 1
proxychains bird search "test" -n 1
```
### 方案 3不用 xreach,用 Exa 搜索替代
### 方案 3不用 bird,用 Exa 搜索替代
xreach 不可用时,可以直接用 Exa 搜索 Twitter 内容:
bird 不可用时,可以直接用 Exa 搜索 Twitter 内容:
```bash
mcporter call 'exa.web_search_exa(query: "site:x.com 搜索词", numResults: 5)'
```
### 方案 4设置 HTTP_PROXY 环境变量
### 方案 4检查认证
```bash
export HTTP_PROXY="http://127.0.0.1:7890"
export HTTPS_PROXY="http://127.0.0.1:7890"
xreach search "test"
bird check
```
> ⚠️ 注意Node.js 原生 fetch 不一定读取这些环境变量,推荐用方案 1 的 --proxy 参数
> 如果返回 "Missing credentials",需要设置 AUTH_TOKEN 和 CT0 环境变量。

View file

@ -2,7 +2,7 @@
from unittest.mock import patch, Mock
from agent_reach.channels.twitter import _detect_xreach_version, TwitterChannel
from agent_reach.channels.twitter import TwitterChannel
def _cp(stdout="", stderr="", returncode=0):
@ -13,27 +13,58 @@ def _cp(stdout="", stderr="", returncode=0):
return m
def test_detect_xreach_version_prefers_npm_when_cli_version_is_stale():
with patch("shutil.which", return_value="/opt/homebrew/bin/npm"), patch(
"subprocess.run",
side_effect=[
_cp(stdout="0.3.0\n"),
_cp(stdout='{"dependencies":{"xreach-cli":{"version":"0.3.2"}}}'),
],
):
assert _detect_xreach_version("/opt/homebrew/bin/xreach") == "0.3.2"
def test_twitter_channel_does_not_false_warn_when_npm_has_newer_xreach():
def test_check_bird_found_and_auth_ok():
"""bird found + bird check returns 0 → ok."""
channel = TwitterChannel()
with patch("shutil.which", side_effect=lambda name: "/opt/homebrew/bin/xreach" if name == "xreach" else "/opt/homebrew/bin/npm"), patch(
with patch("shutil.which", side_effect=lambda name: "/usr/local/bin/bird" if name == "bird" else None), patch(
"subprocess.run",
side_effect=[
_cp(stdout="0.3.0\n"),
_cp(stdout='{"dependencies":{"xreach-cli":{"version":"0.3.2"}}}'),
_cp(stdout="authenticated\n", returncode=0),
],
return_value=_cp(stdout="Authenticated as @user\n", returncode=0),
):
status, message = channel.check()
assert status == "ok"
assert "完整可用" in message
def test_check_bird_found_auth_missing():
"""bird found + bird check returns 1 with 'Missing credentials' → warn about auth."""
channel = TwitterChannel()
with patch("shutil.which", side_effect=lambda name: "/usr/local/bin/bird" if name == "bird" else None), patch(
"subprocess.run",
return_value=_cp(stderr="Missing credentials: AUTH_TOKEN and CT0 required\n", returncode=1),
):
status, message = channel.check()
assert status == "warn"
assert "未配置认证" in message
def test_check_bird_not_found():
"""bird not found → warn with install hint for @steipete/bird."""
channel = TwitterChannel()
with patch("shutil.which", return_value=None):
status, message = channel.check()
assert status == "warn"
assert "@steipete/bird" in message
def test_check_birdx_binary_accepted():
"""birdx symlink is accepted as an alternative binary name."""
channel = TwitterChannel()
with patch("shutil.which", side_effect=lambda name: "/usr/local/bin/birdx" if name == "birdx" else None), patch(
"subprocess.run",
return_value=_cp(stdout="Authenticated as @user\n", returncode=0),
):
status, message = channel.check()
assert status == "ok"
assert "完整可用" in message
def test_check_bird_auth_failure_generic():
"""bird check returns 1 without 'Missing credentials' → generic auth failure warn."""
channel = TwitterChannel()
with patch("shutil.which", side_effect=lambda name: "/usr/local/bin/bird" if name == "bird" else None), patch(
"subprocess.run",
return_value=_cp(stderr="Error: token expired\n", returncode=1),
):
status, message = channel.check()
assert status == "warn"
assert "认证检查失败" in message