Promote QQsci config for QQnote calls

This commit is contained in:
qyh15 2026-05-23 14:29:30 +08:00
parent 64c854937a
commit eaec7d0dd9
5 changed files with 220 additions and 8 deletions

View File

@ -47,6 +47,7 @@ positioning, introduction logic, comparison, novelty framing, and citations.
| [references/literature-routing.md](references/literature-routing.md) | User wants Zotero/Obsidian/DeepSeek note search, strong/weak related-paper screening, keyword extraction, or literature-grounded rewriting |
| [references/word-citation-comments.md](references/word-citation-comments.md) | User wants citation insertion guidance in Word, reference placement, DOI-only comments, or annotated `.docx` citation suggestions |
| [references/zotero-obsidian-subskill.md](references/zotero-obsidian-subskill.md) | User asks QQsci to obtain or refresh literature-note inputs through QQnote, including Zotero child-note summaries or paper comparison tables |
| [references/configuration.md](references/configuration.md) | You need DeepSeek, Zotero, Obsidian, QQnote inheritance, config precedence, secret handling, or wrapper-script usage |
| [references/journal-positioning.md](references/journal-positioning.md) | Target journal is missing, uncertain, or needs recommendation and writing-position adjustment |
## Intake
@ -187,6 +188,11 @@ summaries or paper comparison tables with QQnote, use
Important defaults:
- QQnote is only the note/table generation tool.
- QQsci owns the top-level DeepSeek, Zotero, Obsidian, and QQnote routing
configuration for manuscript workflows. Open `references/configuration.md`
before calling QQnote from QQsci.
- QQnote must remain independently usable through its own environment variables,
vault `.env`, or AwesomeGPT/Zotero preferences when run outside QQsci.
- QQsci owns keyword extraction, strong/weak related-paper screening, target
journal positioning, comparative review, submission-package audit, and
DOI-only Word citation comments.

View File

@ -2,18 +2,28 @@
"deepseek": {
"api_key": "",
"base_url": "https://api.deepseek.com",
"model": "deepseek-chat"
"model": "deepseek-chat",
"env_api_key": "AWESOMEGPT_API_KEY",
"env_base_url": "AWESOMEGPT_BASE_URL",
"env_model": "AWESOMEGPT_MODEL"
},
"zotero": {
"local_api": "http://127.0.0.1:23119/api/users/0",
"web_api_key": "",
"user_id": "",
"library_type": "user"
"library_type": "user",
"env_web_api_key": "ZOTERO_API_KEY",
"env_user_id": "ZOTERO_USER_ID"
},
"obsidian": {
"vault_path": "",
"vault_path": "C:\\Users\\qyh15\\Documents\\Obsidian Vault",
"zotero_integration_note_folder": ""
},
"qqnote": {
"skill_path": "C:\\Users\\qyh15\\.codex\\skills\\QQnote-skill",
"config_mode": "inherit_from_qqsci_when_called_by_qqsci",
"standalone_mode": "use_QQnote_environment_vault_env_or_AwesomeGPT_preferences"
},
"workflow": {
"manifest_path": "state/qqsci-literature-manifest.jsonl",
"skip_existing_notes": true,

View File

@ -0,0 +1,96 @@
# QQsci Configuration
Use this file when QQsci needs DeepSeek, Zotero, Obsidian, or QQnote access.
## Ownership model
QQsci is the top-level configuration owner for manuscript work. Store the
primary DeepSeek, Zotero, Obsidian, and QQnote routing settings in:
```text
config/config.local.json
```
Never commit `config/config.local.json`, print secrets, or ask the user to paste
API keys into chat.
QQnote remains independently usable. When QQnote is run by itself, it may use
its own environment variables, the vault `.env`, or AwesomeGPT/Zotero
preferences. When QQnote is called from QQsci, QQsci should inject its own
configuration into QQnote's expected environment variables.
## Config precedence
For QQsci-controlled manuscript workflows:
1. Existing process environment variables override everything.
2. QQsci `config/config.local.json`.
3. QQsci `config/config.template.json` only for defaults, never secrets.
4. QQnote standalone fallbacks only when QQsci config is absent or the user asks
to run QQnote independently.
For QQnote standalone workflows:
1. Existing process environment variables.
2. Vault `.env`.
3. AwesomeGPT/Zotero profile preferences.
4. Any QQnote-specific local config if QQnote later adds one.
## Environment mapping
QQsci config maps to QQnote's existing environment variable interface:
| QQsci config | QQnote env |
|---|---|
| `deepseek.api_key` | `AWESOMEGPT_API_KEY` |
| `deepseek.base_url` | `AWESOMEGPT_BASE_URL` |
| `deepseek.model` | `AWESOMEGPT_MODEL` |
| `zotero.web_api_key` | `ZOTERO_API_KEY` |
| `zotero.user_id` | `ZOTERO_USER_ID` |
The Zotero local API remains:
```text
http://127.0.0.1:23119/api/users/0
```
unless the user explicitly changes it.
## Calling QQnote from QQsci
Prefer the wrapper:
```powershell
py "$env:USERPROFILE\.codex\skills\qqsci\scripts\run_qqnote_with_qqsci_config.py" --script summarize_zotero_table -- --vault "C:\Users\qyh15\Documents\Obsidian Vault" --item-keys "SXAIQUJT X7GJZ627"
```
The wrapper loads QQsci local config, sets QQnote-compatible environment
variables for the child process, and does not print secrets.
Use `--script generate_zotero_ai_note` when QQnote should create Zotero child
notes.
Use `--script summarize_zotero_table` when QQsci needs a compact paper table.
Use `--script audit_zotero_ai_notes` only for QQnote note-state maintenance, not
for QQsci's manuscript reasoning.
## What belongs in QQsci
QQsci owns:
- manuscript keyword extraction
- strong/weak related-paper screening
- target-journal positioning
- comparative review against strong papers
- submission-package audit
- figure, format, spelling, and terminology checks
- DOI-only Word citation comments
QQnote owns:
- generating Zotero child-note summaries
- generating paper comparison tables from Zotero items
- optional QQnote note-state maintenance when explicitly requested
QQnote output is an input to QQsci, not the decision engine.

View File

@ -3,7 +3,8 @@
Use this file when QQsci needs literature-note inputs from QQnote.
QQnote is only the tool for summarizing papers into Zotero child notes or
compact Markdown comparison tables. QQsci owns all manuscript-facing reasoning:
compact Markdown comparison tables. QQsci owns the top-level DeepSeek/Zotero
configuration for manuscript workflows and all manuscript-facing reasoning:
keyword extraction, strong/weak related-paper screening, target-journal
positioning, comparative review, submission-package audit, figure/format
checks, terminology checks, and DOI-only Word citation comments.
@ -31,8 +32,8 @@ Do not default to creating standalone Obsidian markdown notes when the user says
Use the installed `QQnote-skill` scripts only for note/table generation:
```powershell
py "$env:USERPROFILE\.codex\skills\QQnote-skill\scripts\generate_zotero_ai_note.py" --vault "C:\Users\qyh15\Documents\Obsidian Vault" --item-key SXAIQUJT --skip-existing
py "$env:USERPROFILE\.codex\skills\QQnote-skill\scripts\summarize_zotero_table.py" --vault "C:\Users\qyh15\Documents\Obsidian Vault" --item-keys "SXAIQUJT X7GJZ627" --batch-size 3 --out "C:\Users\qyh15\Documents\Obsidian Vault\99 misc\literature-comparison-table.md"
py "$env:USERPROFILE\.codex\skills\qqsci\scripts\run_qqnote_with_qqsci_config.py" --script generate_zotero_ai_note -- --vault "C:\Users\qyh15\Documents\Obsidian Vault" --item-key SXAIQUJT --skip-existing
py "$env:USERPROFILE\.codex\skills\qqsci\scripts\run_qqnote_with_qqsci_config.py" --script summarize_zotero_table -- --vault "C:\Users\qyh15\Documents\Obsidian Vault" --item-keys "SXAIQUJT X7GJZ627" --batch-size 3 --out "C:\Users\qyh15\Documents\Obsidian Vault\99 misc\literature-comparison-table.md"
```
Use `generate_zotero_ai_note.py` when new or selected papers need Zotero child
@ -47,8 +48,11 @@ logic unless the user explicitly asks for QQnote maintenance.
## Safety and config
- Do not ask the user to paste API keys into chat.
- Follow QQnote's current config rules for DeepSeek/AwesomeGPT and Zotero
credentials when generating notes or tables.
- For QQsci manuscript workflows, load DeepSeek/AwesomeGPT and Zotero
credentials from QQsci `config/config.local.json` and inject them into QQnote
with `scripts/run_qqnote_with_qqsci_config.py`.
- QQnote must still be able to run standalone using its own environment
variables, vault `.env`, or AwesomeGPT/Zotero preferences.
- Keep private config ignored and local-only.
- Do not print secret-bearing config or preference lines.
- Treat Zotero local API at `127.0.0.1:23119` as read-oriented.

View File

@ -0,0 +1,96 @@
#!/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())