Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -119,7 +119,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg
```

Python: `uv run pytest tests/ -v` (1095) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (1096) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (265: 45 daemon_client + 20 conn-manager + 22 app-commands + 132 renderer smoke + 15 i18n + 8 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group + 3 preload-api) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`; renderer React suite: `cd emrg/gui/renderer && npm run typecheck && npm test` (78 vitest: 5 snapshot-store + 9 utils + 3 ErrorBoundary + 2 App smoke + 11 commands + 4 copywriting + 11 i18n + 11 markdown + 15 transcript + 7 TranscriptView) + `npm run build` → `renderer/dist/`
CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
29 changes: 27 additions & 2 deletions scripts/sync-master-from-api.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,11 +36,36 @@

API = "https://api.github.com"

_TOKEN: str | None = None # resolved once by _auth_token(), held in memory only


def _auth_token() -> str | None:
"""Resolve a GitHub token once: env var, else `gh auth token` (read into
memory only — never printed). Falls back to anonymous when unavailable.

Anonymous requests are limited to 60/hr, which a commit-chain walk can
exhaust mid-run (observed cycle 2026-08-26 01:46 on the push counterpart);
authenticating upfront keeps long walks under the limit.
"""
global _TOKEN
if _TOKEN is None:
t = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN")
if not t:
try:
out = subprocess.run(["gh", "auth", "token"], capture_output=True,
text=True, timeout=15, encoding="utf-8",
errors="replace")
t = out.stdout.strip() if out.returncode == 0 else None
except Exception:
t = None
_TOKEN = t or ""
return _TOKEN or None


def api_get(url: str) -> dict:
"""GET a GitHub API URL, using GH_TOKEN or gh CLI auth when available."""
"""GET a GitHub API URL, authenticated when a token is available."""
headers = {"User-Agent": "emrg-sync-master-from-api", "Accept": "application/vnd.github+json"}
token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN")
token = _auth_token()
if token:
headers["Authorization"] = "Bearer " + token
req = urllib.request.Request(url, headers=headers)
Expand Down
10 changes: 10 additions & 0 deletions tests/test_sync_master_from_api.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,6 +69,16 @@ def test_script_reconstructs_gpg_signed_commits():
assert "reconstruct_commit" in content # core logic named


def test_script_authenticates_upfront_via_gh_token():
"""Anonymous API requests are limited to 60/hr; a commit-chain walk can
exhaust them mid-run. The script must resolve a token once via env or
`gh auth token` (memory-only) and use it from the first request."""
content = SCRIPT.read_text(encoding="utf-8")
assert "auth token" in content # gh keyring token resolution
assert "_auth_token()" in content # helper named
assert "Authorization" in content # header applied when token present


def test_script_fails_loud_before_touching_refs():
content = SCRIPT.read_text(encoding="utf-8")
assert "aborting (no refs touched)" in content # mismatch → stop, refs safe
Expand Down
Loading