mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-12 22:09:48 +08:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e736707628 | |||
| d863f11f61 | |||
| 2ce873c65c | |||
| 964cf1e045 | |||
| eded9a98bb | |||
| a64fdbd23f | |||
| 36b9c5e197 | |||
| 851497abbd | |||
| 65a561e654 |
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||
"version": "1.34.1"
|
||||
"version": "1.35.0"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
|
||||
English | [中文](./CHANGELOG.zh.md)
|
||||
|
||||
## 1.35.0 - 2026-02-24
|
||||
|
||||
### Features
|
||||
- `baoyu-image-gen`: add Replicate provider support with configurable models (by @justnode)
|
||||
- `baoyu-infographic`: add `dense-modules` layout and 3 new styles (`morandi-journal`, `pop-laboratory`, `retro-pop-grid`) for high-density infographics. Add keyword shortcuts for auto-selection. Prompt credit: [AJ](https://waytoagi.feishu.cn/wiki/YG0zwalijihRREkgmPzcWRInnUg)
|
||||
|
||||
### Documentation
|
||||
- `baoyu-image-gen`: add Replicate model configuration documentation
|
||||
|
||||
## 1.34.2 - 2026-02-25
|
||||
|
||||
### Documentation
|
||||
- `baoyu-markdown-to-html`: clarify theme resolution order with local and cross-skill EXTEND.md fallbacks before prompting user.
|
||||
- `baoyu-post-to-wechat`: align markdown conversion theme handling with deterministic fallback (`CLI --theme` -> EXTEND.md `default_theme` -> `default`) and require explicit `--theme` parameter.
|
||||
|
||||
## 1.34.1 - 2026-02-20
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
|
||||
[English](./CHANGELOG.md) | 中文
|
||||
|
||||
## 1.35.0 - 2026-02-24
|
||||
|
||||
### 新功能
|
||||
- `baoyu-image-gen`:新增 Replicate 图片生成服务,支持自定义模型配置 (by @justnode)
|
||||
- `baoyu-infographic`:新增 `dense-modules` 高密度模块布局及 3 种新风格(`morandi-journal`、`pop-laboratory`、`retro-pop-grid`),支持关键词快捷选择。高密度信息大图提示词来自 [AJ](https://waytoagi.feishu.cn/wiki/YG0zwalijihRREkgmPzcWRInnUg)
|
||||
|
||||
### 文档
|
||||
- `baoyu-image-gen`:补充 Replicate 模型配置说明文档
|
||||
|
||||
## 1.34.2 - 2026-02-25
|
||||
|
||||
### 文档
|
||||
- `baoyu-markdown-to-html`:明确主题解析优先级,先读取本技能与跨技能 EXTEND.md 的 `default_theme`,仅在未命中时询问用户。
|
||||
- `baoyu-post-to-wechat`:统一 markdown 转 HTML 的主题解析回退链(CLI `--theme` -> EXTEND.md `default_theme` -> `default`),并强制始终显式传入 `--theme` 参数。
|
||||
|
||||
## 1.34.1 - 2026-02-20
|
||||
|
||||
### 修复
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: baoyu-image-gen
|
||||
description: AI image generation with OpenAI, Google and DashScope APIs. Supports text-to-image, reference images, aspect ratios. Sequential by default; parallel generation available on request. Use when user asks to generate, create, or draw images.
|
||||
description: AI image generation with OpenAI, Google, DashScope and Replicate APIs. Supports text-to-image, reference images, aspect ratios. Sequential by default; parallel generation available on request. Use when user asks to generate, create, or draw images.
|
||||
---
|
||||
|
||||
# Image Generation (AI SDK)
|
||||
|
||||
Official API-based image generation. Supports OpenAI, Google and DashScope (阿里通义万象) providers.
|
||||
Official API-based image generation. Supports OpenAI, Google, DashScope (阿里通义万象) and Replicate providers.
|
||||
|
||||
## Script Directory
|
||||
|
||||
@@ -71,6 +71,12 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provi
|
||||
|
||||
# DashScope (阿里通义万象)
|
||||
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "一只可爱的猫" --image out.png --provider dashscope
|
||||
|
||||
# Replicate (google/nano-banana-pro)
|
||||
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provider replicate
|
||||
|
||||
# Replicate with specific model
|
||||
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provider replicate --model google/nano-banana
|
||||
```
|
||||
|
||||
## Options
|
||||
@@ -80,7 +86,7 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "一只可爱的猫" --image ou
|
||||
| `--prompt <text>`, `-p` | Prompt text |
|
||||
| `--promptfiles <files...>` | Read prompt from files (concatenated) |
|
||||
| `--image <path>` | Output image path (required) |
|
||||
| `--provider google\|openai\|dashscope` | Force provider (default: google) |
|
||||
| `--provider google\|openai\|dashscope\|replicate` | Force provider (default: google) |
|
||||
| `--model <id>`, `-m` | Model ID (`--ref` with OpenAI requires GPT Image model, e.g. `gpt-image-1.5`) |
|
||||
| `--ar <ratio>` | Aspect ratio (e.g., `16:9`, `1:1`, `4:3`) |
|
||||
| `--size <WxH>` | Size (e.g., `1024x1024`) |
|
||||
@@ -97,19 +103,46 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "一只可爱的猫" --image ou
|
||||
| `OPENAI_API_KEY` | OpenAI API key |
|
||||
| `GOOGLE_API_KEY` | Google API key |
|
||||
| `DASHSCOPE_API_KEY` | DashScope API key (阿里云) |
|
||||
| `REPLICATE_API_TOKEN` | Replicate API token |
|
||||
| `OPENAI_IMAGE_MODEL` | OpenAI model override |
|
||||
| `GOOGLE_IMAGE_MODEL` | Google model override |
|
||||
| `DASHSCOPE_IMAGE_MODEL` | DashScope model override (default: z-image-turbo) |
|
||||
| `REPLICATE_IMAGE_MODEL` | Replicate model override (default: google/nano-banana-pro) |
|
||||
| `OPENAI_BASE_URL` | Custom OpenAI endpoint |
|
||||
| `GOOGLE_BASE_URL` | Custom Google endpoint |
|
||||
| `DASHSCOPE_BASE_URL` | Custom DashScope endpoint |
|
||||
| `REPLICATE_BASE_URL` | Custom Replicate endpoint |
|
||||
|
||||
**Load Priority**: CLI args > EXTEND.md > env vars > `<cwd>/.baoyu-skills/.env` > `~/.baoyu-skills/.env`
|
||||
|
||||
## Replicate Model Configuration
|
||||
|
||||
When using `--provider replicate`, the model can be configured in the following ways (highest priority first):
|
||||
|
||||
1. CLI flag: `--model <owner/name>`
|
||||
2. EXTEND.md: `default_model.replicate`
|
||||
3. Env var: `REPLICATE_IMAGE_MODEL`
|
||||
4. Built-in default: `google/nano-banana-pro`
|
||||
|
||||
Supported model formats:
|
||||
|
||||
- `owner/name` (recommended for official models), e.g. `google/nano-banana-pro`
|
||||
- `owner/name:version` (community models by version), e.g. `stability-ai/sdxl:<version>`
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Use Replicate default model
|
||||
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provider replicate
|
||||
|
||||
# Override model explicitly
|
||||
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provider replicate --model google/nano-banana
|
||||
```
|
||||
|
||||
## Provider Selection
|
||||
|
||||
1. `--ref` provided + no `--provider` → auto-select Google first, then OpenAI
|
||||
2. `--provider` specified → use it (if `--ref`, must be `google` or `openai`)
|
||||
1. `--ref` provided + no `--provider` → auto-select Google first, then OpenAI, then Replicate
|
||||
2. `--provider` specified → use it (if `--ref`, must be `google`, `openai`, or `replicate`)
|
||||
3. Only one API key available → use that provider
|
||||
4. Multiple available → default to Google
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ description: EXTEND.md YAML schema for baoyu-image-gen user preferences
|
||||
---
|
||||
version: 1
|
||||
|
||||
default_provider: null # google|openai|dashscope|null (null = auto-detect)
|
||||
default_provider: null # google|openai|dashscope|replicate|null (null = auto-detect)
|
||||
|
||||
default_quality: null # normal|2k|null (null = use default: 2k)
|
||||
|
||||
@@ -23,6 +23,7 @@ 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"
|
||||
replicate: null # e.g., "google/nano-banana-pro"
|
||||
---
|
||||
```
|
||||
|
||||
@@ -38,6 +39,7 @@ default_model:
|
||||
| `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 |
|
||||
| `default_model.replicate` | string\|null | null | Replicate default model |
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -62,5 +64,6 @@ default_model:
|
||||
google: "gemini-3-pro-image-preview"
|
||||
openai: "gpt-image-1.5"
|
||||
dashscope: "z-image-turbo"
|
||||
replicate: "google/nano-banana-pro"
|
||||
---
|
||||
```
|
||||
|
||||
@@ -14,7 +14,7 @@ Options:
|
||||
-p, --prompt <text> Prompt text
|
||||
--promptfiles <files...> Read prompt from files (concatenated)
|
||||
--image <path> Output image path (required)
|
||||
--provider google|openai|dashscope Force provider (auto-detect by default)
|
||||
--provider google|openai|dashscope|replicate Force provider (auto-detect by default)
|
||||
-m, --model <id> Model ID
|
||||
--ar <ratio> Aspect ratio (e.g., 16:9, 1:1, 4:3)
|
||||
--size <WxH> Size (e.g., 1024x1024)
|
||||
@@ -30,12 +30,15 @@ Environment variables:
|
||||
GOOGLE_API_KEY Google API key
|
||||
GEMINI_API_KEY Gemini API key (alias for GOOGLE_API_KEY)
|
||||
DASHSCOPE_API_KEY DashScope API key (阿里云通义万象)
|
||||
REPLICATE_API_TOKEN Replicate API token
|
||||
OPENAI_IMAGE_MODEL Default OpenAI model (gpt-image-1.5)
|
||||
GOOGLE_IMAGE_MODEL Default Google model (gemini-3-pro-image-preview)
|
||||
DASHSCOPE_IMAGE_MODEL Default DashScope model (z-image-turbo)
|
||||
REPLICATE_IMAGE_MODEL Default Replicate model (google/nano-banana-pro)
|
||||
OPENAI_BASE_URL Custom OpenAI endpoint
|
||||
GOOGLE_BASE_URL Custom Google endpoint
|
||||
DASHSCOPE_BASE_URL Custom DashScope endpoint
|
||||
REPLICATE_BASE_URL Custom Replicate endpoint
|
||||
|
||||
Env file load order: CLI args > EXTEND.md > process.env > <cwd>/.baoyu-skills/.env > ~/.baoyu-skills/.env`);
|
||||
}
|
||||
@@ -108,7 +111,7 @@ function parseArgs(argv: string[]): CliArgs {
|
||||
|
||||
if (a === "--provider") {
|
||||
const v = argv[++i];
|
||||
if (v !== "google" && v !== "openai" && v !== "dashscope") throw new Error(`Invalid provider: ${v}`);
|
||||
if (v !== "google" && v !== "openai" && v !== "dashscope" && v !== "replicate") throw new Error(`Invalid provider: ${v}`);
|
||||
out.provider = v;
|
||||
continue;
|
||||
}
|
||||
@@ -250,9 +253,9 @@ function parseSimpleYaml(yaml: string): Partial<ExtendConfig> {
|
||||
} 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 };
|
||||
config.default_model = { google: null, openai: null, dashscope: null, replicate: null };
|
||||
currentKey = "default_model";
|
||||
} else if (currentKey === "default_model" && (key === "google" || key === "openai" || key === "dashscope")) {
|
||||
} else if (currentKey === "default_model" && (key === "google" || key === "openai" || key === "dashscope" || key === "replicate")) {
|
||||
const cleaned = value.replace(/['"]/g, "");
|
||||
config.default_model![key] = cleaned === "null" ? null : cleaned;
|
||||
}
|
||||
@@ -323,9 +326,9 @@ function normalizeOutputImagePath(p: string): string {
|
||||
}
|
||||
|
||||
function detectProvider(args: CliArgs): Provider {
|
||||
if (args.referenceImages.length > 0 && args.provider && args.provider !== "google" && args.provider !== "openai") {
|
||||
if (args.referenceImages.length > 0 && args.provider && args.provider !== "google" && args.provider !== "openai" && args.provider !== "replicate") {
|
||||
throw new Error(
|
||||
"Reference images require a ref-capable provider. Use --provider google (Gemini multimodal) or --provider openai (GPT Image edits)."
|
||||
"Reference images require a ref-capable provider. Use --provider google (Gemini multimodal), --provider openai (GPT Image edits), or --provider replicate."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -334,22 +337,24 @@ function detectProvider(args: CliArgs): Provider {
|
||||
const hasGoogle = !!(process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY);
|
||||
const hasOpenai = !!process.env.OPENAI_API_KEY;
|
||||
const hasDashscope = !!process.env.DASHSCOPE_API_KEY;
|
||||
const hasReplicate = !!process.env.REPLICATE_API_TOKEN;
|
||||
|
||||
if (args.referenceImages.length > 0) {
|
||||
if (hasGoogle) return "google";
|
||||
if (hasOpenai) return "openai";
|
||||
if (hasReplicate) return "replicate";
|
||||
throw new Error(
|
||||
"Reference images require Google or OpenAI. Set GOOGLE_API_KEY/GEMINI_API_KEY or OPENAI_API_KEY, or remove --ref."
|
||||
"Reference images require Google, OpenAI or Replicate. Set GOOGLE_API_KEY/GEMINI_API_KEY, OPENAI_API_KEY, or REPLICATE_API_TOKEN, or remove --ref."
|
||||
);
|
||||
}
|
||||
|
||||
const available = [hasGoogle && "google", hasOpenai && "openai", hasDashscope && "dashscope"].filter(Boolean) as Provider[];
|
||||
const available = [hasGoogle && "google", hasOpenai && "openai", hasDashscope && "dashscope", hasReplicate && "replicate"].filter(Boolean) as Provider[];
|
||||
|
||||
if (available.length === 1) return available[0]!;
|
||||
if (available.length > 1) return available[0]!;
|
||||
|
||||
throw new Error(
|
||||
"No API key found. Set GOOGLE_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY, or DASHSCOPE_API_KEY.\n" +
|
||||
"No API key found. Set GOOGLE_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY, DASHSCOPE_API_KEY, or REPLICATE_API_TOKEN.\n" +
|
||||
"Create ~/.baoyu-skills/.env or <cwd>/.baoyu-skills/.env with your keys."
|
||||
);
|
||||
}
|
||||
@@ -389,6 +394,9 @@ async function loadProviderModule(provider: Provider): Promise<ProviderModule> {
|
||||
if (provider === "dashscope") {
|
||||
return (await import("./providers/dashscope")) as ProviderModule;
|
||||
}
|
||||
if (provider === "replicate") {
|
||||
return (await import("./providers/replicate")) as ProviderModule;
|
||||
}
|
||||
return (await import("./providers/openai")) as ProviderModule;
|
||||
}
|
||||
|
||||
@@ -436,6 +444,7 @@ async function main(): Promise<void> {
|
||||
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;
|
||||
if (provider === "replicate") model = extendConfig.default_model.replicate ?? null;
|
||||
}
|
||||
model = model || providerModule.getDefaultModel();
|
||||
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import path from "node:path";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import type { CliArgs } from "../types";
|
||||
|
||||
const DEFAULT_MODEL = "google/nano-banana-pro";
|
||||
const SYNC_WAIT_SECONDS = 60;
|
||||
const POLL_INTERVAL_MS = 2000;
|
||||
const MAX_POLL_MS = 300_000;
|
||||
|
||||
export function getDefaultModel(): string {
|
||||
return process.env.REPLICATE_IMAGE_MODEL || DEFAULT_MODEL;
|
||||
}
|
||||
|
||||
function getApiToken(): string | null {
|
||||
return process.env.REPLICATE_API_TOKEN || null;
|
||||
}
|
||||
|
||||
function getBaseUrl(): string {
|
||||
const base = process.env.REPLICATE_BASE_URL || "https://api.replicate.com";
|
||||
return base.replace(/\/+$/g, "");
|
||||
}
|
||||
|
||||
function parseModelId(model: string): { owner: string; name: string; version: string | null } {
|
||||
const [ownerName, version] = model.split(":");
|
||||
const parts = ownerName!.split("/");
|
||||
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
||||
throw new Error(
|
||||
`Invalid Replicate model format: "${model}". Expected "owner/name" or "owner/name:version".`
|
||||
);
|
||||
}
|
||||
return { owner: parts[0], name: parts[1], version: version || null };
|
||||
}
|
||||
|
||||
function buildInput(prompt: string, args: CliArgs, referenceImages: string[]): Record<string, unknown> {
|
||||
const input: Record<string, unknown> = { prompt };
|
||||
|
||||
if (args.aspectRatio) {
|
||||
input.aspect_ratio = args.aspectRatio;
|
||||
}
|
||||
|
||||
if (args.n > 1) {
|
||||
input.number_of_images = args.n;
|
||||
}
|
||||
|
||||
input.output_format = "png";
|
||||
|
||||
if (referenceImages.length > 0) {
|
||||
if (referenceImages.length === 1) {
|
||||
input.image = referenceImages[0];
|
||||
} else {
|
||||
for (let i = 0; i < referenceImages.length; i++) {
|
||||
input[`image${i > 0 ? i + 1 : ""}`] = referenceImages[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
async function readImageAsDataUrl(p: string): Promise<string> {
|
||||
const buf = await readFile(p);
|
||||
const ext = path.extname(p).toLowerCase();
|
||||
let mimeType = "image/png";
|
||||
if (ext === ".jpg" || ext === ".jpeg") mimeType = "image/jpeg";
|
||||
else if (ext === ".gif") mimeType = "image/gif";
|
||||
else if (ext === ".webp") mimeType = "image/webp";
|
||||
return `data:${mimeType};base64,${buf.toString("base64")}`;
|
||||
}
|
||||
|
||||
type PredictionResponse = {
|
||||
id: string;
|
||||
status: string;
|
||||
output: unknown;
|
||||
error: string | null;
|
||||
urls?: { get?: string };
|
||||
};
|
||||
|
||||
async function createPrediction(
|
||||
apiToken: string,
|
||||
model: { owner: string; name: string; version: string | null },
|
||||
input: Record<string, unknown>,
|
||||
sync: boolean
|
||||
): Promise<PredictionResponse> {
|
||||
const baseUrl = getBaseUrl();
|
||||
|
||||
let url: string;
|
||||
const body: Record<string, unknown> = { input };
|
||||
|
||||
if (model.version) {
|
||||
url = `${baseUrl}/v1/predictions`;
|
||||
body.version = model.version;
|
||||
} else {
|
||||
url = `${baseUrl}/v1/models/${model.owner}/${model.name}/predictions`;
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
if (sync) {
|
||||
headers["Prefer"] = `wait=${SYNC_WAIT_SECONDS}`;
|
||||
}
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.text();
|
||||
throw new Error(`Replicate API error (${res.status}): ${err}`);
|
||||
}
|
||||
|
||||
return (await res.json()) as PredictionResponse;
|
||||
}
|
||||
|
||||
async function pollPrediction(apiToken: string, getUrl: string): Promise<PredictionResponse> {
|
||||
const start = Date.now();
|
||||
|
||||
while (Date.now() - start < MAX_POLL_MS) {
|
||||
const res = await fetch(getUrl, {
|
||||
headers: { Authorization: `Bearer ${apiToken}` },
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.text();
|
||||
throw new Error(`Replicate poll error (${res.status}): ${err}`);
|
||||
}
|
||||
|
||||
const prediction = (await res.json()) as PredictionResponse;
|
||||
|
||||
if (prediction.status === "succeeded") return prediction;
|
||||
if (prediction.status === "failed" || prediction.status === "canceled") {
|
||||
throw new Error(`Replicate prediction ${prediction.status}: ${prediction.error || "unknown error"}`);
|
||||
}
|
||||
|
||||
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
||||
}
|
||||
|
||||
throw new Error(`Replicate prediction timed out after ${MAX_POLL_MS / 1000}s`);
|
||||
}
|
||||
|
||||
function extractOutputUrl(prediction: PredictionResponse): string {
|
||||
const output = prediction.output;
|
||||
|
||||
if (typeof output === "string") return output;
|
||||
|
||||
if (Array.isArray(output)) {
|
||||
const first = output[0];
|
||||
if (typeof first === "string") return first;
|
||||
}
|
||||
|
||||
if (output && typeof output === "object" && "url" in output) {
|
||||
const url = (output as Record<string, unknown>).url;
|
||||
if (typeof url === "string") return url;
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected Replicate output format: ${JSON.stringify(output)}`);
|
||||
}
|
||||
|
||||
async function downloadImage(url: string): Promise<Uint8Array> {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error(`Failed to download image from Replicate: ${res.status}`);
|
||||
const buf = await res.arrayBuffer();
|
||||
return new Uint8Array(buf);
|
||||
}
|
||||
|
||||
export async function generateImage(
|
||||
prompt: string,
|
||||
model: string,
|
||||
args: CliArgs
|
||||
): Promise<Uint8Array> {
|
||||
const apiToken = getApiToken();
|
||||
if (!apiToken) throw new Error("REPLICATE_API_TOKEN is required. Get one at https://replicate.com/account/api-tokens");
|
||||
|
||||
const parsedModel = parseModelId(model);
|
||||
|
||||
const refDataUrls: string[] = [];
|
||||
for (const refPath of args.referenceImages) {
|
||||
refDataUrls.push(await readImageAsDataUrl(refPath));
|
||||
}
|
||||
|
||||
const input = buildInput(prompt, args, refDataUrls);
|
||||
|
||||
console.log(`Generating image with Replicate (${model})...`);
|
||||
|
||||
let prediction = await createPrediction(apiToken, parsedModel, input, true);
|
||||
|
||||
if (prediction.status !== "succeeded") {
|
||||
if (!prediction.urls?.get) {
|
||||
throw new Error("Replicate prediction did not return a poll URL");
|
||||
}
|
||||
console.log("Waiting for prediction to complete...");
|
||||
prediction = await pollPrediction(apiToken, prediction.urls.get);
|
||||
}
|
||||
|
||||
console.log("Generation completed.");
|
||||
|
||||
const outputUrl = extractOutputUrl(prediction);
|
||||
return downloadImage(outputUrl);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export type Provider = "google" | "openai" | "dashscope";
|
||||
export type Provider = "google" | "openai" | "dashscope" | "replicate";
|
||||
export type Quality = "normal" | "2k";
|
||||
|
||||
export type CliArgs = {
|
||||
@@ -27,5 +27,6 @@ export type ExtendConfig = {
|
||||
google: string | null;
|
||||
openai: string | null;
|
||||
dashscope: string | null;
|
||||
replicate: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: baoyu-infographic
|
||||
description: Generates professional infographics with 20 layout types and 17 visual styles. Analyzes content, recommends layout×style combinations, and generates publication-ready infographics. Use when user asks to create "infographic", "信息图", "visual summary", or "可视化".
|
||||
description: Generates professional infographics with 21 layout types and 20 visual styles. Analyzes content, recommends layout×style combinations, and generates publication-ready infographics. Use when user asks to create "infographic", "信息图", "visual summary", "可视化", or "高密度信息大图".
|
||||
---
|
||||
|
||||
# Infographic Generator
|
||||
@@ -20,8 +20,8 @@ Two dimensions: **layout** (information structure) × **style** (visual aestheti
|
||||
|
||||
| Option | Values |
|
||||
|--------|--------|
|
||||
| `--layout` | 20 options (see Layout Gallery), default: bento-grid |
|
||||
| `--style` | 17 options (see Style Gallery), default: craft-handmade |
|
||||
| `--layout` | 21 options (see Layout Gallery), default: bento-grid |
|
||||
| `--style` | 20 options (see Style Gallery), default: craft-handmade |
|
||||
| `--aspect` | landscape (16:9), portrait (9:16), square (1:1) |
|
||||
| `--lang` | en, zh, ja, etc. |
|
||||
|
||||
@@ -49,6 +49,7 @@ Two dimensions: **layout** (information structure) × **style** (visual aestheti
|
||||
| `venn-diagram` | Overlapping concepts |
|
||||
| `winding-roadmap` | Journey, milestones |
|
||||
| `circular-flow` | Cycles, recurring processes |
|
||||
| `dense-modules` | High-density modules, data-rich guides |
|
||||
|
||||
Full definitions: `references/layouts/<layout>.md`
|
||||
|
||||
@@ -73,6 +74,9 @@ Full definitions: `references/layouts/<layout>.md`
|
||||
| `ikea-manual` | Minimal line art |
|
||||
| `knolling` | Organized flat-lay |
|
||||
| `lego-brick` | Toy brick construction |
|
||||
| `pop-laboratory` | Blueprint grid, coordinate markers, lab precision |
|
||||
| `morandi-journal` | Hand-drawn doodle, warm Morandi tones |
|
||||
| `retro-pop-grid` | 1970s retro pop art, Swiss grid, thick outlines |
|
||||
|
||||
Full definitions: `references/styles/<style>.md`
|
||||
|
||||
@@ -92,9 +96,23 @@ Full definitions: `references/styles/<style>.md`
|
||||
| Educational | `bento-grid` + `chalkboard` |
|
||||
| Journey | `winding-roadmap` + `storybook-watercolor` |
|
||||
| Categories | `periodic-table` + `bold-graphic` |
|
||||
| Product Guide | `dense-modules` + `morandi-journal` |
|
||||
| Technical Guide | `dense-modules` + `pop-laboratory` |
|
||||
| Trendy Guide | `dense-modules` + `retro-pop-grid` |
|
||||
|
||||
Default: `bento-grid` + `craft-handmade`
|
||||
|
||||
## Keyword Shortcuts
|
||||
|
||||
When user input contains these keywords, **auto-select** the associated layout and offer associated styles as top recommendations in Step 3. Skip content-based layout inference for matched keywords.
|
||||
|
||||
If a shortcut has **Prompt Notes**, append them to the generated prompt (Step 5) as additional style instructions.
|
||||
|
||||
| User Keyword | Layout | Recommended Styles | Default Aspect | Prompt Notes |
|
||||
|--------------|--------|--------------------|----------------|--------------|
|
||||
| 高密度信息大图 / high-density-info | `dense-modules` | `morandi-journal`, `pop-laboratory`, `retro-pop-grid` | portrait | — |
|
||||
| 信息图 / infographic | `bento-grid` | `craft-handmade` | landscape | Minimalist: clean canvas, ample whitespace, no complex background textures. Simple cartoon elements and icons only. |
|
||||
|
||||
## Output Structure
|
||||
|
||||
```
|
||||
@@ -176,7 +194,9 @@ See `references/structured-content-template.md` for detailed format.
|
||||
|
||||
### Step 3: Recommend Combinations
|
||||
|
||||
Recommend 3-5 layout×style combinations based on:
|
||||
**3.1 Check Keyword Shortcuts first**: If user input matches a keyword from the **Keyword Shortcuts** table, auto-select the associated layout and prioritize associated styles as top recommendations. Skip content-based layout inference.
|
||||
|
||||
**3.2 Otherwise**, recommend 3-5 layout×style combinations based on:
|
||||
- Data structure → matching layout
|
||||
- Content tone → matching style
|
||||
- Audience expectations
|
||||
@@ -222,8 +242,8 @@ Report: topic, layout, style, aspect, language, output path, files created.
|
||||
- `references/analysis-framework.md` - Analysis methodology
|
||||
- `references/structured-content-template.md` - Content format
|
||||
- `references/base-prompt.md` - Prompt template
|
||||
- `references/layouts/<layout>.md` - 20 layout definitions
|
||||
- `references/styles/<style>.md` - 17 style definitions
|
||||
- `references/layouts/<layout>.md` - 21 layout definitions
|
||||
- `references/styles/<style>.md` - 20 style definitions
|
||||
|
||||
## Extension Support
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ Approach content analysis as a **world-class instructional designer**:
|
||||
| **Cycle/Loop** | Recurring processes, feedback loops | circular-flow | craft-handmade, technical-schematic |
|
||||
| **System/Structure** | Components, architecture, anatomy | structural-breakdown, bento-grid | technical-schematic, ikea-manual |
|
||||
| **Journey/Narrative** | Stories, user flows, milestones | winding-roadmap, story-mountain | storybook-watercolor, comic-strip |
|
||||
| **Overview/Summary** | Multiple topics, feature highlights | bento-grid, periodic-table | chalkboard, bold-graphic |
|
||||
| **Overview/Summary** | Multiple topics, feature highlights | bento-grid, periodic-table, dense-modules | chalkboard, bold-graphic |
|
||||
| **Product/Buying Guide** | Multi-dimension comparisons, specs, pitfalls | dense-modules | morandi-journal, pop-laboratory, retro-pop-grid |
|
||||
|
||||
### 2. Learning Objective Identification
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# dense-modules
|
||||
|
||||
High-density modular layout with 6-7 typed information modules packed with concrete data.
|
||||
|
||||
## Structure
|
||||
|
||||
- 6-7 distinct modules per image, each serving a specific information function
|
||||
- Every module contains concrete data: brand names, numbers, percentages, parameters
|
||||
- Minimal whitespace—compact spacing prioritized over breathing room
|
||||
- Smaller text acceptable to maximize information density
|
||||
- Each module identified by coordinate label or section marker (e.g., MOD-1, SEC-A)
|
||||
|
||||
## Module Archetypes
|
||||
|
||||
| Module | Purpose | Content Requirements |
|
||||
|--------|---------|---------------------|
|
||||
| **Brand/Selection Array** | Grid of options with recommendations | 4-8 items with icons, names, brief descriptions; highlight "best choice" |
|
||||
| **Specification Scale** | Quality/measurement gauge | 3-5 levels with precise numerical increments, quality indicators (emoji faces, checkmarks) |
|
||||
| **Deep Dive/Detail** | Technical breakdown of key item | Zoom-in callouts, internal components, cross-section or exploded view |
|
||||
| **Scenario Comparison** | Side-by-side use cases | 3-6 scenarios with specific recommendations and data per scenario |
|
||||
| **Identification Tips** | How-to checklist | 3-5 inspection methods: look/test/check/ask format |
|
||||
| **Warning/Pitfall Zone** | Critical mistakes to avoid | 3-5 pitfalls with consequences, 1-2 correct approaches; high visual contrast |
|
||||
| **Quick Reference** | Compact summary | Dense table, one-line summaries, decision flowchart, or key takeaways |
|
||||
|
||||
## Variants
|
||||
|
||||
| Variant | Focus | Visual Emphasis |
|
||||
|---------|-------|-----------------|
|
||||
| **Coordinate-labeled** | Precision and systematicity | Each module has alphanumeric coordinate (A-01, B-05, C-12), ruler/axis markers |
|
||||
| **Grid-cell** | Order and structure | Modules in strict rectangular cells divided by thick lines, Swiss grid feel |
|
||||
| **Free-flowing** | Organic density | Magazine-style layout with dotted frames, varying module sizes, connected by arrows |
|
||||
|
||||
## Best For
|
||||
|
||||
- Product selection guides and buying guides
|
||||
- Multi-dimensional comparison content
|
||||
- Data-rich educational materials
|
||||
- "Avoid pitfalls" / "complete guide" formats
|
||||
- Content targeting platforms like Xiaohongshu with high-density visual requirements
|
||||
|
||||
## Visual Elements
|
||||
|
||||
- Module boundary markers (thick lines, dotted frames, or coordinate grids)
|
||||
- Quality indicators per module (emoji faces, checkmarks, crosses, crowns)
|
||||
- Data callout boxes with highlighted numbers
|
||||
- Comparison arrows and progression indicators
|
||||
- Warning/alert visual markers for pitfall modules
|
||||
- Metadata in corners (page numbers, timestamps, small barcodes)
|
||||
|
||||
## Text Placement
|
||||
|
||||
- Main title at top, prominent and impactful
|
||||
- Subtitle with module count ("X大维度全面解析...")
|
||||
- Module headers inside colored badges or labeled frames
|
||||
- Body text compact, multiple columns within modules
|
||||
- Numbers highlighted with accent colors, slightly larger than body text
|
||||
|
||||
## Information Density Rules
|
||||
|
||||
- Every corner should contain useful information or metadata
|
||||
- No decorative-only empty space
|
||||
- Text size may be reduced to fit more content—information over font size
|
||||
- Each module must have specific data points, not generic descriptions
|
||||
- Balance between density and readability: dense but organized
|
||||
|
||||
## Recommended Pairings
|
||||
|
||||
- `pop-laboratory`: Technical precision with coordinate markers and blueprint grid
|
||||
- `morandi-journal`: Hand-drawn warmth with doodle illustrations and organic frames
|
||||
- `retro-pop-grid`: 1970s pop art with strict grid cells and bold contrast
|
||||
- `corporate-memphis`: Clean business feel for product comparisons
|
||||
- `technical-schematic`: Engineering precision for technical product guides
|
||||
@@ -0,0 +1,60 @@
|
||||
# morandi-journal
|
||||
|
||||
Hand-drawn doodle illustration with warm Morandi color tones and cozy bullet journal aesthetic.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- Background: Warm cream/beige with subtle paper texture (#F5F0E6)
|
||||
- Primary: Muted teal/sage green (#7BA3A8) for headers and frames
|
||||
- Secondary: Warm terracotta/orange (#D4956A) for highlights and numbers
|
||||
- Line art: Dark charcoal brown (#4A4540)
|
||||
- Soft highlights: Pale yellow (#F5E6C8)
|
||||
|
||||
## Visual Elements
|
||||
|
||||
- Hand-drawn doodle illustrations with organic, slightly imperfect ink lines
|
||||
- Washi tape strip decorations (diagonal stripes pattern, beige and brown)
|
||||
- Rounded card containers for brand/option items
|
||||
- Hand-drawn rulers, scales, and progress bars with emoji quality indicators
|
||||
- Smiley/frowny faces as quality markers (😊✓ 😐 ☹️✗)
|
||||
- Dotted line frames around sections
|
||||
- Connecting arrows and dotted lines between modules
|
||||
- Corner decorations: tiny houses, stars, sparkles, clouds
|
||||
- Wavy line dividers between sections
|
||||
- Callout bubbles for tips
|
||||
- Magnifying glass icons for identification tips
|
||||
- Thumbs up/down icons (hand-drawn style)
|
||||
|
||||
## Variants
|
||||
|
||||
| Variant | Focus | Visual Emphasis |
|
||||
|---------|-------|-----------------|
|
||||
| **Cozy journal** | Maximum warmth | More washi tape, stickers, decorative doodles |
|
||||
| **Clean sketch** | Readability | Cleaner lines, less decoration, more structured |
|
||||
|
||||
## Typography
|
||||
|
||||
- Main title: Bold hand-lettered calligraphy style with decorative flourishes
|
||||
- Module headers: Clean handwritten text in white on dark teal rounded badge (#6B9080)
|
||||
- Body text: Neat handwritten print style, easy to read
|
||||
- Numbers: Highlighted in terracotta (#D4956A), slightly larger than body
|
||||
|
||||
## Style Enforcement
|
||||
|
||||
- All imagery must maintain hand-drawn/doodle aesthetic—no digital precision
|
||||
- Organic, slightly imperfect shapes throughout
|
||||
- Sketch-like quality with visible line weight variations
|
||||
- Warm and cozy journal feel, not clinical or corporate
|
||||
|
||||
## Avoid
|
||||
|
||||
- Flat vector icons or emoji
|
||||
- Clean geometric shapes
|
||||
- Stock illustration style
|
||||
- Strict grid layout
|
||||
- Pure white background
|
||||
- Digital/corporate look
|
||||
|
||||
## Best For
|
||||
|
||||
Product selection guides, lifestyle content, educational overviews, consumer-facing comparison content, Xiaohongshu-style posts
|
||||
@@ -0,0 +1,48 @@
|
||||
# pop-laboratory
|
||||
|
||||
Lab manual precision meets pop art color impact—coordinate systems, technical diagrams, and fluorescent accents on blueprint grid.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- Background: Professional grayish-white with faint blueprint grid texture (#F2F2F2)
|
||||
- Primary: Muted teal/sage green (#B8D8BE) for major functional blocks and data zones
|
||||
- High-alert accent: Vibrant fluorescent pink (#E91E63) strictly for warnings, critical data, or "winner" highlights
|
||||
- Marker highlights: Vivid lemon yellow (#FFF200) as translucent highlighter effect for keywords
|
||||
- Line art: Ultra-fine charcoal brown (#2D2926) for technical grids, coordinates, and hairlines
|
||||
|
||||
## Visual Elements
|
||||
|
||||
- Coordinate-style labels on every module (e.g., R-20, G-02, SEC-08)
|
||||
- Technical diagrams: exploded views, cross-sections with anchor points, architectural skeletal lines
|
||||
- Vertical/horizontal rulers with precise markers (0.5mm, 1.8mm, 45°)
|
||||
- "Marker-over-print" effect: color blocks slightly offset from text, postmodern print feel
|
||||
- Cross-hair targets, mathematical symbols (Σ, Δ, ∞), directional arrows (X/Y axis)
|
||||
- Microscopic detail annotations alongside macroscopic bold headers
|
||||
- Corner metadata: tiny barcodes, timestamps, technical parameters
|
||||
- High contrast between massive bold headers and tiny 8pt-style annotations
|
||||
|
||||
## Typography
|
||||
|
||||
- Headers: Bold brutalist characters, high visual impact
|
||||
- Body: Professional sans-serif or crisp technical print
|
||||
- Numbers: Large, highlighted with yellow or blue to stand out
|
||||
- Annotations: Ultra-crisp, small technical labels
|
||||
|
||||
## Style Enforcement
|
||||
|
||||
- Strictly systematic color usage: only teal, pink, yellow, charcoal—no rainbow palette
|
||||
- Sufficient fine grid lines and coordinate annotations throughout
|
||||
- Maintain tension between large impactful headers and small precise parameters
|
||||
- Lab manual aesthetic: mix of microscopic details and macroscopic data
|
||||
|
||||
## Avoid
|
||||
|
||||
- Cute or cartoonish doodles
|
||||
- Soft pastels or generic textures
|
||||
- Empty white space
|
||||
- Flat vector stock icons
|
||||
- Organic or hand-drawn imperfections
|
||||
|
||||
## Best For
|
||||
|
||||
Technical product guides, specification comparisons, precision-focused data visualization, engineering-adjacent content
|
||||
@@ -0,0 +1,47 @@
|
||||
# retro-pop-grid
|
||||
|
||||
1970s retro pop art with strict Swiss international grid, thick black outlines, and flat color blocks.
|
||||
|
||||
## Color Palette
|
||||
|
||||
- Background: Warm vintage cream/beige (#F5F0E6)
|
||||
- Flat accents: Salmon pink, sky blue, mustard yellow, mint green—all muted retro tones
|
||||
- Contrast blocks: Solid pure black (#000000) and solid pure white (#FFFFFF) used strategically for extreme contrast
|
||||
- Line art and outlines: Solid thick black
|
||||
|
||||
## Visual Elements
|
||||
|
||||
- Uniform thick black outlines on all illustrations, text boxes, and grid dividers
|
||||
- Pure 2D flat vector aesthetic with subtle screen print texture
|
||||
- Strict Swiss international grid: poster divided into square and rectangular cells by thick black lines
|
||||
- Black-background cells with white text for warnings or key categories (inverted contrast)
|
||||
- Geometric fill patterns in empty cells: checkerboards, diagonal lines, dots
|
||||
- Flat abstract symbols, warning signs, keyholes, stars, arrows
|
||||
- Vintage comic-style smiley/frowny faces for quality indicators
|
||||
- Colored cells used for breathing room—some with minimal/no content
|
||||
|
||||
## Typography
|
||||
|
||||
- Headers: Bold brutalist or retro thick display fonts, high legibility
|
||||
- Body: Clean sans-serif, structured typographic alignment
|
||||
- Decorative English text acceptable for stylistic labels ("WARNING", "INFO", "BEST")
|
||||
- All content text in specified language
|
||||
|
||||
## Style Enforcement
|
||||
|
||||
- Absolutely no gradients, shading, drop shadows, or 3D effects
|
||||
- Everything anchored in grid cells—no floating or unorganized elements
|
||||
- Maintain 1970s retro pop art and underground comic illustration feel
|
||||
- Visual density balanced with rhythmic grid—some cells intentionally sparse for contrast
|
||||
|
||||
## Avoid
|
||||
|
||||
- 3D rendering, realistic details, gradients, soft shadows
|
||||
- Soft, thin, or sketch-like pencil lines
|
||||
- Free-flowing, unorganized, or floating layouts (everything must be grid-anchored)
|
||||
- Pure white background canvas
|
||||
- Organic or hand-drawn imperfections
|
||||
|
||||
## Best For
|
||||
|
||||
Trendy product guides, design-conscious content, visually striking comparisons, content targeting design-savvy audiences, bold social media posts
|
||||
@@ -68,9 +68,24 @@ Use `AskUserQuestion` to ask whether to format first. Formatting can fix:
|
||||
|
||||
**If user declines**: Continue with original file.
|
||||
|
||||
### Step 1: Confirm Theme
|
||||
### Step 1: Determine Theme
|
||||
|
||||
Before converting, use AskUserQuestion to confirm the theme (unless user already specified):
|
||||
**Theme resolution order** (first match wins):
|
||||
1. User explicitly specified theme (CLI `--theme` or conversation)
|
||||
2. EXTEND.md `default_theme` (this skill's own EXTEND.md, checked in Step 0)
|
||||
3. `baoyu-post-to-wechat` EXTEND.md `default_theme` (cross-skill fallback)
|
||||
4. If none found → use AskUserQuestion to confirm
|
||||
|
||||
**Cross-skill EXTEND.md check** (only if this skill's EXTEND.md has no `default_theme`):
|
||||
|
||||
```bash
|
||||
# Check baoyu-post-to-wechat EXTEND.md for default_theme
|
||||
test -f "$HOME/.baoyu-skills/baoyu-post-to-wechat/EXTEND.md" && grep -o 'default_theme:.*' "$HOME/.baoyu-skills/baoyu-post-to-wechat/EXTEND.md"
|
||||
```
|
||||
|
||||
**If theme is resolved from EXTEND.md**: Use it directly, do NOT ask the user.
|
||||
|
||||
**If no default found**: Use AskUserQuestion to confirm:
|
||||
|
||||
| Theme | Description |
|
||||
|-------|-------------|
|
||||
|
||||
@@ -196,20 +196,19 @@ B) Continue - provide HTML file manually
|
||||
|
||||
**Skip if**: Input is `.html` file
|
||||
|
||||
1. **Ask theme preference** (unless specified in EXTEND.md or CLI):
|
||||
1. **Resolve theme** (first match wins, do NOT ask user if resolved):
|
||||
- CLI `--theme` argument
|
||||
- EXTEND.md `default_theme` (loaded in Step 0)
|
||||
- Fallback: `default`
|
||||
|
||||
| Theme | Description |
|
||||
|-------|-------------|
|
||||
| `default` | 经典主题 - 传统排版,标题居中带底边,二级标题白字彩底 |
|
||||
| `grace` | 优雅主题 - 文字阴影,圆角卡片,精致引用块 |
|
||||
| `simple` | 简洁主题 - 现代极简风,不对称圆角,清爽留白 |
|
||||
|
||||
2. **Execute conversion** (using the discovered skill):
|
||||
2. **Execute conversion** (using the discovered skill), **always pass `--theme`**:
|
||||
|
||||
```bash
|
||||
npx -y bun ${MD_TO_HTML_SKILL_DIR}/scripts/main.ts <markdown_file> --theme <theme>
|
||||
```
|
||||
|
||||
**CRITICAL**: Always include `--theme` parameter. Never omit it, even if using `default`.
|
||||
|
||||
3. **Parse JSON output** to get: `htmlPath`, `title`, `author`, `summary`, `contentImages`
|
||||
|
||||
### Step 4: Validate Metadata
|
||||
|
||||
Reference in New Issue
Block a user