mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-24 02:59:47 +08:00
refactor(codex-imagegen): extract backend to packages/baoyu-codex-imagegen workspace
Move the codex-imagegen wrapper out of scripts/ into its own workspace package alongside baoyu-md, baoyu-chrome-cdp, and baoyu-fetch. Drop the bash shim — src/main.ts now carries a `#\!/usr/bin/env bun` shebang and serves as the sole entrypoint. Add scripts/sync-codex-imagegen.sh so skills/baoyu-image-gen/scripts/codex-imagegen/ can be regenerated from packages/baoyu-codex-imagegen/src/ for skill self-containment. Update CLAUDE.md, docs/codex-imagegen-backend.md, and the CI workflow paths accordingly.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user