Promote QQsci config for QQnote calls
This commit is contained in:
parent
64c854937a
commit
eaec7d0dd9
6
SKILL.md
6
SKILL.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
Loading…
Reference in New Issue