xiaowei-system/scripts/cnb_npc_monitor.py

105 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""CNB CodeBuddy NPC 通用监控脚本 — 自动盯所有 open Issue + PR。
设计:
- 自动列出仓库所有 open 的 Issue含 NPC 评论摘要)
- 列出所有 open PRNPC 推的 MR
- 输出稳定(无时间戳),任何新 Issue / 新评论 / 新 PR 都会让输出变化
- 配合 cronjob monitor_script 使用:有变化才触发 agent静默零消耗
"""
import json
import os
import urllib.error
import urllib.request
API = "https://api.cnb.cool"
REPO = os.environ.get("CNB_REPO", "muchen-org/download-webhook")
def load_token():
"""从 ~/.hermes/.env 读取 CNB_TOKEN"""
env_path = os.path.expanduser("~/.hermes/.env")
try:
with open(env_path) as f:
for line in f:
line = line.strip()
if line.startswith("CNB_TOKEN="):
return line.split("=", 1)[1].strip()
except FileNotFoundError:
pass
return os.environ.get("CNB_TOKEN", "")
def api_get(path):
req = urllib.request.Request(
f"{API}{path}",
headers={
"Authorization": f"Bearer {load_token()}",
"accept": "application/json",
},
method="GET",
)
try:
with urllib.request.urlopen(req, timeout=20) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
return {"error": e.code}
except Exception as e:
return {"error": str(e)}
def get_list(payload, key="items"):
"""兼容 list / {items: [...]} 两种返回"""
if isinstance(payload, list):
return payload
if isinstance(payload, dict) and key in payload:
return payload[key]
if isinstance(payload, dict) and "data" in payload:
return payload["data"]
return []
def main():
lines = []
# 1. 所有 open Issue
issues = api_get(f"/{REPO}/-/issues?state=open")
issue_list = get_list(issues)
open_issues = [i for i in issue_list if i.get("state") == "open"]
lines.append(f"Open issues: {len(open_issues)}")
for issue in sorted(open_issues, key=lambda x: int(x.get("number", 0))):
num = issue.get("number", "?")
comments = api_get(f"/{REPO}/-/issues/{num}/comments")
comment_list = get_list(comments)
# 只显示 NPC 评论(作者含 npc 或模型名)
npc_comments = [
c for c in comment_list
if "npc" in (c.get("author", {}).get("username", "")).lower()
or "npc" in (c.get("author", {}).get("nickname", "")).lower()
or (c.get("author", {}).get("username", "") or "").startswith("npc/")
or (c.get("author", {}).get("nickname", "") or "").startswith("npc/")
or c.get("author", {}).get("username", "") == "deepseek-v4-flash"
]
title = (issue.get("title") or "").replace("\n", " ")
lines.append(f" #{num} open: {title[:70]}")
for c in npc_comments:
body = (c.get("body") or "").replace("\n", " ")[:80]
lines.append(f" NPC: {body}")
if not npc_comments:
lines.append(f" (no NPC activity)")
# 2. 所有 open PR
pulls = api_get(f"/{REPO}/-/pulls?state=open")
pull_list = get_list(pulls)
open_pulls = [p for p in pull_list if p.get("state") == "open"]
lines.append(f"Open PRs: {len(open_pulls)}")
for p in sorted(open_pulls, key=lambda x: int(x.get("number", 0))):
author = p.get("author", {}).get("nickname", p.get("author", {}).get("username", "?"))
lines.append(f" PR#{p.get('number', '?')} [{author}]: {(p.get('title') or '')[:60]}")
print("\n".join(lines))
if __name__ == "__main__":
main()