fix: doctor status must be honest — show real capability per channel
Before: everything under tier 0 showed ✅ with vague descriptions After: each channel shows its actual capability and limitations Changes: - GitHub: 'Public repos only. Set github_token for private repos' - Twitter: shows 'Full access' vs 'Read-only' depending on birdx - Bilibili: ⚠️ warning on servers about potential IP blocks - XiaoHongShu: friendly message when no cookie (was showing Jina 451) - Doctor format: tier 0 items now show detailed status messages, not just ✅ - README: platform table updated to match reality (removed exaggerated claims) - README: doctor example updated to show new honest format
This commit is contained in:
@@ -13,10 +13,10 @@ Agent Eyes 把最好的开源工具粘在一起,一次安装全部搞定。
|
|||||||
| 平台 | 能力 | 配置难度 | 说明 |
|
| 平台 | 能力 | 配置难度 | 说明 |
|
||||||
|------|------|:--------:|------|
|
|------|------|:--------:|------|
|
||||||
| 🌐 **网页** | 阅读 | 零配置 | 任意 URL → 干净 Markdown([Jina Reader](https://github.com/jina-ai/reader) ⭐9.8K 驱动) |
|
| 🌐 **网页** | 阅读 | 零配置 | 任意 URL → 干净 Markdown([Jina Reader](https://github.com/jina-ai/reader) ⭐9.8K 驱动) |
|
||||||
| 🐦 **Twitter/X** | 阅读 · 搜索 · **发推 · 回复** | Cookie | 不花 $100/月 Twitter API,浏览器 Cookie 就能玩转([birdx](https://github.com/runesleo/birdx) 驱动) |
|
| 🐦 **Twitter/X** | 阅读 · 搜索 | 零配置 / Cookie | 单条推文零配置可读(Jina Reader)。装 [birdx](https://github.com/runesleo/birdx) + Cookie 解锁搜索、时间线、发推 |
|
||||||
| 📕 **小红书** | 阅读 · 搜索 · **发帖 · 评论 · 点赞 · 收藏** | Cookie | 完整操作能力:发图文/视频笔记、回复评论、查看用户主页 |
|
| 📕 **小红书** | 阅读 · 搜索 · **发帖 · 评论 · 点赞 · 收藏** | Cookie | 完整操作能力:发图文/视频笔记、回复评论、查看用户主页 |
|
||||||
| 🔍 **全网搜索** | 搜索 | 免费 Key | AI 语义搜索,一个 Key 搜全网 + Reddit + Twitter([Exa](https://exa.ai) 驱动) |
|
| 🔍 **全网搜索** | 搜索 | 免费 Key | AI 语义搜索,一个 Key 搜全网 + Reddit + Twitter([Exa](https://exa.ai) 驱动) |
|
||||||
| 📦 **GitHub** | 阅读 · 搜索 · **创建 Issue/PR · 评论 · Review · Fork · Star** | 零配置 | 完整 GitHub 操作([gh CLI](https://cli.github.com/) 驱动) |
|
| 📦 **GitHub** | 阅读 · 搜索 | 零配置 | 公开仓库代码、README、搜索([GitHub API](https://docs.github.com/en/rest) 驱动)。设 token 可访问私有仓库 |
|
||||||
| 📺 **YouTube** | 阅读 | 零配置 | 1800+ 视频网站字幕提取([yt-dlp](https://github.com/yt-dlp/yt-dlp) ⭐148K 驱动) |
|
| 📺 **YouTube** | 阅读 | 零配置 | 1800+ 视频网站字幕提取([yt-dlp](https://github.com/yt-dlp/yt-dlp) ⭐148K 驱动) |
|
||||||
| 📺 **B站** | 阅读 | 零配置 / 代理 | 视频信息 + 字幕。本地直接用,服务器需代理 |
|
| 📺 **B站** | 阅读 | 零配置 / 代理 | 视频信息 + 字幕。本地直接用,服务器需代理 |
|
||||||
| 📡 **RSS** | 阅读 | 零配置 | 任意 RSS/Atom 源([feedparser](https://github.com/kurtmckee/feedparser) ⭐2.3K 驱动) |
|
| 📡 **RSS** | 阅读 | 零配置 | 任意 RSS/Atom 源([feedparser](https://github.com/kurtmckee/feedparser) ⭐2.3K 驱动) |
|
||||||
@@ -109,11 +109,24 @@ agent-eyes configure proxy http://用户名:密码@IP:端口
|
|||||||
$ agent-eyes doctor
|
$ agent-eyes doctor
|
||||||
|
|
||||||
👁️ Agent Eyes Status
|
👁️ Agent Eyes Status
|
||||||
✅ Web [Jina Reader] ✅ GitHub [API] ✅ RSS [feedparser]
|
========================================
|
||||||
✅ YouTube [yt-dlp] ✅ Bilibili [API] ✅ Twitter [birdx]
|
|
||||||
⬜ Search [need Exa key] ⬜ XiaoHongShu [cookies] ⬜ Reddit [proxy]
|
|
||||||
|
|
||||||
6/9 active
|
✅ Ready (no setup needed):
|
||||||
|
✅ GitHub repos and code — Public repos only
|
||||||
|
✅ Twitter/X posts — Full access (search + timeline + threads)
|
||||||
|
✅ YouTube video transcripts — yt-dlp
|
||||||
|
⚠️ Bilibili video info — May be blocked on servers
|
||||||
|
✅ RSS and Atom feeds — feedparser
|
||||||
|
✅ Web pages (any URL) — Jina Reader API
|
||||||
|
|
||||||
|
🔍 Search (need free Exa API key):
|
||||||
|
⬜ Semantic web search
|
||||||
|
|
||||||
|
🔧 Optional (advanced setup):
|
||||||
|
⬜ Reddit posts and comments — Need proxy
|
||||||
|
⬜ XiaoHongShu (小红书) — Need cookies
|
||||||
|
|
||||||
|
Status: 6/9 channels active
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -20,6 +20,23 @@ class BilibiliChannel(Channel):
|
|||||||
domain = urlparse(url).netloc.lower()
|
domain = urlparse(url).netloc.lower()
|
||||||
return "bilibili.com" in domain or "b23.tv" in domain
|
return "bilibili.com" in domain or "b23.tv" in domain
|
||||||
|
|
||||||
|
def check(self, config=None):
|
||||||
|
proxy = config.get("bilibili_proxy") if config else None
|
||||||
|
if proxy:
|
||||||
|
return "ok", "Via proxy"
|
||||||
|
# Detect if we're on a server (same logic as cli._detect_environment)
|
||||||
|
import os
|
||||||
|
indicators = [
|
||||||
|
os.path.exists("/var/run/docker.sock"),
|
||||||
|
os.path.exists("/etc/cloud"),
|
||||||
|
"SSH_CONNECTION" in os.environ,
|
||||||
|
"container" in os.environ.get("container", ""),
|
||||||
|
]
|
||||||
|
is_server = any(indicators)
|
||||||
|
if is_server:
|
||||||
|
return "warn", "May be blocked on servers. Fix: agent-eyes configure proxy URL"
|
||||||
|
return "ok", "Local access"
|
||||||
|
|
||||||
async def read(self, url: str, config=None) -> ReadResult:
|
async def read(self, url: str, config=None) -> ReadResult:
|
||||||
# Proxy support (Bilibili blocks server IPs)
|
# Proxy support (Bilibili blocks server IPs)
|
||||||
proxy = config.get("bilibili_proxy") if config else None
|
proxy = config.get("bilibili_proxy") if config else None
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from typing import List
|
|||||||
|
|
||||||
class GitHubChannel(Channel):
|
class GitHubChannel(Channel):
|
||||||
name = "github"
|
name = "github"
|
||||||
description = "GitHub repos, issues, PRs, code"
|
description = "GitHub repos and code"
|
||||||
backends = ["GitHub API"]
|
backends = ["GitHub API"]
|
||||||
tier = 0
|
tier = 0
|
||||||
|
|
||||||
@@ -26,6 +26,12 @@ class GitHubChannel(Channel):
|
|||||||
h["Authorization"] = f"Bearer {token}"
|
h["Authorization"] = f"Bearer {token}"
|
||||||
return h
|
return h
|
||||||
|
|
||||||
|
def check(self, config=None):
|
||||||
|
token = config.get("github_token") if config else None
|
||||||
|
if token:
|
||||||
|
return "ok", "Full access (authenticated)"
|
||||||
|
return "ok", "Public repos only. Set github_token for private repos + higher rate limits"
|
||||||
|
|
||||||
def can_handle(self, url: str) -> bool:
|
def can_handle(self, url: str) -> bool:
|
||||||
domain = urlparse(url).netloc.lower()
|
domain = urlparse(url).netloc.lower()
|
||||||
return "github.com" in domain
|
return "github.com" in domain
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import requests
|
|||||||
|
|
||||||
class TwitterChannel(Channel):
|
class TwitterChannel(Channel):
|
||||||
name = "twitter"
|
name = "twitter"
|
||||||
description = "Twitter/X posts, search, timelines"
|
description = "Twitter/X posts"
|
||||||
backends = ["birdx", "Jina Reader"]
|
backends = ["birdx", "Jina Reader"]
|
||||||
tier = 0 # Single tweet reading is zero-config
|
tier = 0 # Single tweet reading is zero-config
|
||||||
|
|
||||||
@@ -27,7 +27,8 @@ class TwitterChannel(Channel):
|
|||||||
def check(self, config=None):
|
def check(self, config=None):
|
||||||
# Basic reading always works (Jina fallback)
|
# Basic reading always works (Jina fallback)
|
||||||
if shutil.which("birdx"):
|
if shutil.which("birdx"):
|
||||||
return "ok", "birdx (full: search + timeline + threads)"
|
return "ok", "Full access (search + timeline + threads)"
|
||||||
|
return "ok", "Read-only (single tweets via Jina). Install birdx for search + timelines"
|
||||||
return "ok", "Jina Reader (single tweets only)"
|
return "ok", "Jina Reader (single tweets only)"
|
||||||
|
|
||||||
async def read(self, url: str, config=None) -> ReadResult:
|
async def read(self, url: str, config=None) -> ReadResult:
|
||||||
|
|||||||
@@ -37,11 +37,13 @@ def format_report(results: Dict[str, dict]) -> str:
|
|||||||
lines.append("")
|
lines.append("")
|
||||||
lines.append("✅ Ready (no setup needed):")
|
lines.append("✅ Ready (no setup needed):")
|
||||||
for key, r in results.items():
|
for key, r in results.items():
|
||||||
if r["tier"] == 0 and r["status"] == "ok":
|
if r["tier"] == 0:
|
||||||
backends = ", ".join(r["backends"]) if r["backends"] else "built-in"
|
if r["status"] == "ok":
|
||||||
lines.append(f" ✅ {r['name']} [{backends}]")
|
lines.append(f" ✅ {r['name']} — {r['message']}")
|
||||||
elif r["tier"] == 0 and r["status"] in ("warn", "off"):
|
elif r["status"] == "warn":
|
||||||
lines.append(f" ⚠️ {r['name']} — {r['message']}")
|
lines.append(f" ⚠️ {r['name']} — {r['message']}")
|
||||||
|
elif r["status"] in ("off", "error"):
|
||||||
|
lines.append(f" ❌ {r['name']} — {r['message']}")
|
||||||
|
|
||||||
# Tier 1 — needs free key
|
# Tier 1 — needs free key
|
||||||
tier1 = {k: r for k, r in results.items() if r["tier"] == 1}
|
tier1 = {k: r for k, r in results.items() if r["tier"] == 1}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class TestDoctor:
|
|||||||
results = check_all(tmp_config)
|
results = check_all(tmp_config)
|
||||||
assert results["web"]["status"] == "ok"
|
assert results["web"]["status"] == "ok"
|
||||||
assert results["github"]["status"] == "ok"
|
assert results["github"]["status"] == "ok"
|
||||||
assert results["bilibili"]["status"] == "ok"
|
assert results["bilibili"]["status"] in ("ok", "warn") # warn on servers
|
||||||
assert results["rss"]["status"] == "ok"
|
assert results["rss"]["status"] == "ok"
|
||||||
|
|
||||||
def test_exa_off_without_key(self, tmp_config):
|
def test_exa_off_without_key(self, tmp_config):
|
||||||
|
|||||||
Reference in New Issue
Block a user