mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-13 06:19:46 +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.
81 lines
2.2 KiB
TypeScript
81 lines
2.2 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { mkdir, readFile, writeFile, copyFile, stat } from "node:fs/promises";
|
|
import { existsSync, openSync, closeSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { setTimeout as delay } from "node:timers/promises";
|
|
|
|
export function cacheKey(prompt: string, aspect: string, refs: string[]): string {
|
|
const h = createHash("sha256");
|
|
h.update(prompt);
|
|
h.update("|");
|
|
h.update(aspect);
|
|
h.update("|");
|
|
for (const r of [...refs].sort()) h.update(r);
|
|
return h.digest("hex").slice(0, 16);
|
|
}
|
|
|
|
export async function lookupCache(cacheDir: string, key: string): Promise<string | null> {
|
|
const entry = path.join(cacheDir, `${key}.png`);
|
|
try {
|
|
const s = await stat(entry);
|
|
if (s.size > 1000) return entry;
|
|
} catch {}
|
|
return null;
|
|
}
|
|
|
|
export async function storeCache(cacheDir: string, key: string, sourcePath: string): Promise<void> {
|
|
await mkdir(cacheDir, { recursive: true });
|
|
const entry = path.join(cacheDir, `${key}.png`);
|
|
await copyFile(sourcePath, entry);
|
|
}
|
|
|
|
export class FileLock {
|
|
private fd: number | null = null;
|
|
constructor(private lockPath: string) {}
|
|
|
|
async acquire(timeoutMs = 30_000): Promise<void> {
|
|
const start = Date.now();
|
|
await mkdir(path.dirname(this.lockPath), { recursive: true });
|
|
while (Date.now() - start < timeoutMs) {
|
|
try {
|
|
this.fd = openSync(this.lockPath, "wx");
|
|
return;
|
|
} catch (e: any) {
|
|
if (e.code !== "EEXIST") throw e;
|
|
if (await this.isStale()) {
|
|
try {
|
|
await this.release(true);
|
|
} catch {}
|
|
continue;
|
|
}
|
|
await delay(200);
|
|
}
|
|
}
|
|
throw new Error(`Failed to acquire lock at ${this.lockPath} within ${timeoutMs}ms`);
|
|
}
|
|
|
|
private async isStale(): Promise<boolean> {
|
|
try {
|
|
const s = await stat(this.lockPath);
|
|
return Date.now() - s.mtimeMs > 10 * 60 * 1000;
|
|
} catch {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
async release(force = false): Promise<void> {
|
|
if (this.fd != null) {
|
|
try {
|
|
closeSync(this.fd);
|
|
} catch {}
|
|
this.fd = null;
|
|
}
|
|
if (existsSync(this.lockPath) || force) {
|
|
const { unlink } = await import("node:fs/promises");
|
|
try {
|
|
await unlink(this.lockPath);
|
|
} catch {}
|
|
}
|
|
}
|
|
}
|