mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-12 22:09:48 +08:00
49e9c46ca4
Expose Codex CLI's built-in image_gen tool through baoyu-image-gen's
standard CLI + batch flow as a dedicated provider. The provider spawns
the bundled scripts/codex-imagegen/main.ts (synced from
packages/baoyu-codex-imagegen/src/) so the skill remains self-contained
and inherits retry, cache, file-lock, and JSONL logging. No
OPENAI_API_KEY required — uses the user's Codex subscription.
New env vars: BAOYU_CODEX_IMAGEGEN_{BIN,CACHE_DIR,TIMEOUT_MS,RETRIES,LOG_FILE}.
Hyphenated provider names now resolve under-scored env vars (e.g.,
BAOYU_IMAGE_GEN_CODEX_CLI_CONCURRENCY). codex-cli is never auto-selected
— pin via --provider or default_provider in EXTEND.md.
82 lines
2.4 KiB
TypeScript
82 lines
2.4 KiB
TypeScript
import { spawn } from "node:child_process";
|
|
import { writeFile, mkdtemp } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { GenError, type CodexRunResult } from "./types.ts";
|
|
import { parseEventStream } from "./parser.ts";
|
|
|
|
export interface SpawnInput {
|
|
instruction: string;
|
|
timeoutMs: number;
|
|
refImages?: string[];
|
|
}
|
|
|
|
export async function runCodexExec(input: SpawnInput): Promise<CodexRunResult> {
|
|
const start = Date.now();
|
|
const logDir = await mkdtemp(path.join(tmpdir(), "codex-imggen-"));
|
|
const rawLogPath = path.join(logDir, "stream.jsonl");
|
|
|
|
// --skip-git-repo-check: lets the wrapper run from non-git cwds
|
|
// (e.g. /tmp, or a skill installed under ~/.claude/plugins/...).
|
|
// Without it, codex refuses with "Not inside a trusted directory".
|
|
const args = [
|
|
"exec",
|
|
"--json",
|
|
"--sandbox",
|
|
"danger-full-access",
|
|
"--skip-git-repo-check",
|
|
];
|
|
for (const img of input.refImages ?? []) {
|
|
args.push("--image", img);
|
|
}
|
|
args.push("-");
|
|
|
|
let timedOut = false;
|
|
const child = spawn("codex", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
|
|
let stdout = "";
|
|
let stderr = "";
|
|
child.stdout.on("data", (chunk) => {
|
|
stdout += chunk.toString();
|
|
});
|
|
child.stderr.on("data", (chunk) => {
|
|
stderr += chunk.toString();
|
|
});
|
|
|
|
child.stdin.write(input.instruction);
|
|
child.stdin.end();
|
|
|
|
const timer = setTimeout(() => {
|
|
timedOut = true;
|
|
child.kill("SIGTERM");
|
|
setTimeout(() => child.kill("SIGKILL"), 2000);
|
|
}, input.timeoutMs);
|
|
|
|
const exit = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve) => {
|
|
child.on("close", (code, signal) => resolve({ code, signal }));
|
|
});
|
|
clearTimeout(timer);
|
|
|
|
await writeFile(rawLogPath, stdout + (stderr ? `\n--- stderr ---\n${stderr}` : ""));
|
|
|
|
if (timedOut) {
|
|
throw new GenError("timeout", `codex exec exceeded ${input.timeoutMs}ms (log: ${rawLogPath})`);
|
|
}
|
|
if (exit.code !== 0) {
|
|
if (stderr.includes("command not found") || stderr.includes("not found: codex")) {
|
|
throw new GenError("codex_not_installed", "codex CLI not installed", false);
|
|
}
|
|
throw new GenError(
|
|
"spawn_failed",
|
|
`codex exec exited ${exit.code} signal=${exit.signal} (log: ${rawLogPath})`,
|
|
);
|
|
}
|
|
|
|
const parsed = parseEventStream(stdout);
|
|
return {
|
|
...parsed,
|
|
rawLogPath,
|
|
durationMs: Date.now() - start,
|
|
};
|
|
}
|