Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions BINARY_DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ These tools are required for common command paths.

### 1.1 `image-processing` runtime policy

- `convert --from-svg` and `svg-validate`:
- Rust-backed (`usvg`/`resvg` + Rust image encoding path).
- `convert --in` and `svg-validate`:
- Rust-backed (`image` decode/encode for raster inputs, `usvg`/`resvg` for SVG inputs).
- No external runtime binary requirement.

## 2. Runtime Dependencies (Optional / Degradation Paths)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Each crate is either a standalone CLI binary or a shared library used across the
- [crates/fzf-cli](crates/fzf-cli): Interactive `fzf` toolbox for files, Git, processes, ports, and shell history.
- [crates/memo-cli](crates/memo-cli): Capture-first memo workflow CLI with agent enrichment loop (`add`, `list`, `search`, `report`,
`fetch`, `apply`).
- [crates/image-processing](crates/image-processing): Batch image transformation CLI (resize/crop/optimize) with JSON/report outputs.
- [crates/image-processing](crates/image-processing): Image conversion CLI for `svg/png/webp/jpg` plus SVG validation with JSON/report outputs.
- [crates/screen-record](crates/screen-record): macOS ScreenCaptureKit + Linux (X11) recorder for a single window or display with optional
audio.

Expand Down
4 changes: 4 additions & 0 deletions crates/codex-cli/tests/agent_prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fn agent_prompt_requires_dangerous_mode() {
let _path = prepend_path(&lock, stub.path());

let _danger = EnvGuard::remove(&lock, "CODEX_ALLOW_DANGEROUS_ENABLED");
let _ephemeral = EnvGuard::remove(&lock, "CODEX_CLI_EPHEMERAL_ENABLED");
let _model = EnvGuard::set(&lock, "CODEX_CLI_MODEL", "m");
let _reasoning = EnvGuard::set(&lock, "CODEX_CLI_REASONING", "low");
let args_log_path = args_log.path().to_string_lossy().to_string();
Expand Down Expand Up @@ -68,6 +69,7 @@ fn agent_prompt_execs_codex_with_expected_args() {
let _path = prepend_path(&lock, stub.path());

let _danger = EnvGuard::set(&lock, "CODEX_ALLOW_DANGEROUS_ENABLED", "true");
let _ephemeral = EnvGuard::remove(&lock, "CODEX_CLI_EPHEMERAL_ENABLED");
let _model = EnvGuard::set(&lock, "CODEX_CLI_MODEL", "gpt-test");
let _reasoning = EnvGuard::set(&lock, "CODEX_CLI_REASONING", "high");
let args_log_path = args_log.path().to_string_lossy().to_string();
Expand Down Expand Up @@ -114,6 +116,7 @@ fn agent_prompt_execs_codex_with_ephemeral_arg_when_requested() {
let _path = prepend_path(&lock, stub.path());

let _danger = EnvGuard::set(&lock, "CODEX_ALLOW_DANGEROUS_ENABLED", "true");
let _ephemeral = EnvGuard::remove(&lock, "CODEX_CLI_EPHEMERAL_ENABLED");
let _model = EnvGuard::set(&lock, "CODEX_CLI_MODEL", "gpt-test");
let _reasoning = EnvGuard::set(&lock, "CODEX_CLI_REASONING", "high");
let args_log_path = args_log.path().to_string_lossy().to_string();
Expand Down Expand Up @@ -161,6 +164,7 @@ fn agent_prompt_reads_stdin_when_no_args() {
let _path = prepend_path(&lock, stub.path());

let _danger = EnvGuard::set(&lock, "CODEX_ALLOW_DANGEROUS_ENABLED", "true");
let _ephemeral = EnvGuard::remove(&lock, "CODEX_CLI_EPHEMERAL_ENABLED");
let _model = EnvGuard::set(&lock, "CODEX_CLI_MODEL", "m");
let _reasoning = EnvGuard::set(&lock, "CODEX_CLI_REASONING", "medium");
let args_log_path = args_log.path().to_string_lossy().to_string();
Expand Down
10 changes: 7 additions & 3 deletions crates/codex-cli/tests/agent_templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn codex_cli_bin() -> PathBuf {
}

fn run(args: &[&str], vars: &[(&str, &str)]) -> CmdOutput {
let mut options = CmdOptions::default();
let mut options = CmdOptions::default().with_env_remove("CODEX_CLI_EPHEMERAL_ENABLED");
for (key, value) in vars {
options = options.with_env(key, value);
}
Expand All @@ -19,7 +19,9 @@ fn run(args: &[&str], vars: &[(&str, &str)]) -> CmdOutput {
}

fn run_with_path_prepend(args: &[&str], vars: &[(&str, &str)], path_prepend: &Path) -> CmdOutput {
let mut options = CmdOptions::default().with_path_prepend(path_prepend);
let mut options = CmdOptions::default()
.with_env_remove("CODEX_CLI_EPHEMERAL_ENABLED")
.with_path_prepend(path_prepend);
for (key, value) in vars {
options = options.with_env(key, value);
}
Expand All @@ -28,7 +30,9 @@ fn run_with_path_prepend(args: &[&str], vars: &[(&str, &str)], path_prepend: &Pa
}

fn run_with_stdin(args: &[&str], vars: &[(&str, &str)], stdin: &str) -> CmdOutput {
let mut options = CmdOptions::default().with_stdin_str(stdin);
let mut options = CmdOptions::default()
.with_env_remove("CODEX_CLI_EPHEMERAL_ENABLED")
.with_stdin_str(stdin);
for (key, value) in vars {
options = options.with_env(key, value);
}
Expand Down
1 change: 1 addition & 0 deletions crates/codex-cli/tests/runtime_exec_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ done

let _path = prepend_path(&lock, stub.path());
let _danger = EnvGuard::set(&lock, "CODEX_ALLOW_DANGEROUS_ENABLED", "true");
let _ephemeral = EnvGuard::remove(&lock, "CODEX_CLI_EPHEMERAL_ENABLED");
let _model = EnvGuard::set(&lock, "CODEX_CLI_MODEL", "gpt-test");
let _reason = EnvGuard::set(&lock, "CODEX_CLI_REASONING", "high");
let _argv_log = EnvGuard::set(&lock, "CODEX_TEST_ARGV_LOG", &args_log_path);
Expand Down
2 changes: 1 addition & 1 deletion crates/image-processing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ serde_json = { workspace = true }

chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
globset = "0.4"
image = { version = "0.25", default-features = false, features = ["png", "webp"] }
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp"] }
nils-common = { version = "0.6.7", path = "../nils-common", package = "nils-common" }
nils-term = { version = "0.6.7", path = "../nils-term", package = "nils-term" }
resvg = "0.47"
Expand Down
32 changes: 16 additions & 16 deletions crates/image-processing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

## Overview

`image-processing` provides a modern SVG-first flow:
`image-processing` provides a focused conversion and validation flow:

- `svg-validate --in <svg> --out <svg>`
- `convert --from-svg <path> --to png|webp|svg --out <file>`
- `convert --in <path> --to png|webp|jpg --out <file>`

`generate` is removed.

Expand All @@ -25,29 +25,29 @@ Help:
## Commands

- `svg-validate`: Validate and sanitize one SVG input into one SVG output.
- `convert`: Render trusted SVG source into `png`, `webp`, or `svg` output.
- `convert`: Convert `svg|png|jpg|jpeg|webp` input into `png`, `webp`, or `jpg` output.

## Common flags

- Input:
- `svg-validate`: `--in <path>` (exactly one)
- `convert`: `--from-svg <path>`
- `convert`: `--in <path>` (exactly one)
- Output: `--out <file>`
- Output controls: `--overwrite`, `--dry-run`, `--json`, `--report`
- Render sizing for raster output: `--width`, `--height`

## `convert --from-svg` contract (v1)
## `convert` contract

- Required: `--from-svg`, `--to png|webp|svg`, `--out <file>`.
- Forbidden: `--in`.
- `--out` extension must match `--to`.
- Optional: `--width` and `--height` for `png`/`webp` sizing.
- `--to svg` does not support `--width`/`--height`.
- Required: exactly one `--in`, `--to png|webp|jpg`, `--out <file>`.
- Supported inputs: `svg`, `png`, `jpg`, `jpeg`, `webp`.
- `--out` extension must match `--to` (`.jpeg` is accepted for `--to jpg`).
- Optional: `--width` and `--height` for raster sizing.
- `--to jpg` flattens alpha onto a white background.

## `svg-validate` contract

- Required: exactly one `--in <svg>` and `--out <svg>`.
- Forbidden: `--from-svg`, `--to`, `--width`, `--height`.
- Forbidden: `--to`, `--width`, `--height`.
- Output is deterministic for identical input.

## Examples
Expand All @@ -64,17 +64,17 @@ cargo run -p nils-image-processing -- svg-validate \

```bash
cargo run -p nils-image-processing -- convert \
--from-svg out/plan-doc-examples/llm.cleaned.svg \
--in out/plan-doc-examples/llm.cleaned.svg \
--to png \
--out out/plan-doc-examples/llm.png \
--json
```

```bash
cargo run -p nils-image-processing -- convert \
--from-svg crates/image-processing/tests/fixtures/sample-icon.svg \
--to webp \
--out out/plan-doc-examples/sample.webp \
--in crates/image-processing/tests/fixtures/sample-icon.svg \
--to jpg \
--out out/plan-doc-examples/sample.jpg \
--width 512 \
--json
```
Expand All @@ -87,7 +87,7 @@ cargo run -p nils-image-processing -- convert \

## Dependencies

- `convert --from-svg` and `svg-validate`: no external binary dependency (Rust backend).
- `convert --in` and `svg-validate`: no external binary dependency (Rust backend).

## Docs

Expand Down
8 changes: 4 additions & 4 deletions crates/image-processing/docs/runbooks/llm-svg-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

## Purpose

Use a provider-agnostic pipeline to turn user intent into policy-compliant SVG, then render with `image-processing convert --from-svg`.
Use a provider-agnostic pipeline to turn user intent into policy-compliant SVG, then render with `image-processing convert --in`.

## Contract

- `generate` is removed.
- `convert --from-svg <path>` is the canonical SVG source flow.
- `convert --in <path>` is the canonical SVG-to-raster flow.
- `svg-validate` must gate LLM output before raster export.

## Quick start
Expand All @@ -32,7 +32,7 @@ cargo run -p nils-image-processing -- svg-validate \

```bash
cargo run -p nils-image-processing -- convert \
--from-svg out/plan-llm/traffic-car.cleaned.svg \
--in out/plan-llm/traffic-car.cleaned.svg \
--to png \
--out out/plan-llm/traffic-car.png \
--json
Expand Down Expand Up @@ -64,4 +64,4 @@ Any previous `generate --preset ...` usage must migrate to:

1. intent -> SVG (LLM or hand-authored),
2. `svg-validate`,
3. `convert --from-svg`.
3. `convert --in`.
21 changes: 12 additions & 9 deletions crates/image-processing/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use clap::{ArgAction, Parser, ValueEnum};
use clap::{ArgAction, Parser, ValueEnum, ValueHint};

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
#[value(rename_all = "kebab-case")]
Expand All @@ -20,20 +20,23 @@ impl Operation {
#[command(
name = "image-processing",
version,
about = "Validate SVG inputs and convert trusted SVG to raster/vector outputs.",
after_help = "Notes:\n - convert requires --from-svg + --to png|webp|svg + --out.\n - convert supports optional --width/--height for png|webp output sizing.\n - svg-validate requires exactly one --in + --out.\n - Use --json for machine-readable output (stdout JSON only; logs go to stderr).\n"
about = "Convert SVG or raster inputs to png/webp/jpg outputs and validate SVG inputs.",
after_help = "Notes:\n - convert requires exactly one --in + --to png|webp|jpg + --out.\n - convert accepts svg|png|jpg|jpeg|webp inputs.\n - convert supports optional --width/--height for raster output sizing.\n - svg-validate requires exactly one --in + --out.\n - Use --json for machine-readable output (stdout JSON only; logs go to stderr).\n"
)]
pub struct Cli {
#[arg(value_enum)]
pub subcommand: Operation,

#[arg(long = "in", action = ArgAction::Append, default_value = None)]
#[arg(
long = "in",
action = ArgAction::Append,
default_value = None,
value_hint = ValueHint::FilePath,
help = "Input file path"
)]
pub inputs: Vec<String>,

#[arg(long = "from-svg", help = "Trusted SVG input path for convert mode")]
pub from_svg: Option<String>,

#[arg(long, help = "Output file path")]
#[arg(long, help = "Output file path", value_hint = ValueHint::FilePath)]
pub out: Option<String>,

#[arg(long, help = "Overwrite existing output file")]
Expand All @@ -45,7 +48,7 @@ pub struct Cli {
#[arg(long, help = "Print per-item processing report")]
pub report: bool,

#[arg(long = "to", help = "Output format: png, webp, or svg")]
#[arg(long = "to", help = "Output format: png, webp, or jpg")]
pub to: Option<String>,

#[arg(long, help = "Raster output width in pixels")]
Expand Down
Loading
Loading