feat(codex-imagegen): cwd-drift immunity (path resolve + skip-git-repo-check)

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).
This commit is contained in:
Yelban
2026-05-21 16:25:44 +08:00
parent 9596d39e7b
commit df16bd5d1a
3 changed files with 30 additions and 12 deletions
+13 -4
View File
@@ -71,13 +71,22 @@ function parseArgs(argv: string[]): CliOptions {
}
if (!opts.outputPath) throw new GenError("invalid_args", "--image is required", false);
if (!opts.prompt && !promptFile) throw new GenError("invalid_args", "--prompt or --prompt-file required", false);
// Resolve every filesystem path to absolute up front, so behavior is
// independent of the caller's cwd. This matters when the wrapper is
// invoked from a skill running in an arbitrary working directory.
const cwd = process.cwd();
const toAbs = (p: string) => (path.isAbsolute(p) ? p : path.resolve(cwd, p));
opts.outputPath = toAbs(opts.outputPath);
if (promptFile) {
opts.prompt = ""; // will be loaded later
(opts as any).__promptFile = promptFile;
}
if (!path.isAbsolute(opts.outputPath)) {
opts.outputPath = path.resolve(process.cwd(), opts.outputPath);
(opts as any).__promptFile = toAbs(promptFile);
}
opts.refImages = opts.refImages.map(toAbs);
if (opts.cacheDir) opts.cacheDir = toAbs(opts.cacheDir);
if (opts.logFile) opts.logFile = toAbs(opts.logFile);
return opts;
}
+10 -1
View File
@@ -16,7 +16,16 @@ export async function runCodexExec(input: SpawnInput): Promise<CodexRunResult> {
const logDir = await mkdtemp(path.join(tmpdir(), "codex-imggen-"));
const rawLogPath = path.join(logDir, "stream.jsonl");
const args = ["exec", "--json", "--sandbox", "danger-full-access"];
// --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);
}