diff --git a/README.md b/README.md index 3b36e04..094d695 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ AI Agent 已经能帮你写代码、改文档、管项目——但你让它去 |---|---| | 💰 **完全免费** | 所有工具开源、所有 API 免费。唯一可能花钱的是服务器代理($1/月),本地电脑不需要 | | 🔒 **隐私安全** | Cookie 只存在你本地,不上传不外传。代码完全开源,随时可审查 | -| 🔄 **持续更新** | 底层工具(yt-dlp、birdx、Jina Reader 等)定期追踪更新到最新版,你不用自己盯 | +| 🔄 **持续更新** | 底层工具(yt-dlp、bird、Jina Reader 等)定期追踪更新到最新版,你不用自己盯 | | 🤖 **兼容所有 Agent** | Claude Code、OpenClaw、Cursor、Windsurf……任何能跑命令行的 Agent 都能用 | | 🩺 **自带诊断** | `agent-reach doctor` 一条命令告诉你哪个通、哪个不通、怎么修 | @@ -130,7 +130,7 @@ Agent Reach 做的事情很简单:**帮你把这些选型和配置的活儿做 ``` channels/ ├── web.py → Jina Reader ← 可以换成 Firecrawl、Crawl4AI…… -├── twitter.py → birdx ← 可以换成 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…… @@ -146,7 +146,7 @@ channels/ | 场景 | 选型 | 为什么选它 | |------|------|-----------| | 读网页 | [Jina Reader](https://github.com/jina-ai/reader) | 9.8K Star,免费,不需要 API Key | -| 读推特 | [birdx](https://github.com/runesleo/birdx) | Cookie 登录,免费。官方 API 按量付费(读一条 $0.005) | +| 读推特 | [bird](https://github.com/steipete/bird) | Cookie 登录,免费。官方 API 按量付费(读一条 $0.005) | | 视频字幕 + 搜索 | [yt-dlp](https://github.com/yt-dlp/yt-dlp) | 148K Star,YouTube + B站 + 1800 站通吃 | | 搜全网 | [Exa](https://exa.ai) via [mcporter](https://github.com/nicepkg/mcporter) | AI 语义搜索,MCP 接入免 Key | | GitHub | [gh CLI](https://cli.github.com) | 官方工具,认证后完整 API 能力 | @@ -183,7 +183,7 @@ Star 一下,下次需要的时候能找到。⭐ ## 致谢 -[Jina Reader](https://github.com/jina-ai/reader) · [yt-dlp](https://github.com/yt-dlp/yt-dlp) · [birdx](https://github.com/runesleo/birdx) · [Exa](https://exa.ai) · [feedparser](https://github.com/kurtmckee/feedparser) +[Jina Reader](https://github.com/jina-ai/reader) · [yt-dlp](https://github.com/yt-dlp/yt-dlp) · [bird](https://github.com/steipete/bird) · [Exa](https://exa.ai) · [feedparser](https://github.com/kurtmckee/feedparser) ## License diff --git a/agent_reach/channels/twitter.py b/agent_reach/channels/twitter.py index 09d7ac5..0e1d9ec 100644 --- a/agent_reach/channels/twitter.py +++ b/agent_reach/channels/twitter.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -"""Twitter/X — via birdx CLI (free) or Jina Reader fallback. +"""Twitter/X — via bird CLI (free) or Jina Reader fallback. -Backend: birdx (https://github.com/runesleo/birdx) for search/timeline +Backend: bird (@steipete/bird npm package) for search/timeline Jina Reader for single tweets Swap to: any Twitter access tool """ @@ -14,10 +14,29 @@ from typing import List import requests +def _bird_cmd(): + """Find bird CLI binary.""" + return shutil.which("bird") or shutil.which("birdx") + + +def _bird_env(config=None): + """Build env dict with Twitter cookies for bird CLI.""" + import os + env = os.environ.copy() + if config: + auth_token = config.get("twitter_auth_token") + ct0 = config.get("twitter_ct0") + if auth_token: + env["AUTH_TOKEN"] = auth_token + if ct0: + env["CT0"] = ct0 + return env + + class TwitterChannel(Channel): name = "twitter" description = "Twitter/X 推文" - backends = ["birdx", "Jina Reader"] + backends = ["bird", "Jina Reader"] tier = 0 # Single tweet reading is zero-config def can_handle(self, url: str) -> bool: @@ -26,21 +45,23 @@ class TwitterChannel(Channel): def check(self, config=None): # Basic reading always works (Jina fallback) - if shutil.which("birdx"): + if _bird_cmd(): return "ok", "搜索、时间线、发推全部可用" - return "ok", "可读取推文。安装 birdx + 配置 Cookie 可解锁搜索和发推" + return "ok", "可读取推文。安装 bird + 配置 Cookie 可解锁搜索和发推" async def read(self, url: str, config=None) -> ReadResult: - # Try birdx first - if shutil.which("birdx"): - return await self._read_birdx(url) + # Try bird first + bird = _bird_cmd() + if bird: + return await self._read_bird(url, bird, config) # Fallback: Jina Reader return await self._read_jina(url) - async def _read_birdx(self, url: str) -> ReadResult: + async def _read_bird(self, url: str, bird: str, config=None) -> ReadResult: result = subprocess.run( - ["birdx", "read", url], + [bird, "read", url], capture_output=True, text=True, timeout=30, + env=_bird_env(config), ) if result.returncode != 0: return await self._read_jina(url) @@ -84,7 +105,7 @@ class TwitterChannel(Channel): "The tweet may have been deleted, or the account is private.\n\n" "Tips:\n" "- Make sure the URL is correct\n" - "- Try: birdx read (if birdx is installed)\n" + "- Try: bird read (if bird CLI is installed)\n" "- For protected tweets, configure Twitter cookies: " "agent-reach configure twitter-cookies AUTH_TOKEN CT0", url=url, @@ -105,7 +126,7 @@ class TwitterChannel(Channel): "The tweet may have been deleted, or the account is private.\n\n" "Tips:\n" "- Make sure the URL is correct\n" - "- Try: birdx read (if birdx is installed)\n" + "- Try: bird read (if bird CLI is installed)\n" "- For protected tweets, configure Twitter cookies: " "agent-reach configure twitter-cookies AUTH_TOKEN CT0", url=url, @@ -115,27 +136,29 @@ class TwitterChannel(Channel): async def search(self, query: str, config=None, **kwargs) -> List[SearchResult]: limit = kwargs.get("limit", 10) - if shutil.which("birdx"): - return await self._search_birdx(query, limit) + bird = _bird_cmd() + if bird: + return await self._search_bird(query, limit, bird, config) # Fallback to Exa return await self._search_exa(query, limit, config) - async def _search_birdx(self, query: str, limit: int) -> List[SearchResult]: + async def _search_bird(self, query: str, limit: int, bird: str, config=None) -> List[SearchResult]: try: result = subprocess.run( - ["birdx", "search", query, "-n", str(limit)], + [bird, "search", query, "-n", str(limit)], capture_output=True, text=True, timeout=30, + env=_bird_env(config), ) if result.returncode != 0: return [] - return self._parse_birdx_output(result.stdout) + return self._parse_bird_output(result.stdout) except (subprocess.TimeoutExpired, FileNotFoundError): return [] - def _parse_birdx_output(self, text: str) -> List[SearchResult]: - """Parse birdx text output into SearchResults.""" + def _parse_bird_output(self, text: str) -> List[SearchResult]: + """Parse bird text output into SearchResults.""" results = [] current = {} text_lines = [] diff --git a/agent_reach/cli.py b/agent_reach/cli.py index 2ef9bd9..def241b 100644 --- a/agent_reach/cli.py +++ b/agent_reach/cli.py @@ -305,23 +305,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") - # ── birdx (for Twitter search) ── - if shutil.which("birdx"): - print(" ✅ birdx already installed") + # ── bird CLI (for Twitter search) ── + if shutil.which("bird") or shutil.which("birdx"): + print(" ✅ bird CLI already installed") else: - if shutil.which("pip3") or shutil.which("pip"): - pip_cmd = "pip3" if shutil.which("pip3") else "pip" + if shutil.which("npm"): try: subprocess.run( - [pip_cmd, "install", "-q", "birdx"], + ["npm", "install", "-g", "@steipete/bird"], capture_output=True, text=True, timeout=120, ) - if shutil.which("birdx"): - print(" ✅ birdx installed (Twitter search + timeline)") + if shutil.which("bird"): + print(" ✅ bird CLI installed (Twitter search + timeline)") else: - print(" ⬜ birdx install failed (optional — Twitter reading still works via Jina)") + print(" ⬜ bird CLI install failed (optional — Twitter reading still works via Jina)") except Exception: - print(" ⬜ birdx install failed (optional — Twitter reading still works via Jina)") + print(" ⬜ bird CLI install failed (optional — Twitter reading still works via Jina)") + else: + print(" ⬜ bird CLI requires Node.js (optional — Twitter reading still works via Jina)") def _install_mcporter(): @@ -435,6 +436,7 @@ def _detect_environment(): def _cmd_configure(args): """Set a config value and test it, or auto-extract from browser.""" + import shutil from agent_reach.config import Config config = Config() @@ -525,17 +527,23 @@ def _cmd_configure(args): print("Testing Twitter access...", end=" ") try: import subprocess - result = subprocess.run( - ["birdx", "search", "test", "-n", "1", - "--auth-token", auth_token, "--ct0", ct0], - capture_output=True, text=True, timeout=15, - ) - if result.returncode == 0 and result.stdout.strip(): - print("✅ Twitter Advanced works!") + bird = shutil.which("bird") or shutil.which("birdx") + if not bird: + print("⚠️ bird CLI not installed. Run: npm install -g @steipete/bird") else: - print(f"⚠️ Test returned no results (cookies might be wrong)") - except FileNotFoundError: - print("⚠️ birdx not installed. Run: pip install birdx") + import os + env = os.environ.copy() + env["AUTH_TOKEN"] = auth_token + env["CT0"] = ct0 + result = subprocess.run( + [bird, "search", "test", "-n", "1"], + capture_output=True, text=True, timeout=15, + env=env, + ) + if result.returncode == 0 and result.stdout.strip(): + print("✅ Twitter Advanced works!") + else: + print(f"⚠️ Test returned no results (cookies might be wrong)") except Exception as e: print(f"❌ Failed: {e}") else: diff --git a/agent_reach/config.py b/agent_reach/config.py index 918b106..008214e 100644 --- a/agent_reach/config.py +++ b/agent_reach/config.py @@ -22,7 +22,7 @@ class Config: FEATURE_REQUIREMENTS = { "exa_search": ["exa_api_key"], "reddit_proxy": ["reddit_proxy"], - "twitter_birdx": ["twitter_auth_token", "twitter_ct0"], + "twitter_bird": ["twitter_auth_token", "twitter_ct0"], "groq_whisper": ["groq_api_key"], "github_token": ["github_token"], } diff --git a/agent_reach/core.py b/agent_reach/core.py index 4a42986..ac0009f 100644 --- a/agent_reach/core.py +++ b/agent_reach/core.py @@ -78,7 +78,7 @@ class AgentReach: return [r.to_dict() for r in results] async def search_twitter(self, query: str, limit: int = 10) -> List[Dict[str, Any]]: - """Search Twitter. Uses birdx if available, else Exa.""" + """Search Twitter. Uses bird CLI if available, else Exa.""" ch = get_channel("twitter") results = await ch.search(query, config=self.config, limit=limit) return [r.to_dict() for r in results] diff --git a/agent_reach/guides/setup-twitter.md b/agent_reach/guides/setup-twitter.md index c71bfe3..f9da9de 100644 --- a/agent_reach/guides/setup-twitter.md +++ b/agent_reach/guides/setup-twitter.md @@ -1,44 +1,36 @@ -# Twitter 高级功能配置指南(birdx) +# Twitter 高级功能配置指南(bird CLI) ## 功能说明 基础 Twitter 功能(搜索+读单条推文)无需配置,开箱即用。 -高级功能需要 birdx: +高级功能需要 bird CLI: - 查看用户时间线 - 深度搜索(更精确、更多结果) - 读取完整线程(thread) - 查看关注列表推文 -birdx 是免费开源工具,但需要你的 Twitter 账号 cookie。 +bird 是免费开源工具(npm 包 @steipete/bird),但需要你的 Twitter 账号 cookie。 ## Agent 可自动完成的步骤 -1. 检查 birdx 是否安装: +1. 检查 bird 是否安装: ```bash -which birdx && echo "installed" || echo "not installed" +which bird && echo "installed" || echo "not installed" ``` -2. 安装 birdx: +2. 安装 bird: ```bash -pip install birdx +npm install -g @steipete/bird ``` -3. 检查是否已配置 cookie: +3. 测试(需要先配置 cookie): ```bash -birdx whoami 2>&1 +AUTH_TOKEN="xxx" CT0="yyy" bird search "test" -n 1 ``` -4. 如果用户提供了 cookie,配置 birdx: +4. 用 agent-reach 配置 cookie(推荐): ```bash -# birdx 的 cookie 配置文件位置 -# 通常在 ~/.birdx/cookies.json 或通过环境变量 -export TWITTER_AUTH_TOKEN="用户提供的auth_token" -export TWITTER_CT0="用户提供的ct0" -``` - -5. 测试: -```bash -birdx search "test" -n 1 +agent-reach configure twitter-cookies "auth_token=xxx; ct0=yyy" ``` ## 需要用户手动做的步骤 @@ -47,7 +39,13 @@ birdx search "test" -n 1 > Twitter 高级功能需要你的 Twitter 账号 cookie(完全免费)。 > -> 步骤: +> **最简单的方式:** +> 1. 安装 Chrome 插件 [Cookie-Editor](https://chromewebstore.google.com/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) +> 2. 打开 https://x.com 并确保已登录 +> 3. 点击 Cookie-Editor 插件图标 → Export → Header String +> 4. 把导出的内容发给我 +> +> **手动方式:** > 1. 用 Chrome 打开 https://x.com 并确保你已登录 > 2. 按 **F12** 打开开发者工具(Mac 按 Cmd+Option+I) > 3. 点击顶部的 **Application**(应用)标签 @@ -62,8 +60,8 @@ birdx search "test" -n 1 ## Agent 收到 cookie 后的操作 -1. 安装 birdx(如果没装):`pip install birdx` -2. 配置 cookie:写入 birdx 配置 -3. 测试:`birdx whoami` 确认身份 -4. 反馈:"✅ Twitter 高级功能已开启!你的账号是 @xxx。现在我可以查看时间线、读取线程了。" +1. 安装 bird(如果没装):`npm install -g @steipete/bird` +2. 配置 cookie:`agent-reach configure twitter-cookies "粘贴的内容"` +3. 测试:运行 `agent-reach doctor` 确认 Twitter 状态 +4. 反馈:"✅ Twitter 高级功能已开启!现在可以搜索推文、查看时间线了。" 5. 如果失败:"❌ Cookie 无效或已过期,请重新导出。" diff --git a/docs/README_en.md b/docs/README_en.md index 1a708e0..5a73af4 100644 --- a/docs/README_en.md +++ b/docs/README_en.md @@ -45,7 +45,7 @@ Copy that to your Agent. A few minutes later, it can read tweets, search Reddit, |---|---| | 💰 **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, birdx, 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 | @@ -56,7 +56,7 @@ Copy that to your Agent. A few minutes later, it can read tweets, search Reddit, | 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 ([birdx](https://github.com/runesleo/birdx)) | +| 🐦 **Twitter/X** | Read · Search | Zero config / Cookie | Single tweets readable out of the box. Cookie unlocks search, timeline, posting ([bird](https://github.com/steipete/bird)) | | 📕 **XiaoHongShu** | Read · Search · **Post · Comment · Like** | mcporter | Via [xiaohongshu-mcp](https://github.com/user/xiaohongshu-mcp) internal API, install and go | | 🔍 **Web Search** | Search | Auto-configured | Auto-configured during install, free, no API key ([Exa](https://exa.ai) via [mcporter](https://github.com/nicepkg/mcporter)) | | 📦 **GitHub** | Read · Search | Zero config | [gh CLI](https://cli.github.com) powered. Public repos work immediately. `gh auth login` unlocks Fork, Issue, PR | @@ -164,7 +164,7 @@ Each platform is a single Python file implementing a unified interface. **Backen ``` channels/ ├── web.py → Jina Reader ← swap to Firecrawl, Crawl4AI… -├── twitter.py → birdx ← swap to Nitter, official API… +├── twitter.py → bird ← 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… @@ -180,7 +180,7 @@ channels/ | Scenario | Tool | Why | |----------|------|-----| | Read web pages | [Jina Reader](https://github.com/jina-ai/reader) | 9.8K stars, free, no API key needed | -| Read tweets | [birdx](https://github.com/runesleo/birdx) | Cookie auth, free. Official API is pay-per-use ($0.005/post read) | +| Read tweets | [bird](https://github.com/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 | @@ -203,7 +203,7 @@ This project was entirely vibe-coded 🎸 There might be rough edges here and th ## Credits -[Jina Reader](https://github.com/jina-ai/reader) · [yt-dlp](https://github.com/yt-dlp/yt-dlp) · [birdx](https://github.com/runesleo/birdx) · [Exa](https://exa.ai) · [feedparser](https://github.com/kurtmckee/feedparser) +[Jina Reader](https://github.com/jina-ai/reader) · [yt-dlp](https://github.com/yt-dlp/yt-dlp) · [bird](https://github.com/steipete/bird) · [Exa](https://exa.ai) · [feedparser](https://github.com/kurtmckee/feedparser) ## License diff --git a/docs/install.md b/docs/install.md index 4a6844f..787b8a8 100644 --- a/docs/install.md +++ b/docs/install.md @@ -23,7 +23,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, birdx), 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. **Read the output carefully**, then run: