4 Commits

Author SHA1 Message Date
Pnant 1ee30ecf28 fix: restore full cli file for xhs cookie import
ci / test (3.10) (push) Has been cancelled
ci / test (3.11) (push) Has been cancelled
ci / test (3.12) (push) Has been cancelled
ci / test (3.13) (push) Has been cancelled
2026-05-18 20:39:22 +08:00
Pnant 61d6536754 fix: support manual xhs cookies for xhs-cli (tests/test_cli.py) 2026-05-18 20:38:26 +08:00
Pnant 27b2cd2d02 fix: support manual xhs cookies for xhs-cli (agent_reach/guides/setup-xiaohongshu.md) 2026-05-18 20:38:24 +08:00
Pnant 7eae32d0b7 fix: support manual xhs cookies for xhs-cli (agent_reach/cli.py) 2026-05-18 20:38:21 +08:00
7 changed files with 72 additions and 61 deletions
-40
View File
@@ -28,43 +28,3 @@ jobs:
- name: Run tests
run: |
pytest -q
# Editable installs (-e) never exercise wheel packaging, so a broken wheel
# can pass tests and still fail every real `pip install` from source.
# This job builds the actual wheel and installs it into a clean venv.
wheel-gate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Build wheel
run: |
python -m pip install --upgrade pip build
python -m build
- name: Verify wheel has no duplicate entries and ships data files
run: |
python - <<'PY'
import glob, zipfile, collections
whl = glob.glob("dist/*.whl")[0]
names = zipfile.ZipFile(whl).namelist()
dupes = [n for n, c in collections.Counter(names).items() if c > 1]
assert not dupes, f"duplicate entries in wheel: {dupes}"
assert "agent_reach/skill/SKILL.md" in names, "SKILL.md missing from wheel"
for prefix in ("agent_reach/guides/", "agent_reach/scripts/", "agent_reach/skill/references/"):
assert any(n.startswith(prefix) for n in names), f"{prefix} missing from wheel"
print(f"wheel OK: {len(names)} entries, no duplicates, data files present")
PY
- name: Smoke-install wheel into clean venv
run: |
python -m venv /tmp/smoke
/tmp/smoke/bin/pip install --quiet dist/*.whl
/tmp/smoke/bin/agent-reach version
cd /tmp && /tmp/smoke/bin/python -c "import agent_reach; from importlib.resources import files; assert (files('agent_reach')/'skill'/'SKILL.md').is_file(); print('SKILL.md ships in site-packages OK')"
+1 -1
View File
@@ -3,7 +3,7 @@
## Project
Agent Reach — Python CLI + library that gives AI agents read/search access to 14+ internet platforms.
Positioning: installer + doctor + config tool. NOT a wrapper — after install, agents call upstream tools directly.
Repo: github.com/Panniantong/Agent-Reach | License: MIT | Version: 1.4.1
Repo: github.com/Panniantong/Agent-Reach | License: MIT | Version: 1.3.0
## Commands
- `pip install -e .` — Dev install
+1 -1
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Agent Reach — Give your AI Agent eyes to see the entire internet."""
__version__ = "1.4.1"
__version__ = "1.4.0"
__author__ = "Neo Reid"
from agent_reach.core import AgentReach
+35 -16
View File
@@ -1143,19 +1143,19 @@ def _parse_twitter_cookie_input(value: str):
def _configure_xhs_cookies(value):
"""Import cookies into xiaohongshu-mcp Docker container.
"""Import cookies for xhs-cli, with legacy xiaohongshu-mcp support.
Accepts two formats:
1. Cookie-Editor JSON export (array of cookie objects)
2. Header String: "name1=value1; name2=value2; ..."
The xiaohongshu-mcp container stores cookies at $COOKIES_PATH
(default: /app/data/cookies.json or cookies.json in workdir).
Format: JSON array of {name, value, domain, path, expires, httpOnly, secure, sameSite}.
xhs-cli stores cookies as a name/value dict at ~/.xiaohongshu-cli/cookies.json.
The legacy Docker MCP stores Cookie-Editor style arrays at $COOKIES_PATH.
"""
import json
import shutil
import subprocess
import time
value = value.strip()
if not value:
@@ -1165,6 +1165,7 @@ def _configure_xhs_cookies(value):
# Detect format and parse
cookies_json = None
cookies = []
# Try JSON format first (Cookie-Editor JSON export)
if value.startswith("["):
@@ -1174,6 +1175,7 @@ def _configure_xhs_cookies(value):
# Validate it looks like cookie objects
first = parsed[0]
if isinstance(first, dict) and "name" in first and "value" in first:
cookies = parsed
cookies_json = json.dumps(parsed)
print(f" Parsed {len(parsed)} cookies from JSON format")
else:
@@ -1188,7 +1190,6 @@ def _configure_xhs_cookies(value):
# Header String format: "key1=val1; key2=val2; ..."
if cookies_json is None and "=" in value:
cookies = []
for part in value.split(";"):
part = part.strip()
if "=" not in part:
@@ -1222,17 +1223,37 @@ def _configure_xhs_cookies(value):
print(' 2. Header String: "key1=val1; key2=val2; ..."')
return
# Primary path: configure xhs-cli directly.
xhs_cookie_map = {
str(c.get("name", "")).strip(): str(c.get("value", ""))
for c in cookies
if isinstance(c, dict) and str(c.get("name", "")).strip()
}
if xhs_cookie_map.get("a1"):
xhs_config_dir = os.path.expanduser("~/.xiaohongshu-cli")
os.makedirs(xhs_config_dir, exist_ok=True)
xhs_cookie_path = os.path.join(xhs_config_dir, "cookies.json")
with open(xhs_cookie_path, "w", encoding="utf-8") as f:
json.dump({**xhs_cookie_map, "saved_at": time.time()}, f, indent=2)
os.chmod(xhs_cookie_path, 0o600)
print(f"✅ xhs-cli cookies saved to {xhs_cookie_path}")
print(" Run `xhs status` or `agent-reach doctor` to verify.")
else:
print("[!] Cookie input does not include the required `a1` cookie.")
print(" xhs-cli will not treat this as a logged-in session. Export all xiaohongshu.com cookies from Cookie-Editor.")
# Keep a legacy Cookie-Editor array for users still running xiaohongshu-mcp.
legacy_cookie_path = os.path.expanduser("~/.agent-reach/xhs-cookies.json")
os.makedirs(os.path.dirname(legacy_cookie_path), exist_ok=True)
with open(legacy_cookie_path, "w") as f:
f.write(cookies_json)
os.chmod(legacy_cookie_path, 0o600)
print(f" Legacy MCP cookie array saved to {legacy_cookie_path}")
# Find the container
docker = shutil.which("docker")
if not docker:
# No Docker - write to a local file for manual import
cookie_path = os.path.expanduser("~/.agent-reach/xhs-cookies.json")
with open(cookie_path, "w") as f:
f.write(cookies_json)
os.chmod(cookie_path, 0o600)
print(f" Cookies saved to {cookie_path}")
print(" Docker not found. Copy manually:")
print(f" docker cp {cookie_path} xiaohongshu-mcp:/app/data/cookies.json")
print(" Docker not found; skipping xiaohongshu-mcp import.")
return
# Check if xiaohongshu-mcp container is running
@@ -1243,9 +1264,7 @@ def _configure_xhs_cookies(value):
)
container_name = result.stdout.strip()
if not container_name:
print("[X] xiaohongshu-mcp container is not running.")
print(" Start it first:")
print(" docker run -d --name xiaohongshu-mcp -p 18060:18060 xpzouying/xiaohongshu-mcp")
print(" xiaohongshu-mcp container is not running; skipping legacy Docker import.")
return
except Exception as e:
print(f"[X] Could not check Docker: {e}")
+6 -1
View File
@@ -39,7 +39,9 @@ agent-reach doctor
> 3. 点击 Cookie-Editor 图标 → Export → Header String
> 4. 把导出的字符串发给 Agent,运行:`agent-reach configure xhs-cookies "导出的cookie字符串"`
>
> **注意**:不要依赖 QR 扫码登录,Cookie-Editor 导出方式最简单可靠。
> **注意**:不要依赖 QR 扫码登录,Cookie-Editor 导出方式最简单可靠。这个方式也适合 WSL、SSH、容器等无法直接读取桌面浏览器 Cookie 的环境。
`agent-reach configure xhs-cookies` 会把 Cookie 同步到 `~/.xiaohongshu-cli/cookies.json`,供 `xhs status/search/read` 直接使用;如果你还在用旧的 `xiaohongshu-mcp` Docker 方案,也会保留兼容导入文件。
## 使用示例
@@ -69,6 +71,9 @@ A: 推荐使用住宅代理:`export HTTP_PROXY="http://user:pass@ip:port"`。
**Q: xhs-cli 不支持我的系统?**
A: 确保 Python 3.10+ 和 pipx 已安装。运行 `pipx install xiaohongshu-cli` 即可。
**Q: WSL 里 `xhs login` 读不到 Windows 浏览器怎么办?**
A: 这是常见限制。WSL 里没有直接可读的 Linux 浏览器 Cookie 时,`xhs login` 自动提取会失败。推荐在 Windows 浏览器用 Cookie-Editor 导出 `xiaohongshu.com` 的 Header String,然后在 WSL 里运行 `agent-reach configure xhs-cookies "..."`,再用 `xhs status` 验证。
## 备选方案:Docker MCP
如果你已经在使用 [xiaohongshu-mcp](https://github.com/xpzouying/xiaohongshu-mcp) Docker 方案,它也能正常工作:
+7 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "agent-reach"
version = "1.4.1"
version = "1.4.0"
description = "Give your AI Agent eyes to see the entire internet. Search + Read 10+ platforms."
readme = "README.md"
license = {text = "MIT"}
@@ -64,6 +64,12 @@ build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["agent_reach"]
[tool.hatch.build.targets.wheel.force-include]
"agent_reach/guides" = "agent_reach/guides"
# Keep the whole skill directory so SKILL.md, SKILL_en.md, and references/ ship together.
"agent_reach/skill" = "agent_reach/skill"
"agent_reach/scripts" = "agent_reach/scripts"
[tool.ruff]
target-version = "py310"
line-length = 100
+22 -1
View File
@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
"""Tests for Agent Reach CLI."""
import json
import os
import pytest
import requests
from unittest.mock import patch
@@ -42,6 +45,24 @@ class TestCLI:
assert auth_token == "token123"
assert ct0 == "ct0abc"
def test_configure_xhs_cookies_writes_xhs_cli_cookie_file(self, tmp_path, monkeypatch, capsys):
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setattr("shutil.which", lambda _name: None)
cli._configure_xhs_cookies("a1=token123; web_session=session456; other=value")
cookie_path = tmp_path / ".xiaohongshu-cli" / "cookies.json"
data = json.loads(cookie_path.read_text())
assert data["a1"] == "token123"
assert data["web_session"] == "session456"
assert "saved_at" in data
assert oct(os.stat(cookie_path).st_mode & 0o777) == "0o600"
legacy_path = tmp_path / ".agent-reach" / "xhs-cookies.json"
assert legacy_path.exists()
captured = capsys.readouterr()
assert "xhs-cli cookies saved" in captured.out
class TestCheckUpdateRetry:
def test_retry_timeout_classification(self):
@@ -89,7 +110,7 @@ class TestCheckUpdateRetry:
sequence = [
R(429, headers={"Retry-After": "3"}),
R(200, payload={"tag_name": "v1.4.1"}),
R(200, payload={"tag_name": "v1.4.0"}),
]
with patch("requests.get", side_effect=sequence):