From 23fac03691c6fb3aa47d78023cf3ab1b72a060e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jim=20Liu=20=E5=AE=9D=E7=8E=89?= Date: Mon, 25 May 2026 00:18:16 -0500 Subject: [PATCH] refactor(codex-imagegen): extract backend to packages/baoyu-codex-imagegen workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .github/workflows/codex-imagegen-tests.yml | 12 +-- CLAUDE.md | 10 +- docs/codex-imagegen-backend.md | 64 ++++++----- packages/baoyu-codex-imagegen/README.md | 102 ++++++++++++++++++ packages/baoyu-codex-imagegen/package.json | 39 +++++++ .../baoyu-codex-imagegen/src}/cache.test.ts | 0 .../baoyu-codex-imagegen/src}/cache.ts | 0 .../baoyu-codex-imagegen/src}/logger.ts | 0 .../baoyu-codex-imagegen/src}/main.ts | 1 + .../baoyu-codex-imagegen/src}/parser.test.ts | 0 .../baoyu-codex-imagegen/src}/parser.ts | 0 .../baoyu-codex-imagegen/src}/spawn.ts | 0 .../baoyu-codex-imagegen/src}/types.ts | 0 .../src}/validator.test.ts | 0 .../baoyu-codex-imagegen/src}/validator.ts | 0 scripts/codex-imagegen.sh | 19 ---- scripts/sync-codex-imagegen.sh | 59 ++++++++++ 17 files changed, 252 insertions(+), 54 deletions(-) create mode 100644 packages/baoyu-codex-imagegen/README.md create mode 100644 packages/baoyu-codex-imagegen/package.json rename {scripts/codex-imagegen => packages/baoyu-codex-imagegen/src}/cache.test.ts (100%) rename {scripts/codex-imagegen => packages/baoyu-codex-imagegen/src}/cache.ts (100%) rename {scripts/codex-imagegen => packages/baoyu-codex-imagegen/src}/logger.ts (100%) rename {scripts/codex-imagegen => packages/baoyu-codex-imagegen/src}/main.ts (99%) mode change 100644 => 100755 rename {scripts/codex-imagegen => packages/baoyu-codex-imagegen/src}/parser.test.ts (100%) rename {scripts/codex-imagegen => packages/baoyu-codex-imagegen/src}/parser.ts (100%) rename {scripts/codex-imagegen => packages/baoyu-codex-imagegen/src}/spawn.ts (100%) rename {scripts/codex-imagegen => packages/baoyu-codex-imagegen/src}/types.ts (100%) rename {scripts/codex-imagegen => packages/baoyu-codex-imagegen/src}/validator.test.ts (100%) rename {scripts/codex-imagegen => packages/baoyu-codex-imagegen/src}/validator.ts (100%) delete mode 100755 scripts/codex-imagegen.sh create mode 100755 scripts/sync-codex-imagegen.sh diff --git a/.github/workflows/codex-imagegen-tests.yml b/.github/workflows/codex-imagegen-tests.yml index eb723b4..2b60a0d 100644 --- a/.github/workflows/codex-imagegen-tests.yml +++ b/.github/workflows/codex-imagegen-tests.yml @@ -3,13 +3,11 @@ name: codex-imagegen tests on: push: paths: - - 'scripts/codex-imagegen/**' - - 'scripts/codex-imagegen.sh' + - 'packages/baoyu-codex-imagegen/**' - '.github/workflows/codex-imagegen-tests.yml' pull_request: paths: - - 'scripts/codex-imagegen/**' - - 'scripts/codex-imagegen.sh' + - 'packages/baoyu-codex-imagegen/**' - '.github/workflows/codex-imagegen-tests.yml' workflow_dispatch: @@ -30,11 +28,11 @@ jobs: run: bun --version - name: Run unit tests - working-directory: scripts/codex-imagegen + working-directory: packages/baoyu-codex-imagegen run: bun test - name: Bundle smoke test (catches import/syntax errors) - run: bun build --target=node scripts/codex-imagegen/main.ts --outfile /tmp/main-build.js + run: bun build --target=node packages/baoyu-codex-imagegen/src/main.ts --outfile /tmp/main-build.js - name: Help output smoke test - run: bun scripts/codex-imagegen/main.ts --help + run: bun packages/baoyu-codex-imagegen/src/main.ts --help diff --git a/CLAUDE.md b/CLAUDE.md index fb7f606..0b63791 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,19 +68,21 @@ Skills that render images MUST declare the backend-selection convention **inline ### `codex-imagegen` Backend -A backend for non-Codex runtimes (e.g., Claude Code) that generates images by spawning `codex exec --json --sandbox danger-full-access` and delegating to Codex CLI's built-in `image_gen` tool. Uses the user's Codex subscription — no `OPENAI_API_KEY` required. +A backend for non-Codex runtimes (e.g., Claude Code) that generates images by spawning `codex exec --json --sandbox danger-full-access` and delegating to Codex CLI's built-in `image_gen` tool. Uses the user's Codex subscription — no `OPENAI_API_KEY` required. Lives under [packages/baoyu-codex-imagegen](packages/baoyu-codex-imagegen) so it follows the same workspace layout as other shared packages. -Invoke via: +Invoke via (TS entrypoint with `#!/usr/bin/env bun` shebang): ```bash -./scripts/codex-imagegen.sh \ +bun packages/baoyu-codex-imagegen/src/main.ts \ --image \ --prompt-file prompts/01-cover.md \ --aspect 16:9 \ --cache-dir ~/.cache/baoyu-codex-imagegen ``` -Stdout emits a single JSON line: `{"status":"ok","path":...,"bytes":N,...}`. On failure, `{"status":"error","error_kind":...}`. Skills route here by setting `preferred_image_backend: codex-imagegen` in EXTEND.md. Full reference: [docs/codex-imagegen-backend.md](docs/codex-imagegen-backend.md). +Without bun installed: `npx -y bun packages/baoyu-codex-imagegen/src/main.ts …`. + +Stdout emits a single JSON line: `{"status":"ok","path":...,"bytes":N,...}`. On failure, `{"status":"error","error_kind":...}`. Skills route here by setting `preferred_image_backend: codex-imagegen` in EXTEND.md, or by running `baoyu-image-gen --provider codex-cli` (which spawns the same wrapper internally). Full reference: [docs/codex-imagegen-backend.md](docs/codex-imagegen-backend.md). ## Release Process diff --git a/docs/codex-imagegen-backend.md b/docs/codex-imagegen-backend.md index fc18da0..ccd8151 100644 --- a/docs/codex-imagegen-backend.md +++ b/docs/codex-imagegen-backend.md @@ -35,13 +35,13 @@ codex login # signs in with your OpenAI account (subscription) codex --version # confirm >= 0.130 ``` -`bun` is preferred for running the wrapper. On macOS: +`bun` is required for running the wrapper. On macOS: ```bash brew install oven-sh/bun/bun ``` -If `bun` is missing, the shell entrypoint falls back to `npx -y bun`. +If `bun` is not on `PATH`, fall back to `npx -y bun packages/baoyu-codex-imagegen/src/main.ts …`. ## Usage @@ -49,20 +49,32 @@ If `bun` is missing, the shell entrypoint falls back to `npx -y bun`. ```bash # Inline prompt -./scripts/codex-imagegen.sh \ +bun packages/baoyu-codex-imagegen/src/main.ts \ --image /tmp/cat.png \ --prompt "A friendly orange cat, watercolor" # Prompt from file -./scripts/codex-imagegen.sh \ +bun packages/baoyu-codex-imagegen/src/main.ts \ --image cover.png \ --prompt-file prompts/01-cover.md \ --aspect 16:9 # Verbose mode for debugging -./scripts/codex-imagegen.sh -v --image dog.png --prompt "A corgi" --aspect 1:1 +bun packages/baoyu-codex-imagegen/src/main.ts -v --image dog.png --prompt "A corgi" --aspect 1:1 ``` +### Through `baoyu-image-gen` + +```bash +${BUN_X} skills/baoyu-image-gen/scripts/main.ts \ + --provider codex-cli \ + --prompt "A friendly orange cat, watercolor" \ + --image /tmp/cat.png \ + --ar 1:1 +``` + +The `codex-cli` provider spawns the bundled `codex-imagegen` TS entrypoint internally and surfaces its retry/cache machinery through baoyu-image-gen's standard CLI + batch flow. + On success, stdout emits a single JSON line: ```json @@ -80,7 +92,7 @@ Image-generating skills (e.g., `baoyu-cover-image`, `baoyu-article-illustrator`) preferred_image_backend: codex-imagegen ``` -When the LLM runs the skill, it reads the preference and — guided by the `### codex-imagegen Backend` section in `CLAUDE.md` — invokes `scripts/codex-imagegen.sh`. +When the LLM runs the skill, it reads the preference and — guided by the `### codex-imagegen Backend` section in `CLAUDE.md` — invokes `bun packages/baoyu-codex-imagegen/src/main.ts`. > **Note**: The integration is mediated by the LLM reading `CLAUDE.md`. It is not a hard binding. If a skill does not route to the backend automatically, mentioning it explicitly in the prompt works. @@ -191,24 +203,26 @@ On failure, exit code is `1` and the JSON contains `error` and `error_kind`: ## Architecture ``` -scripts/codex-imagegen.sh # thin bash entrypoint -scripts/codex-imagegen/ -├── main.ts # parseArgs → cache → lock → retry loop → emit JSON -├── types.ts # CliOptions, GenerateResult, GenError, ErrorKind -├── spawn.ts # spawn codex exec --json --sandbox danger-full-access -├── parser.ts # parse JSONL event stream → toolCalls, usage, thread_id -├── validator.ts # verify image_gen invocation + PNG magic + file size -├── cache.ts # cacheKey(sha256), FileLock, lookup/store -├── logger.ts # JsonLogger (verbose stderr + JSONL file) -├── parser.test.ts -├── cache.test.ts -└── validator.test.ts +packages/baoyu-codex-imagegen/ +├── src/ +│ ├── main.ts # parseArgs → cache → lock → retry loop → emit JSON (`#!/usr/bin/env bun`) +│ ├── types.ts # CliOptions, GenerateResult, GenError, ErrorKind +│ ├── spawn.ts # spawn codex exec --json --sandbox danger-full-access +│ ├── parser.ts # parse JSONL event stream → toolCalls, usage, thread_id +│ ├── validator.ts # verify image_gen invocation + PNG magic + file size +│ ├── cache.ts # cacheKey(sha256), FileLock, lookup/store +│ ├── logger.ts # JsonLogger (verbose stderr + JSONL file) +│ ├── parser.test.ts +│ ├── cache.test.ts +│ └── validator.test.ts +├── package.json # workspace package: `bin` → `src/main.ts`, no build step +└── README.md ``` Run tests: ```bash -cd scripts/codex-imagegen && bun test +cd packages/baoyu-codex-imagegen && bun test ``` ## Internal Flow @@ -216,7 +230,7 @@ cd scripts/codex-imagegen && bun test ```mermaid flowchart LR CC[Claude Code / any caller] - WRAPPER[scripts/codex-imagegen.sh] + WRAPPER[bun packages/baoyu-codex-imagegen/
src/main.ts] CODEX["codex exec --json
--sandbox danger-full-access"] AGENT[Codex agent] TOOL[image_gen built-in tool] @@ -239,18 +253,20 @@ flowchart LR ## Design Decisions -1. **Bash entrypoint + TypeScript implementation** — the shell wrapper picks the runtime (`bun` preferred, falling back to `npx -y bun`); TypeScript handles the orchestration, parsing, retry, cache, and logging. This mirrors the project's existing `scripts/*.mjs` and `skills//scripts/main.ts` pattern. +1. **Pure TypeScript entrypoint** — `src/main.ts` carries a `#!/usr/bin/env bun` shebang and is the sole entry. There is no shell shim: callers either invoke `bun src/main.ts …` directly, run the file as an executable (when `bun` is on `PATH`), or fall back to `npx -y bun src/main.ts …`. This matches the project's `skills//scripts/main.ts` convention. 2. **`--sandbox danger-full-access`** — necessary so the spawned agent can `cp`/`mv` the rendered PNG out of `$CODEX_HOME/generated_images/` to the user-specified path. Standard sandboxes block this. 3. **Parse the JSONL event stream** — the final `agent_message` and intermediate `command_execution` events let the wrapper verify what actually happened (was `image_gen` called? did `cp` reach the right destination?), which is far more reliable than scraping freeform stdout. -4. **Infrastructure, not a skill** — this backend is a CLI utility that skills route to via `preferred_image_backend`. It belongs in `scripts/`, not `skills/`, because it has no `SKILL.md` and is never loaded directly by an agent. +4. **Shared package, not a skill** — this backend is a CLI utility that skills route to via `preferred_image_backend` and that `baoyu-image-gen --provider codex-cli` spawns internally. It lives under `packages/` alongside the other shared workspaces (`baoyu-md`, `baoyu-chrome-cdp`, `baoyu-fetch`) because it has no `SKILL.md` and is never loaded directly by an agent. 5. **File lock instead of internal queue** — keeps the implementation small and works across multiple shell sessions or processes invoking the same wrapper concurrently. ## Related Files | File | Role | |------|------| -| `scripts/codex-imagegen.sh` | CLI entrypoint | -| `scripts/codex-imagegen/` | TypeScript implementation | +| `packages/baoyu-codex-imagegen/src/main.ts` | TypeScript CLI entrypoint (`#!/usr/bin/env bun`) | +| `packages/baoyu-codex-imagegen/src/` | TypeScript implementation | +| `packages/baoyu-codex-imagegen/package.json` | Workspace manifest | +| `skills/baoyu-image-gen/scripts/providers/codex-cli.ts` | Provider adapter that lets `baoyu-image-gen --provider codex-cli` spawn this wrapper | | `docs/codex-imagegen-backend.md` | This document | | `CLAUDE.md` | Tells LLMs how to invoke this backend | | `.github/workflows/codex-imagegen-tests.yml` | CI unit tests | diff --git a/packages/baoyu-codex-imagegen/README.md b/packages/baoyu-codex-imagegen/README.md new file mode 100644 index 0000000..e9261c2 --- /dev/null +++ b/packages/baoyu-codex-imagegen/README.md @@ -0,0 +1,102 @@ +# baoyu-codex-imagegen + +Generate images via Codex CLI's built-in `image_gen` tool from non-Codex runtimes (e.g., Claude Code). The wrapper spawns `codex exec --json` and lets the user's existing Codex subscription drive image generation — **no `OPENAI_API_KEY` required**. + +This package implements the `preferred_image_backend: codex-imagegen` config key referenced across the `baoyu-skills` plugin and is the engine behind `baoyu-image-gen --provider codex-cli`. + +## Layout + +``` +packages/baoyu-codex-imagegen/ +├── src/ +│ ├── main.ts # CLI orchestrator (executable via `#!/usr/bin/env bun`) +│ ├── spawn.ts # codex exec child-process wrapper +│ ├── parser.ts # JSONL event-stream parser +│ ├── validator.ts # Output PNG / image_gen-invocation verification +│ ├── cache.ts # SHA256 idempotency cache + file lock +│ ├── logger.ts # Structured JSONL logging +│ ├── types.ts # Shared types and `GenError` +│ └── *.test.ts # Bun unit tests +└── package.json # `bin` points to `src/main.ts` +``` + +## Prerequisites + +```bash +npm install -g @openai/codex +codex login # signs in with your OpenAI account (subscription) +codex --version # confirm >= 0.130 +``` + +`bun` is required for running the wrapper: + +```bash +brew install oven-sh/bun/bun +``` + +If `bun` is not on `PATH`, `npx -y bun src/main.ts …` works as a fallback. + +## Usage + +```bash +# Inline prompt (executes via shebang once bun is on PATH) +./src/main.ts \ + --image /tmp/cat.png \ + --prompt "A friendly orange cat, watercolor" + +# Or invoke bun explicitly +bun src/main.ts \ + --image cover.png \ + --prompt-file prompts/01-cover.md \ + --aspect 16:9 \ + --cache-dir ~/.cache/baoyu-codex-imagegen + +# Without bun installed +npx -y bun src/main.ts --image cover.png --prompt "..." +``` + +Stdout emits a single JSON line: + +```json +{"status":"ok","path":"…","bytes":1234567,"elapsed_seconds":62,"thread_id":"…","attempts":1,"cached":false,"usage":{…}} +``` + +On failure: + +```json +{"status":"error","path":"…","bytes":0,"error":"…","error_kind":"timeout"} +``` + +`error_kind` values: `codex_not_installed`, `invalid_args`, `prompt_file_missing`, `spawn_failed`, `timeout`, `no_image_gen_tool_use`, `output_missing`, `invalid_png`, `agent_refused`, `lock_busy`. + +## Options + +| Flag | Description | +|---|---| +| `--image ` | Output PNG path (required) | +| `--prompt ` | Prompt text | +| `--prompt-file ` | Read prompt from file (mutually exclusive with `--prompt`) | +| `--aspect ` | Aspect ratio (`1:1`, `16:9`, `9:16`, `4:3`, `2.35:1`). Default: `1:1` | +| `--ref ` | Reference image (repeatable) | +| `--timeout ` | Codex exec timeout in ms. Default: `300000` | +| `--retries ` | Retry attempts on retryable errors. Default: `2` | +| `--retry-delay ` | Base retry delay (exponential). Default: `1500` | +| `--cache-dir ` | Enable idempotency cache. Disabled by default. | +| `--log-file ` | Append structured JSONL log | +| `-v, --verbose` | Verbose stderr logging | +| `-h, --help` | Show help | + +## Test + +```bash +cd packages/baoyu-codex-imagegen +bun test +``` + +## Trade-offs + +- 5–10× slower than direct OpenAI API calls (except on cache hits) +- Uses your Codex subscription — programmatic use of `codex exec` falls into the same terms as interactive use +- Requires `codex` CLI and active login session + +See [`docs/codex-imagegen-backend.md`](../../docs/codex-imagegen-backend.md) for the full background. diff --git a/packages/baoyu-codex-imagegen/package.json b/packages/baoyu-codex-imagegen/package.json new file mode 100644 index 0000000..cb5411c --- /dev/null +++ b/packages/baoyu-codex-imagegen/package.json @@ -0,0 +1,39 @@ +{ + "name": "baoyu-codex-imagegen", + "version": "0.1.0", + "type": "module", + "description": "Generate images via Codex CLI's built-in image_gen tool from non-Codex runtimes (Claude Code, Hermes, …).", + "bin": { + "codex-imagegen": "./src/main.ts" + }, + "files": [ + "src/**/*.ts", + "!src/**/*.test.ts" + ], + "exports": { + ".": { + "types": "./src/main.ts", + "default": "./src/main.ts" + }, + "./src/*": "./src/*" + }, + "scripts": { + "test": "bun test", + "smoke": "bun src/main.ts --help" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/JimLiu/baoyu-skills.git", + "directory": "packages/baoyu-codex-imagegen" + }, + "bugs": { + "url": "https://github.com/JimLiu/baoyu-skills/issues" + }, + "homepage": "https://github.com/JimLiu/baoyu-skills/tree/main/packages/baoyu-codex-imagegen#readme", + "publishConfig": { + "access": "public" + }, + "engines": { + "bun": ">=1.2.0" + } +} diff --git a/scripts/codex-imagegen/cache.test.ts b/packages/baoyu-codex-imagegen/src/cache.test.ts similarity index 100% rename from scripts/codex-imagegen/cache.test.ts rename to packages/baoyu-codex-imagegen/src/cache.test.ts diff --git a/scripts/codex-imagegen/cache.ts b/packages/baoyu-codex-imagegen/src/cache.ts similarity index 100% rename from scripts/codex-imagegen/cache.ts rename to packages/baoyu-codex-imagegen/src/cache.ts diff --git a/scripts/codex-imagegen/logger.ts b/packages/baoyu-codex-imagegen/src/logger.ts similarity index 100% rename from scripts/codex-imagegen/logger.ts rename to packages/baoyu-codex-imagegen/src/logger.ts diff --git a/scripts/codex-imagegen/main.ts b/packages/baoyu-codex-imagegen/src/main.ts old mode 100644 new mode 100755 similarity index 99% rename from scripts/codex-imagegen/main.ts rename to packages/baoyu-codex-imagegen/src/main.ts index 39f36fd..ac66035 --- a/scripts/codex-imagegen/main.ts +++ b/packages/baoyu-codex-imagegen/src/main.ts @@ -1,3 +1,4 @@ +#!/usr/bin/env bun import { readFile, mkdir, copyFile, stat } from "node:fs/promises"; import { homedir } from "node:os"; import path from "node:path"; diff --git a/scripts/codex-imagegen/parser.test.ts b/packages/baoyu-codex-imagegen/src/parser.test.ts similarity index 100% rename from scripts/codex-imagegen/parser.test.ts rename to packages/baoyu-codex-imagegen/src/parser.test.ts diff --git a/scripts/codex-imagegen/parser.ts b/packages/baoyu-codex-imagegen/src/parser.ts similarity index 100% rename from scripts/codex-imagegen/parser.ts rename to packages/baoyu-codex-imagegen/src/parser.ts diff --git a/scripts/codex-imagegen/spawn.ts b/packages/baoyu-codex-imagegen/src/spawn.ts similarity index 100% rename from scripts/codex-imagegen/spawn.ts rename to packages/baoyu-codex-imagegen/src/spawn.ts diff --git a/scripts/codex-imagegen/types.ts b/packages/baoyu-codex-imagegen/src/types.ts similarity index 100% rename from scripts/codex-imagegen/types.ts rename to packages/baoyu-codex-imagegen/src/types.ts diff --git a/scripts/codex-imagegen/validator.test.ts b/packages/baoyu-codex-imagegen/src/validator.test.ts similarity index 100% rename from scripts/codex-imagegen/validator.test.ts rename to packages/baoyu-codex-imagegen/src/validator.test.ts diff --git a/scripts/codex-imagegen/validator.ts b/packages/baoyu-codex-imagegen/src/validator.ts similarity index 100% rename from scripts/codex-imagegen/validator.ts rename to packages/baoyu-codex-imagegen/src/validator.ts diff --git a/scripts/codex-imagegen.sh b/scripts/codex-imagegen.sh deleted file mode 100755 index e575799..0000000 --- a/scripts/codex-imagegen.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -# codex-imagegen: generate images via Codex CLI's built-in image_gen tool -# Thin shell wrapper — implementation in codex-imagegen/main.ts (Bun TypeScript) -# -# Usage: ./codex-imagegen.sh --help - -set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -if command -v bun &>/dev/null; then - BUN_X="bun" -elif command -v npx &>/dev/null; then - BUN_X="npx -y bun" -else - echo "Error: bun or npx required. Install: brew install oven-sh/bun/bun" >&2 - exit 1 -fi - -exec $BUN_X "$SCRIPT_DIR/codex-imagegen/main.ts" "$@" diff --git a/scripts/sync-codex-imagegen.sh b/scripts/sync-codex-imagegen.sh new file mode 100755 index 0000000..30665be --- /dev/null +++ b/scripts/sync-codex-imagegen.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# Sync packages/baoyu-codex-imagegen/src/*.ts (excluding tests) into +# skills/baoyu-image-gen/scripts/codex-imagegen/ so the skill stays +# self-contained (no `../../../../packages/...` lookups at runtime). +# +# Run this whenever packages/baoyu-codex-imagegen/src/ changes, +# and always before tagging a release. + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SRC_DIR="$REPO_ROOT/packages/baoyu-codex-imagegen/src" +DST_DIR="$REPO_ROOT/skills/baoyu-image-gen/scripts/codex-imagegen" + +if [[ ! -d "$SRC_DIR" ]]; then + echo "Error: source dir missing: $SRC_DIR" >&2 + exit 1 +fi + +mkdir -p "$DST_DIR" + +changed=0 +for f in cache.ts logger.ts main.ts parser.ts spawn.ts types.ts validator.ts; do + src="$SRC_DIR/$f" + dst="$DST_DIR/$f" + if [[ ! -f "$src" ]]; then + echo "Error: missing source file: $src" >&2 + exit 1 + fi + if [[ ! -f "$dst" ]] || ! cmp -s "$src" "$dst"; then + cp "$src" "$dst" + echo "synced: $f" + changed=$((changed + 1)) + fi +done + +chmod +x "$DST_DIR/main.ts" + +# Drop any stale .ts files in DST that don't exist in SRC. +for dst in "$DST_DIR"/*.ts; do + [[ -e "$dst" ]] || continue + name="$(basename "$dst")" + case "$name" in + *.test.ts) rm -f "$dst"; echo "removed test artifact: $name" ;; + *) + if [[ ! -f "$SRC_DIR/$name" ]]; then + rm -f "$dst" + echo "removed stale: $name" + changed=$((changed + 1)) + fi + ;; + esac +done + +if [[ "$changed" -eq 0 ]]; then + echo "codex-imagegen sync: up to date" +else + echo "codex-imagegen sync: $changed file(s) updated" +fi