#!/usr/bin/env python3 """Run QQnote scripts with QQsci config injected as environment variables.""" from __future__ import annotations import argparse import json import os import subprocess import sys from pathlib import Path SCRIPT_MAP = { "generate_zotero_ai_note": "generate_zotero_ai_note.py", "summarize_zotero_table": "summarize_zotero_table.py", "audit_zotero_ai_notes": "audit_zotero_ai_notes.py", } def skill_root() -> Path: return Path(__file__).resolve().parents[1] def load_json(path: Path) -> dict: if not path.exists(): return {} return json.loads(path.read_text(encoding="utf-8")) def merged_config(root: Path) -> dict: template = load_json(root / "config" / "config.template.json") local = load_json(root / "config" / "config.local.json") def merge(base: dict, override: dict) -> dict: out = dict(base) for key, value in override.items(): if isinstance(value, dict) and isinstance(out.get(key), dict): out[key] = merge(out[key], value) else: out[key] = value return out return merge(template, local) def set_if_present(env: dict[str, str], key: str, value: object) -> None: if key in env and env[key]: return if value is None: return text = str(value) if text: env[key] = text def build_env(config: dict) -> dict[str, str]: env = os.environ.copy() deepseek = config.get("deepseek") or {} zotero = config.get("zotero") or {} set_if_present(env, deepseek.get("env_api_key", "AWESOMEGPT_API_KEY"), deepseek.get("api_key")) set_if_present(env, deepseek.get("env_base_url", "AWESOMEGPT_BASE_URL"), deepseek.get("base_url")) set_if_present(env, deepseek.get("env_model", "AWESOMEGPT_MODEL"), deepseek.get("model")) set_if_present(env, zotero.get("env_web_api_key", "ZOTERO_API_KEY"), zotero.get("web_api_key")) set_if_present(env, zotero.get("env_user_id", "ZOTERO_USER_ID"), zotero.get("user_id")) return env def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--script", required=True, choices=sorted(SCRIPT_MAP)) parser.add_argument("--qqnote-path", default="") parser.add_argument("qqnote_args", nargs=argparse.REMAINDER) args = parser.parse_args() root = skill_root() config = merged_config(root) qqnote = Path(args.qqnote_path or (config.get("qqnote") or {}).get("skill_path") or "") if not qqnote: qqnote = Path.home() / ".codex" / "skills" / "QQnote-skill" script = qqnote / "scripts" / SCRIPT_MAP[args.script] if not script.exists(): print(f"QQnote script not found: {script}", file=sys.stderr) return 2 extra_args = list(args.qqnote_args) if extra_args and extra_args[0] == "--": extra_args = extra_args[1:] command = [sys.executable, str(script), *extra_args] return subprocess.call(command, env=build_env(config)) if __name__ == "__main__": raise SystemExit(main())