From 288ae72c74a25cb1101562a2fa5f1347bac7368c Mon Sep 17 00:00:00 2001 From: yunyaozhou Date: Tue, 14 Jul 2026 09:11:55 +0800 Subject: [PATCH] feat: add direct Sub2API imagegen skill --- SKILL.md | 62 +++++++++++++++++++++ agents/openai.yaml | 4 ++ scripts/shujk_imagegen.py | 114 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 SKILL.md create mode 100644 agents/openai.yaml create mode 100755 scripts/shujk_imagegen.py diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..f543b0a --- /dev/null +++ b/SKILL.md @@ -0,0 +1,62 @@ +--- +name: shujkimagegen-api +description: Generate or edit images through the repo-managed server6 Sub2API direct endpoint, using the OPENAI_API_KEY from ~/.codex/auth.json and a fixed OpenAI-compatible base URL at https://sub2apicn.shujk.top/v1. Use when image generation should bypass the Cloudflare-proxied sub2apius.shujk.top route, or when the user asks for Shujakuin direct image generation through sub2apicn. +--- + +# Shujk ImageGen API + +Use this helper when image generation or editing should reach server6 directly instead of passing through Cloudflare. + +## Boundary + +- Do not print, copy, commit, or summarize API keys. +- Read only `OPENAI_API_KEY` from `~/.codex/auth.json`; do not read plaintext `.env` files or unrelated secrets. +- Keep the durable source at `ai-infra/skills/sources/shujkimagegen-api/`. +- Do not place this skill under the infra repo's `.agents/skills/` or manage it through cc-switch WebDAV. +- Treat this as a thin wrapper around the system `imagegen` CLI; do not copy or patch the system script. +- The current management state is repo source only. Do not create a HOME runtime placement unless the user explicitly requests it. + +## Endpoint + +- Direct origin: `https://sub2apicn.shujk.top` +- Exported OpenAI-compatible base URL: `https://sub2apicn.shujk.top/v1` + +The `/v1` suffix is required because `/models` is an application page while `/v1/models` is the authenticated OpenAI-compatible API. + +## Workflow + +1. Use the system `imagegen` skill for prompt, composition, and asset decisions. +2. Run the repo source wrapper while the skill remains source-only: + + ```bash + python3 ai-infra/skills/sources/shujkimagegen-api/scripts/shujk_imagegen.py generate \ + --prompt "..." \ + --out output/imagegen/example.png + ``` + +3. For edits, pass through the system CLI arguments: + + ```bash + python3 ai-infra/skills/sources/shujkimagegen-api/scripts/shujk_imagegen.py edit \ + --image input.png \ + --prompt "..." \ + --out output/imagegen/edited.png + ``` + +4. Inspect non-secret configuration with: + + ```bash + python3 ai-infra/skills/sources/shujkimagegen-api/scripts/shujk_imagegen.py \ + --print-config-redacted + ``` + +5. Save project-bound assets inside the active workspace and report the output path and final prompt. + +After an explicit HOME runtime placement, use the equivalent path under `~/.agents/skills/shujkimagegen-api/`. + +## Failure Handling + +- If `OPENAI_API_KEY` is absent, report that auth is unavailable without revealing file contents. +- If the system imagegen CLI is missing, report its expected path. +- If the endpoint rejects a model or option, retry only with an explicitly compatible model or option. +- Do not silently fall back to `sub2apius.shujk.top` or another Cloudflare-proxied endpoint. diff --git a/agents/openai.yaml b/agents/openai.yaml new file mode 100644 index 0000000..25f8330 --- /dev/null +++ b/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Shujk ImageGen API" + short_description: "通过 sub2apicn 直连入口运行图片生成与编辑" + default_prompt: "使用 $shujkimagegen-api 通过 sub2apicn 直连 API 生成或编辑图片。" diff --git a/scripts/shujk_imagegen.py b/scripts/shujk_imagegen.py new file mode 100755 index 0000000..d80350a --- /dev/null +++ b/scripts/shujk_imagegen.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""Run the system imagegen CLI through the server6 direct Sub2API endpoint.""" + +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import subprocess +import sys +from typing import Any + + +DEFAULT_ORIGIN = "https://sub2apicn.shujk.top" +DEFAULT_BASE_URL = f"{DEFAULT_ORIGIN}/v1" +DEFAULT_SYSTEM_IMAGEGEN = ( + Path(os.environ.get("CODEX_HOME", Path.home() / ".codex")) + / "skills/.system/imagegen/scripts/image_gen.py" +) + + +def die(message: str) -> None: + print(f"Error: {message}", file=sys.stderr) + raise SystemExit(1) + + +def load_auth(path: Path) -> str: + if not path.exists(): + die(f"Codex auth file not found: {path}") + try: + data = json.loads(path.read_text(encoding="utf-8")) + except json.JSONDecodeError as exc: + die(f"Codex auth file is not valid JSON: {exc}") + if not isinstance(data, dict): + die("Codex auth file must contain a JSON object.") + api_key = data.get("OPENAI_API_KEY") + if not isinstance(api_key, str) or not api_key.strip(): + die("OPENAI_API_KEY is missing from Codex auth.") + return api_key.strip() + + +def print_redacted_config( + *, + auth_path: Path, + system_script: Path, +) -> None: + api_key = load_auth(auth_path) + payload: dict[str, Any] = { + "auth_path": str(auth_path), + "direct_origin": DEFAULT_ORIGIN, + "openai_base_url": DEFAULT_BASE_URL, + "openai_api_key": f"", + "system_imagegen_script": str(system_script), + "system_imagegen_exists": system_script.exists(), + } + print(json.dumps(payload, ensure_ascii=False, indent=2)) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Run system imagegen CLI through the server6 direct Sub2API endpoint.", + allow_abbrev=False, + ) + parser.add_argument( + "--auth-file", + type=Path, + default=Path.home() / ".codex/auth.json", + help="Path to Codex auth JSON. Defaults to ~/.codex/auth.json.", + ) + parser.add_argument( + "--system-imagegen-script", + type=Path, + default=DEFAULT_SYSTEM_IMAGEGEN, + help="Path to the system imagegen CLI script.", + ) + parser.add_argument( + "--print-config-redacted", + action="store_true", + help="Print resolved non-secret config and exit.", + ) + parser.add_argument( + "imagegen_args", + nargs=argparse.REMAINDER, + help="Arguments passed through to the system imagegen CLI.", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + auth_path = args.auth_file.expanduser() + system_script = args.system_imagegen_script.expanduser() + + if args.print_config_redacted: + print_redacted_config(auth_path=auth_path, system_script=system_script) + return 0 + + if not args.imagegen_args: + die("Missing imagegen CLI arguments. Try: generate --prompt ... --out output.png") + if not system_script.exists(): + die(f"System imagegen CLI not found: {system_script}") + + env = os.environ.copy() + env["OPENAI_API_KEY"] = load_auth(auth_path) + env["OPENAI_BASE_URL"] = DEFAULT_BASE_URL + + command = [sys.executable, str(system_script), *args.imagegen_args] + completed = subprocess.run(command, env=env, check=False) + return completed.returncode + + +if __name__ == "__main__": + raise SystemExit(main())