3 Commits

Author SHA1 Message Date
Pnant 84b474c79b chore: bump version to 1.4.1 (#341)
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
ci / wheel-gate (push) Has been cancelled
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:47:32 +08:00
Pnant b6241e8e67 ci: add wheel-build gate (duplicate-entry check + clean-venv smoke install) (#340)
Editable installs in CI never exercise wheel packaging, which let the
force-include duplication ship broken source installs while tests stayed
green (#308 #315 #328 #332 #334). This gate builds the real wheel, fails
on any duplicate archive entry, asserts SKILL.md/guides/scripts ship,
and smoke-installs into a clean venv.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:45:08 +08:00
nyxst4ck 607f0d2c56 fix(build): drop duplicate force-include that breaks source installs (#329)
Removes the [tool.hatch.build.targets.wheel.force-include] block that duplicated files already included via packages=["agent_reach"], which made modern hatchling fail source installs with 'ValueError: A second file is being added to the wheel archive at the same path'.

Verified before merge: wheel builds clean (48 entries, no duplicates), SKILL.md / guides / scripts all ship in site-packages, 85 tests pass on Python 3.11.

Fixes #334, fixes #332, fixes #328, fixes #315, fixes #308.
2026-06-10 11:41:46 +08:00
7 changed files with 61 additions and 72 deletions
+40
View File
@@ -28,3 +28,43 @@ 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.3.0
Repo: github.com/Panniantong/Agent-Reach | License: MIT | Version: 1.4.1
## 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.0"
__version__ = "1.4.1"
__author__ = "Neo Reid"
from agent_reach.core import AgentReach
+16 -35
View File
@@ -1143,19 +1143,19 @@ def _parse_twitter_cookie_input(value: str):
def _configure_xhs_cookies(value):
"""Import cookies for xhs-cli, with legacy xiaohongshu-mcp support.
"""Import cookies into xiaohongshu-mcp Docker container.
Accepts two formats:
1. Cookie-Editor JSON export (array of cookie objects)
2. Header String: "name1=value1; name2=value2; ..."
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.
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}.
"""
import json
import shutil
import subprocess
import time
value = value.strip()
if not value:
@@ -1165,7 +1165,6 @@ def _configure_xhs_cookies(value):
# Detect format and parse
cookies_json = None
cookies = []
# Try JSON format first (Cookie-Editor JSON export)
if value.startswith("["):
@@ -1175,7 +1174,6 @@ 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:
@@ -1190,6 +1188,7 @@ 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:
@@ -1223,37 +1222,17 @@ 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:
print(" Docker not found; skipping xiaohongshu-mcp import.")
# 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")
return
# Check if xiaohongshu-mcp container is running
@@ -1264,7 +1243,9 @@ def _configure_xhs_cookies(value):
)
container_name = result.stdout.strip()
if not container_name:
print(" xiaohongshu-mcp container is not running; skipping legacy Docker import.")
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")
return
except Exception as e:
print(f"[X] Could not check Docker: {e}")
+1 -6
View File
@@ -39,9 +39,7 @@ agent-reach doctor
> 3. 点击 Cookie-Editor 图标 → Export → Header String
> 4. 把导出的字符串发给 Agent,运行:`agent-reach configure xhs-cookies "导出的cookie字符串"`
>
> **注意**:不要依赖 QR 扫码登录,Cookie-Editor 导出方式最简单可靠。这个方式也适合 WSL、SSH、容器等无法直接读取桌面浏览器 Cookie 的环境。
`agent-reach configure xhs-cookies` 会把 Cookie 同步到 `~/.xiaohongshu-cli/cookies.json`,供 `xhs status/search/read` 直接使用;如果你还在用旧的 `xiaohongshu-mcp` Docker 方案,也会保留兼容导入文件。
> **注意**:不要依赖 QR 扫码登录,Cookie-Editor 导出方式最简单可靠。
## 使用示例
@@ -71,9 +69,6 @@ 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 方案,它也能正常工作:
+1 -7
View File
@@ -1,6 +1,6 @@
[project]
name = "agent-reach"
version = "1.4.0"
version = "1.4.1"
description = "Give your AI Agent eyes to see the entire internet. Search + Read 10+ platforms."
readme = "README.md"
license = {text = "MIT"}
@@ -64,12 +64,6 @@ 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
+1 -22
View File
@@ -1,9 +1,6 @@
# -*- coding: utf-8 -*-
"""Tests for Agent Reach CLI."""
import json
import os
import pytest
import requests
from unittest.mock import patch
@@ -45,24 +42,6 @@ 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):
@@ -110,7 +89,7 @@ class TestCheckUpdateRetry:
sequence = [
R(429, headers={"Retry-After": "3"}),
R(200, payload={"tag_name": "v1.4.0"}),
R(200, payload={"tag_name": "v1.4.1"}),
]
with patch("requests.get", side_effect=sequence):