Generate, run, and visually compare AI-generated interface variants in isolated Git worktrees.
demo.mp4
styles-iterator is for frontend developers and product teams using Claude Code, Codex, or Cursor Agent who want to explore several visual directions at once and compare and iterate on them easily.
styles-iterator run --count 3 --concurrency 3 "Make the app feel more premium, modern, and conversion-focused"The quoted prompt is the shared goal applied to every variant. Each variant still gets a distinct style direction from the prompt.variants presets in your config (e.g. Minimal Swiss SaaS, Neo-brutalist chaos, Dark elegant), so the three windows above explore that goal three different ways. --count walks the preset list (wrapping if it runs out); --concurrency is how many generate in parallel. To explore one style instead, give prompt.variants a single entry or run with --count 1.
Example CLI surface:
styles-iterator
Generate multiple UI variants in isolated git worktrees, run them on separate ports, and compare them visually in a local browser harness.
Usage:
styles-iterator init [--format ts|mjs] [--force]
styles-iterator doctor [--config <path>]
styles-iterator run [prompt] [--agent cursor|codex|claude] [--count 3] [--concurrency 3] [--model <model>] [--no-open] [--no-serve]
styles-iterator serve [session-id] [--config <path>] [--no-open]
styles-iterator list [--session <id>]
styles-iterator diff <variant-id> [--session <id>]
styles-iterator accept <variant-id> [--strategy cherry-pick|squash|apply] [--session <id>] [--cleanup]
styles-iterator cleanup [session-id] [--all] [--delete-branches] [--purge]
The CLI will:
- require a clean base Git worktree by default, except for the loaded
styles-iterator.config.* - create one branch and Git worktree per variant under
.styles-iterator/worktrees/ - run the configured agent against each worktree: Claude Code CLI with the cheap
haikumodel by default, Codex CLI withgpt-5.4low whenagent: "codex", or Cursor Agent whenagent: "cursor" - run configured checks such as lint and typecheck
- commit each successful variant on its own branch
- run each variant on its own local port
- open a local harness with baseline and variants side by side
- let you accept a winner with
cherry-pick,squash, or patch application
pnpm install --frozen-lockfile
pnpm build
pnpm link --globalThen verify the linked CLI:
styles-iterator --help
styles-iterator --versionTo run without linking:
node dist/cli.js --helpThis repository is ready for source installs. The npm package is not published yet, so do not use npm install -g styles-iterator until a release has been published and verified.
Requirements:
- Node.js 20.11 or newer
- pnpm 10
- Git
- Claude Code CLI available as
claude(default), Codex CLI available ascodexwhenagent: "codex", or Cursor Agent CLI available asagentwhenagent: "cursor"
Cursor Agent is optional and only needed when agent: "cursor". It is a separate tool from this package and is not installed by pnpm install. Install and authenticate it by following Cursor's official CLI documentation. After setup, make sure the agent command is on your PATH and signed in (agent status).
Notes:
- If your install only exposes the legacy
cursor-agentcommand, setcursor.bin: "cursor-agent"instyles-iterator.config.*. - For CI or other non-interactive environments, use a Cursor API key instead of browser login:
export CURSOR_API_KEY="your_api_key_here". - The
cursorshell command (the desktop app launcher) is a different tool —styles-iteratorneeds the Cursor Agent CLI.
Codex CLI is an optional generator, used when agent: "codex". Install and authenticate it by following OpenAI's official Codex CLI documentation.
For non-interactive styles-iterator run usage, complete the interactive codex sign-in first or configure API-key auth in your Codex environment.
Enable Codex in project config:
export default {
agent: "codex",
codex: {
model: "gpt-5.4",
reasoningEffort: "low",
},
};Claude Code CLI is the default generator. Install and authenticate it by following Anthropic's official Claude Code documentation. Running claude once walks you through interactive sign-in.
For CI or other non-interactive environments, set an API key instead of the interactive login: export ANTHROPIC_API_KEY="your_api_key_here".
Enable Claude in project config (it is the default, so this is only needed when another agent is configured):
export default {
agent: "claude",
claude: {
model: "haiku",
},
};styles-iterator initThis writes styles-iterator.config.ts.
The generated TypeScript config includes a local config type, so editor type-checking works even when styles-iterator is installed globally or linked from source instead of added to the target repo's dependencies.
You can edit this config without committing it before every run. The clean-worktree check ignores the loaded config file, while other uncommitted app changes still block run and accept unless you pass --allow-dirty.
Commands search for config from the current directory upward to the Git root. If a config lives in a subdirectory, agent, prepare, check, and dev-server commands run from that config directory inside each generated worktree.
For plain Node environments that cannot import TypeScript config files directly, use:
styles-iterator init --format mjsexport default {
count: 3,
concurrency: 3,
basePort: 47000,
includeBaseline: true,
agent: "claude",
cursor: {
bin: "agent",
model: "Composer 2.5",
outputFormat: "stream-json",
timeoutMs: 10 * 60_000,
},
codex: {
bin: "codex",
model: "gpt-5.4",
reasoningEffort: "low",
sandbox: "workspace-write",
approvalPolicy: "never",
timeoutMs: 10 * 60_000,
},
claude: {
bin: "claude",
model: "haiku",
outputFormat: "text",
permissionMode: "acceptEdits",
timeoutMs: 10 * 60_000,
},
app: {
prepareCommand: "pnpm install --frozen-lockfile",
devCommand: "pnpm dev --port {port}",
url: "http://127.0.0.1:{port}",
healthPath: "/",
},
checks: {
commands: ["pnpm lint", "pnpm typecheck"],
allowFailure: true,
},
routes: ["/", "/pricing", "/dashboard"],
};styles-iterator init [--format ts|mjs] [--force]
styles-iterator doctor [--config <path>]
styles-iterator run [prompt] [--agent cursor|codex|claude] [--count 3] [--concurrency 3] [--model <model>] [--no-open] [--no-serve]
styles-iterator serve [session-id] [--config <path>] [--no-open]
styles-iterator list [--session <id>]
styles-iterator diff <variant-id> [--session <id>]
styles-iterator accept <variant-id> [--strategy cherry-pick|squash|apply] [--session <id>] [--cleanup]
styles-iterator cleanup [session-id] [--all] [--delete-branches] [--purge]After styles-iterator run, the CLI creates a session under .styles-iterator/sessions/{sessionId} and one Git worktree per variant under .styles-iterator/worktrees/{sessionId}.
While a variant is generating, the selected agent is producing changes inside that variant worktree. The terminal prints changed-file summaries as the worktree changes, and raw agent output is written per variant to .styles-iterator/sessions/{sessionId}/logs/{variantId}.{claude,codex,cursor}.log.
Concurrent process prefixes are color-coded in terminals that support ANSI color. Set NO_COLOR=1 to disable color or FORCE_COLOR=1 to force it.
When generation finishes, styles-iterator guards the changed files, runs configured checks, commits each successful variant on its generated branch, starts one dev server per ready variant plus the baseline, and opens the comparison harness.
If generation succeeded but starting dev servers failed, fix app.devCommand in your config and run styles-iterator serve --session <session-id>. serve reuses the existing generated worktrees and commits, starts only the dev servers plus harness, and does not run generation again. It prints the harness URL immediately, then logs each dev server command, cwd, log file, PID, and final preview URL as servers become ready. Omitting --session resumes the latest session; resume is an alias for serve.
When agent: "cursor", the default Cursor command is equivalent to:
agent -p --trust --model "Composer 2.5" --output-format stream-json "<prompt>"--trust is included because styles-iterator runs Cursor Agent in freshly-created Git worktrees. Without it, Cursor Agent may stop on each variant and ask whether that worktree directory is trusted. This does not enable --yolo/--force.
Older Cursor Agent installs may only expose cursor-agent. To use that compatibility command:
export default {
cursor: {
bin: "cursor-agent",
},
};If your installed Cursor Agent CLI uses a different command shape, configure it explicitly:
export default {
cursor: {
command:
"agent -p --trust --model {model} --output-format {outputFormat} {prompt}",
model: "Composer 2.5",
},
};Values inserted into cursor.command are shell-quoted automatically, so multiline prompts and model names with spaces are safe in the template form above.
If you override cursor.args or cursor.command, keep --trust unless you want to approve each generated worktree interactively.
Cursor Agent launches are staggered by 1.5 seconds by default to avoid races in Cursor's shared ~/.cursor/cli-config.json. Set STYLES_ITERATOR_CURSOR_LAUNCH_STAGGER_MS=0 to disable that delay, or increase it if your Cursor install still hits config-file rename errors under high concurrency.
Supported template variables include:
{prompt}
{model}
{outputFormat}
{variantId}
{variantName}
{sessionId}
{port}
Enable Codex with:
export default {
agent: "codex",
};The default Codex command is equivalent to:
codex exec --model gpt-5.4 --config 'model_reasoning_effort="low"' --config 'approval_policy="never"' --sandbox workspace-write --color never "<prompt>"gpt-5.4 with low reasoning is the default Codex model/effort pair because it is fast and cheaper for iterative coding. Override either value in config or on the CLI:
styles-iterator run --agent codex --model gpt-5.4 --reasoning lowThe default sandbox is workspace-write, so Codex can edit the generated worktree. The default approval policy is never, because styles-iterator runs Codex non-interactively and logs output per variant. If you want Codex to ask for approvals, run Codex directly or override codex.args/codex.command for your environment.
If your installed Codex CLI uses a different command shape, configure it explicitly:
export default {
agent: "codex",
codex: {
command:
"codex exec --model {model} --config 'model_reasoning_effort=\"{reasoningEffort}\"' --config 'approval_policy=\"{approvalPolicy}\"' --sandbox {sandbox} --color never {prompt}",
},
};Values inserted into codex.command are shell-quoted automatically. Supported template variables include:
{prompt}
{model}
{reasoningEffort}
{sandbox}
{approvalPolicy}
{variantId}
{variantName}
{sessionId}
{port}
Claude Code is the default agent, so no agent setting is required. Set it explicitly if another agent is configured:
export default {
agent: "claude",
};The default Claude command is equivalent to:
claude -p --model haiku --permission-mode acceptEdits --output-format text "<prompt>"haiku is the default model because it is fast and cheap for iterative styling work, and the alias always resolves to the current Haiku. Pin a full id like claude-haiku-4-5-20251001 if you need a reproducible snapshot. Override it in config or on the CLI:
styles-iterator run --agent claude --model claude-sonnet-4-6 "Make the app feel more premium"Aliases such as haiku, sonnet, and opus also work as --model values.
The default permission mode is acceptEdits, so Claude Code can edit files in the generated worktree without prompting, but it does not auto-run arbitrary shell commands. Because styles-iterator works inside throwaway git worktrees, you can switch to bypassPermissions if a project needs the agent to run commands too:
export default {
agent: "claude",
claude: {
permissionMode: "bypassPermissions",
},
};If your installed Claude Code CLI uses a different command shape, configure it explicitly:
export default {
agent: "claude",
claude: {
command:
"claude -p --model {model} --permission-mode {permissionMode} --output-format {outputFormat} {prompt}",
},
};Values inserted into claude.command are shell-quoted automatically. Supported template variables include:
{prompt}
{model}
{outputFormat}
{permissionMode}
{variantId}
{variantName}
{sessionId}
{port}
Each variant gets a deterministic port:
basePort + variantIndex
By default:
v01 -> 47000
v02 -> 47001
v03 -> 47002
baseline -> 47000 + count
Configure your dev command with {port}:
export default {
app: {
devCommand: "pnpm dev --port {port}",
url: "http://127.0.0.1:{port}",
healthPath: "/",
},
};The harness starts at http://127.0.0.1:8787 by default. It shows:
- baseline app, when enabled
- all successful variants
- route selector
- responsive viewport mode by default, plus fixed viewport presets
- zoom control for fitting large viewports
- a deterministic CSS-grid board with a column selector (auto/1–4)
- per-card Focus to expand one preview to full width, Widen/Narrow column-span controls, and a drag-to-resize corner handle (free height + column-snapped width; double-click to reset)
- close and reopen controls for preview windows
- manual reload, plus opt-in auto reload
- ready-only filtering
- iframe previews
- per-variant Reroll, which discards the current iteration and explores a fresh take on the same style direction
- per-variant Continue…, which refines the variant from a custom prompt you type in the harness
- + New variation, which generates an additional variant on its own worktree and port while the harness is running
- live "working" status with a per-variant changed-file count and a rebuilding overlay on the preview while an agent runs
- diff stat, agent summary, and agent output logs (with download links), grouped in a per-card overflow menu
- accept button per committed variant with
cherry-pick,squash, orapply
Reroll, Continue, and New variation run the agent in the background inside the live
run/serve process — no need to restart serve. The harness keeps polling, so the
status badge, changed-file count, and preview update as the agent works (file changes
stream into the iframe through your dev server's hot reload). Each variant branch is kept
as a single commit from the base commit, so accept keeps working unchanged after any
number of iterations.
Iframes are fast and simple, but some apps block framing with X-Frame-Options or CSP. In those cases, use the Open link in each card.
Auto reload is disabled by default. To enable it for a project:
export default {
harness: {
autoRefresh: true,
autoRefreshIntervalMs: 10_000,
gridColumns: "auto",
},
};The harness UI also includes an Auto reload checkbox, so you can turn it on or off while comparing variants.
For harness UI development, run npm run smoke:harness after npm run build. It writes a standalone mocked harness HTML file and prints a file:// URL, so the harness controls can be checked without starting variant dev servers.
When accepting from the live harness, the baseline dev server is stopped before the selected variant is applied so the baseline preview cannot silently change underneath the comparison.
A small Vite and React fixture app lives in examples/vite-react. It is meant for local smoke testing and agent prompt iteration without bringing another repository into the loop. The accompanying examples/styles-iterator.config.ts is a ready-to-run config wired to that app; the root styles-iterator.config.example.ts is the generic starter template that styles-iterator init writes into your own project.
Run the deterministic smoke test:
npm run check
npm run smoke:exampleThe smoke copies the example app to a temporary directory, initializes Git, runs styles-iterator run --no-serve with a fake Codex-compatible agent command, and verifies that a variant commit plus agent output log were produced. It does not install the example app dependencies or start Vite.
styles-iterator accept v03Default strategy is cherry-pick.
Other strategies:
styles-iterator accept v03 --strategy squash
styles-iterator accept v03 --strategy applyEvery styles-iterator run first stops leftover agent, check, prepare, and dev-server processes recorded by previous sessions. This protects repeated runs after crashes, terminal closes, or interrupted sessions without deleting generated worktrees or branches.
Stop servers and remove worktrees:
styles-iterator cleanupAlso delete generated branches and local session state:
styles-iterator cleanup --delete-branches --purgeClean up every session at once (not just the most recent). Combined with --purge, this removes the entire .styles-iterator directory, including worktrees and state left behind by interrupted runs:
styles-iterator cleanup --all --delete-branches --purgeUnlink a source install:
pnpm unlink --global styles-iteratorRemove generated local state manually if needed:
rm -rf .styles-iteratorpnpm run check
pnpm run smoke:harness
pnpm run smoke:exampleBefore publishing a package:
npm publish --dry-runstyles-iterator intentionally isolates generated changes in Git worktrees, but the selected agent still runs with access to the files inside each worktree and may execute commands depending on your agent CLI permissions. Run this only in repositories and environments you trust.
The default guard rails fail variants that change:
package.json- lockfiles
.env*- migrations, database, auth, or API-looking paths
- more than 80 files
You can relax those in config when needed.