diff --git a/skills/baoyu-image-gen/SKILL.md b/skills/baoyu-image-gen/SKILL.md index 58ac0d2..dd04a24 100644 --- a/skills/baoyu-image-gen/SKILL.md +++ b/skills/baoyu-image-gen/SKILL.md @@ -41,7 +41,9 @@ test -f "$HOME/.baoyu-skills/baoyu-image-gen/EXTEND.md" && echo "user" │ Not found │ Use defaults │ └───────────┴───────────────────────────────────────────────────────────────────────────┘ -**EXTEND.md Supports**: Default provider | Default quality | Default aspect ratio +**EXTEND.md Supports**: Default provider | Default quality | Default aspect ratio | Default image size | Default models + +Schema: `references/config/preferences-schema.md` ## Usage @@ -99,7 +101,7 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "一只可爱的猫" --image ou | `GOOGLE_BASE_URL` | Custom Google endpoint | | `DASHSCOPE_BASE_URL` | Custom DashScope endpoint | -**Load Priority**: CLI args > env vars > `/.baoyu-skills/.env` > `~/.baoyu-skills/.env` +**Load Priority**: CLI args > EXTEND.md > env vars > `/.baoyu-skills/.env` > `~/.baoyu-skills/.env` ## Provider Selection diff --git a/skills/baoyu-image-gen/references/config/preferences-schema.md b/skills/baoyu-image-gen/references/config/preferences-schema.md new file mode 100644 index 0000000..ba840ee --- /dev/null +++ b/skills/baoyu-image-gen/references/config/preferences-schema.md @@ -0,0 +1,66 @@ +--- +name: preferences-schema +description: EXTEND.md YAML schema for baoyu-image-gen user preferences +--- + +# Preferences Schema + +## Full Schema + +```yaml +--- +version: 1 + +default_provider: null # google|openai|dashscope|null (null = auto-detect) + +default_quality: null # normal|2k|null (null = use default: 2k) + +default_aspect_ratio: null # "16:9"|"1:1"|"4:3"|"3:4"|"2.35:1"|null + +default_image_size: null # 1K|2K|4K|null (Google only, overrides quality) + +default_model: + google: null # e.g., "gemini-3-pro-image-preview" + openai: null # e.g., "gpt-image-1.5" + dashscope: null # e.g., "z-image-turbo" +--- +``` + +## Field Reference + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `version` | int | 1 | Schema version | +| `default_provider` | string\|null | null | Default provider (null = auto-detect) | +| `default_quality` | string\|null | null | Default quality (null = 2k) | +| `default_aspect_ratio` | string\|null | null | Default aspect ratio | +| `default_image_size` | string\|null | null | Google image size (overrides quality) | +| `default_model.google` | string\|null | null | Google default model | +| `default_model.openai` | string\|null | null | OpenAI default model | +| `default_model.dashscope` | string\|null | null | DashScope default model | + +## Examples + +**Minimal**: +```yaml +--- +version: 1 +default_provider: google +default_quality: 2k +--- +``` + +**Full**: +```yaml +--- +version: 1 +default_provider: google +default_quality: 2k +default_aspect_ratio: "16:9" +default_image_size: 2K +default_model: + google: "gemini-3-pro-image-preview" + openai: "gpt-image-1.5" + dashscope: "z-image-turbo" +--- +``` diff --git a/skills/baoyu-image-gen/scripts/main.ts b/skills/baoyu-image-gen/scripts/main.ts index 1dddcd1..d24469b 100644 --- a/skills/baoyu-image-gen/scripts/main.ts +++ b/skills/baoyu-image-gen/scripts/main.ts @@ -2,7 +2,7 @@ import path from "node:path"; import process from "node:process"; import { homedir } from "node:os"; import { mkdir, readFile, writeFile } from "node:fs/promises"; -import type { CliArgs, Provider } from "./types"; +import type { CliArgs, Provider, ExtendConfig } from "./types"; function printUsage(): void { console.log(`Usage: @@ -37,7 +37,7 @@ Environment variables: GOOGLE_BASE_URL Custom Google endpoint DASHSCOPE_BASE_URL Custom DashScope endpoint -Env file load order: CLI args > process.env > /.baoyu-skills/.env > ~/.baoyu-skills/.env`); +Env file load order: CLI args > EXTEND.md > process.env > /.baoyu-skills/.env > ~/.baoyu-skills/.env`); } function parseArgs(argv: string[]): CliArgs { @@ -49,7 +49,7 @@ function parseArgs(argv: string[]): CliArgs { model: null, aspectRatio: null, size: null, - quality: "2k", + quality: null, imageSize: null, referenceImages: [], n: 1, @@ -215,6 +215,87 @@ async function loadEnv(): Promise { } } +function extractYamlFrontMatter(content: string): string | null { + const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*$/m); + return match ? match[1] : null; +} + +function parseSimpleYaml(yaml: string): Partial { + const config: Partial = {}; + const lines = yaml.split("\n"); + let currentKey: string | null = null; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + + if (trimmed.includes(":") && !trimmed.startsWith("-")) { + const colonIdx = trimmed.indexOf(":"); + const key = trimmed.slice(0, colonIdx).trim(); + let value = trimmed.slice(colonIdx + 1).trim(); + + if (value === "null" || value === "") { + value = "null"; + } + + if (key === "version") { + config.version = value === "null" ? 1 : parseInt(value, 10); + } else if (key === "default_provider") { + config.default_provider = value === "null" ? null : (value as Provider); + } else if (key === "default_quality") { + config.default_quality = value === "null" ? null : (value as "normal" | "2k"); + } else if (key === "default_aspect_ratio") { + const cleaned = value.replace(/['"]/g, ""); + config.default_aspect_ratio = cleaned === "null" ? null : cleaned; + } else if (key === "default_image_size") { + config.default_image_size = value === "null" ? null : (value as "1K" | "2K" | "4K"); + } else if (key === "default_model") { + config.default_model = { google: null, openai: null, dashscope: null }; + currentKey = "default_model"; + } else if (currentKey === "default_model" && (key === "google" || key === "openai" || key === "dashscope")) { + const cleaned = value.replace(/['"]/g, ""); + config.default_model![key] = cleaned === "null" ? null : cleaned; + } + } + } + + return config; +} + +async function loadExtendConfig(): Promise> { + const home = homedir(); + const cwd = process.cwd(); + + const paths = [ + path.join(cwd, ".baoyu-skills", "baoyu-image-gen", "EXTEND.md"), + path.join(home, ".baoyu-skills", "baoyu-image-gen", "EXTEND.md"), + ]; + + for (const p of paths) { + try { + const content = await readFile(p, "utf8"); + const yaml = extractYamlFrontMatter(content); + if (!yaml) continue; + + return parseSimpleYaml(yaml); + } catch { + continue; + } + } + + return {}; +} + +function mergeConfig(args: CliArgs, extend: Partial): CliArgs { + return { + ...args, + provider: args.provider ?? extend.default_provider ?? null, + quality: args.quality ?? extend.default_quality ?? null, + aspectRatio: args.aspectRatio ?? extend.default_aspect_ratio ?? null, + imageSize: args.imageSize ?? extend.default_image_size ?? null, + }; +} + async function readPromptFromFiles(files: string[]): Promise { const parts: string[] = []; for (const f of files) { @@ -283,9 +364,13 @@ async function main(): Promise { } await loadEnv(); + const extendConfig = await loadExtendConfig(); + const mergedArgs = mergeConfig(args, extendConfig); - let prompt: string | null = args.prompt; - if (!prompt && args.promptFiles.length > 0) prompt = await readPromptFromFiles(args.promptFiles); + if (!mergedArgs.quality) mergedArgs.quality = "2k"; + + let prompt: string | null = mergedArgs.prompt; + if (!prompt && mergedArgs.promptFiles.length > 0) prompt = await readPromptFromFiles(mergedArgs.promptFiles); if (!prompt) prompt = await readPromptFromStdin(); if (!prompt) { @@ -295,24 +380,32 @@ async function main(): Promise { return; } - if (!args.imagePath) { + if (!mergedArgs.imagePath) { console.error("Error: --image is required"); printUsage(); process.exitCode = 1; return; } - const provider = detectProvider(args); + const provider = detectProvider(mergedArgs); const providerModule = await loadProviderModule(provider); - const model = args.model || providerModule.getDefaultModel(); - const outputPath = normalizeOutputImagePath(args.imagePath); + + let model = mergedArgs.model; + if (!model && extendConfig.default_model) { + if (provider === "google") model = extendConfig.default_model.google ?? null; + if (provider === "openai") model = extendConfig.default_model.openai ?? null; + if (provider === "dashscope") model = extendConfig.default_model.dashscope ?? null; + } + model = model || providerModule.getDefaultModel(); + + const outputPath = normalizeOutputImagePath(mergedArgs.imagePath); let imageData: Uint8Array; let retried = false; while (true) { try { - imageData = await providerModule.generateImage(prompt, model, args); + imageData = await providerModule.generateImage(prompt, model, mergedArgs); break; } catch (e) { if (!retried) { @@ -328,7 +421,7 @@ async function main(): Promise { await mkdir(dir, { recursive: true }); await writeFile(outputPath, imageData); - if (args.json) { + if (mergedArgs.json) { console.log( JSON.stringify( { diff --git a/skills/baoyu-image-gen/scripts/types.ts b/skills/baoyu-image-gen/scripts/types.ts index cdd3c8c..a595e9a 100644 --- a/skills/baoyu-image-gen/scripts/types.ts +++ b/skills/baoyu-image-gen/scripts/types.ts @@ -9,10 +9,23 @@ export type CliArgs = { model: string | null; aspectRatio: string | null; size: string | null; - quality: Quality; + quality: Quality | null; imageSize: string | null; referenceImages: string[]; n: number; json: boolean; help: boolean; }; + +export type ExtendConfig = { + version: number; + default_provider: Provider | null; + default_quality: Quality | null; + default_aspect_ratio: string | null; + default_image_size: "1K" | "2K" | "4K" | null; + default_model: { + google: string | null; + openai: string | null; + dashscope: string | null; + }; +};