mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-12 13:59:47 +08:00
df16bd5d1a
Two improvements so the wrapper works the same regardless of caller cwd or whether cwd is a git repo: 1. main.ts: resolve all filesystem path args (--prompt-file, --ref, --cache-dir, --log-file) to absolute paths up front. Previously only --image was resolved. Agents passing relative paths from arbitrary working directories (e.g. when SKILL.md is interpreted from a plugin install dir) now produce correct file lookups. 2. spawn.ts: pass --skip-git-repo-check to 'codex exec'. Without it, codex refuses to run outside a trusted directory: 'Not inside a trusted directory and --skip-git-repo-check was not specified.' This made the wrapper fail when invoked from /tmp or from a non-git project tree. 3. baoyu-cover-image/SKILL.md: explicit path resolution note — scripts/codex-imagegen.sh lives at plugin/repo root (not shell cwd-relative). From the skill base directory it is at '../../scripts/codex-imagegen.sh'. Agents should resolve to an absolute path before invoking. Verified: - 16 unit tests pass - e2e from /tmp (non-git, relative-path args) → 45s, 1.6MB PNG, status:ok Composes with the existing cover-image wiring (commit e3932e4).
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,
|
|
};
|
|
}
|