Fast CLI for routing code and media work through the right LLM path: direct API calls when available, multi-model councils when you need agreement, and browser recipes for web-only models such as ChatGPT Pro and Claude's Fable 5.
Yoetz is built for coding agents and terminal workflows: gitignore-aware bundles, structured JSON output, reproducible session artifacts, live model resolution, local budget checks, and multimodal inputs across OpenAI, OpenRouter, Gemini, and LiteLLM-compatible backends.
Yoetz is under active development. Command behavior may change before 1.0.
- Bundle repository context into prompt-ready artifacts without fighting
.gitignore. - Ask one model with text, code, images, or video.
- Council multiple models in parallel when you want independent opinions.
- Review staged diffs or files with agent-readable output.
- Generate images and video through providers that expose generation APIs.
- Use browser recipes for web-only model surfaces while keeping terminal output parseable.
brew install avivsinai/tap/yoetz
yoetz --versionscoop bucket add avivsinai https://github.com/avivsinai/scoop-bucket
scoop install yoetz
yoetz --versionDownload archives and checksums from the latest GitHub release.
curl -fLO https://github.com/avivsinai/yoetz/releases/latest/download/yoetz-aarch64-apple-darwin.tar.gz
curl -fLO https://github.com/avivsinai/yoetz/releases/latest/download/SHA256SUMS.txt
shasum -a 256 -c SHA256SUMS.txt --ignore-missing
tar xzf yoetz-aarch64-apple-darwin.tar.gz
sudo mv yoetz /usr/local/bin/Release archives are published for macOS, Linux, and Windows targets. Use
sha256sum instead of shasum -a 256 on Linux.
cargo install --git https://github.com/avivsinai/yoetz --lockedYoetz also ships an agent skill so Claude Code, Codex CLI, and compatible agent runtimes know how to call the CLI safely: resolve live model IDs, keep stdout parseable, bundle large context first, and use browser recipes intentionally.
/plugin marketplace add avivsinai/skills-marketplace
/plugin install yoetz@avivsinai-marketplace
npx skills add avivsinai/yoetznpx skild install @avivsinai/yoetzThe skill source lives at skills/yoetz/SKILL.md and is versioned with the CLI release metadata.
Start with a command that needs no API key:
yoetz bundle -p "Summarize this project" -f README.md --format jsonThen configure at least one provider. Environment variables are enough for most users:
export OPENROUTER_API_KEY=...
export OPENAI_API_KEY=...
export GEMINI_API_KEY=...For persistent configuration:
mkdir -p ~/.config/yoetz
cat > ~/.config/yoetz/config.toml <<'EOF'
[defaults]
provider = "openrouter"
# Optional after resolving a current model ID:
# model = "<id from yoetz models resolve>"
[providers.openrouter]
base_url = "https://openrouter.ai/api/v1"
api_key_env = "OPENROUTER_API_KEY"
kind = "openai-compatible"
EOFFrom a source checkout, you can start from the full example instead:
cp docs/config.example.toml ~/.config/yoetz/config.tomlYoetz also supports YOETZ_CONFIG_PATH, repo-local ./yoetz.toml, and profile
overlays selected with --config-profile <name>. See
docs/config.example.toml for the shape. Global
--timeout-secs controls provider HTTP calls and local Cursor CLI calls, and
defaults to 180.
--allow-unknown permits model IDs that are absent from the registry; reserve
it for self-hosted models whose IDs cannot be registered.
The default models frontier lab list is configurable with
[frontier].families; --all and --family continue to bypass that list.
yoetz ask writes artifacts to ~/.yoetz/sessions/<id>/ by default. Headless
callers can skip that entirely with yoetz ask --no-session (or
[sessions] no_session = true), and [sessions] max_age_days /
[sessions] max_count prune old or excess session dirs on startup. Both are
off by default, and [sessions] is only honored from trusted config
locations, never from repo-local ./yoetz.toml. A max_count of 0 removes
all completed sessions while preserving active writers. A legacy session with
no lease file is left alone for its first five minutes to avoid racing a writer
that has just created the directory. In JSON output,
--no-session emits session_dir: "" and response_json: null; artifact
consumers must handle those values without constructing a path.
Resolve live model IDs before putting them in scripts:
yoetz models sync
yoetz models frontier --format json
yoetz models frontier --family anthropic --format jsonMODEL_ID=$(yoetz models frontier --family openai --format json | jq -r '.[0].model.id')
yoetz ask \
-p "Explain the error handling tradeoffs in this file" \
-f crates/yoetz-cli/src/main.rs \
--provider openrouter \
--model "$MODEL_ID" \
--format jsonyoetz review diff --staged --format json
yoetz review file --path crates/yoetz-core/src/bundle.rs --format jsonTo inspect and apply a patch returned in the review's content JSON:
cat > patches-schema.json <<'JSON'
{
"type": "object",
"properties": {
"patches": { "type": "array", "items": { "type": "string" } }
},
"required": ["patches"],
"additionalProperties": false
}
JSON
yoetz review diff --staged --response-schema patches-schema.json \
--format json > review.json
jq -r '.content | fromjson | .patches | join("\n")' review.json > review.patch
yoetz apply --patch-file review.patch --check
yoetz apply --patch-file review.patch--response-schema makes the extraction contract explicit; without it,
content is free-form model text. --check runs git apply --check and does
not change files. Inspect the patch before the final command; yoetz apply
invokes git apply but does not accept or adjudicate the review findings for
you.
Yoetz can use an authenticated local Cursor CLI as a text backend. Install
Cursor CLI, run cursor-agent login (or agent login), then resolve the live
model ID before calling it:
yoetz models list --provider cursor -s "grok 4.6" --format json
CURSOR_MODEL=$(yoetz models list --provider cursor -s "grok 4.6" --format json \
| jq -er '.models | map(.id) | map(select(contains("grok-4.6") and endswith("-xhigh"))) | if length == 1 then .[0] else error("expected one Grok 4.6 xhigh model") end')
test -n "$CURSOR_MODEL"
yoetz ask \
-p "Find the root cause in this error path" \
-f "crates/**/*.rs" \
--provider cursor \
--model "$CURSOR_MODEL" \
--format jsonCursor runs in read-only Ask mode inside a temporary Yoetz-owned workspace;
the real repository is never trusted or exposed as its workspace. After
resolving another API model into OTHER_MODEL, prefix the Cursor model so
mixed-council provider routing stays explicit:
yoetz council -p "Challenge this design" \
--models "cursor/$CURSOR_MODEL,$OTHER_MODEL" \
--format jsonCursor CLI currently supports text only through Yoetz. Media, response schemas,
explicit output-token limits, and dollar budget flags fail closed because the
Cursor CLI does not expose equivalent contracts or dollar cost. Yoetz accepts
only its default temperature sentinel, 0.1, for Cursor because omission and
an explicit --temperature 0.1 are indistinguishable; other values fail closed
because Yoetz cannot honor them through the Cursor CLI. Cursor may use its own
default sampling value.
OPENAI_MODEL=$(yoetz models frontier --family openai --format json | jq -r '.[0].model.id')
GEMINI_MODEL=$(yoetz models frontier --family gemini --format json | jq -r '.[0].model.id')
XAI_MODEL=$(yoetz models frontier --family xai --format json | jq -r '.[0].model.id')
yoetz council \
-p "Which API shape is safer for agents?" \
-f crates/yoetz-core/src/types.rs \
--models "$OPENAI_MODEL,$GEMINI_MODEL,$XAI_MODEL" \
--format json--models is explicit on purpose. Pick current IDs from yoetz models frontier
or yoetz models resolve, and pass the returned IDs verbatim. Avoid using
stale provider names or hand-written wrapper paths.
yoetz bundle \
-p "Review the browser transport design" \
--name browser-transport-review \
-f "crates/yoetz-cli/src/browser*.rs" \
-f recipes/chatgpt.yaml \
--format jsonThe JSON response points to session artifacts under ~/.yoetz/sessions/<id>/,
including a descriptive Markdown file such as
browser-transport-review_20260812-193000Z.md. If --name is omitted, Yoetz
derives the name from the prompt.
MODEL_ID=$(yoetz models frontier --family gemini --format json | jq -r '.[0].model.id')
yoetz ask -p "Describe this diagram" --image diagram.png --provider gemini --model "$MODEL_ID" --format json
yoetz ask -p "Summarize this clip" --video demo.mp4 --provider gemini --model "$MODEL_ID" --format jsonUse --image-mime or --video-mime for signed URLs or extensionless files.
IMAGE_MODEL_ID=$(yoetz models list -s image --format json | jq -r '.models[] | .id | select(startswith("gemini/")) | sub("^gemini/"; "")' | head -1)
yoetz generate image \
-p "A clean product diagram of a terminal-first LLM router" \
--provider gemini \
--model "$IMAGE_MODEL_ID" \
--format json
VIDEO_MODEL_ID=$(yoetz models list -s veo --format json | jq -r '.models[] | .id | select(startswith("gemini/")) | sub("^gemini/"; "")' | head -1)
yoetz generate video \
-p "A short UI walkthrough" \
--provider gemini \
--model "$VIDEO_MODEL_ID" \
--format jsonGeneration still requires a provider-specific --provider plus a model that
that provider accepts. List or resolve live models before pinning a script.
Yoetz is designed to be called by agents and scripts.
export YOETZ_AGENT=1
yoetz ask -p "Return JSON only" -f src/lib.rs --format json --output-final /tmp/yoetz-result.jsonUseful agent-facing guarantees:
--format jsonkeeps stdout parseable.--response-schema <path>requests model-side structured output forask,council, orreview; the provider must produce a response that matches that schema.--output-schema <path>validates the final serialized Yoetz CLI envelope. It is checked after the command completes and before--output-finalis written. The names differ by only one word, but they protect different layers.--format jsononly selects the envelope representation.- Progress and diagnostics use stderr where possible.
--output-finalwrites the final response to a stable path.ask,bundle,council, andreviewcreate replayable session artifacts.- Budget flags such as
--max-cost-usdand--daily-budget-usdare local preflight/accounting aids, not provider-side hard limits.
Agent skill installation options are listed in Agent Skills.
Browser recipes let Yoetz use web-only model surfaces from the terminal. The
built-in ChatGPT recipe targets GPT-5.6 Sol at the account's known top tier
(Pro or Extra High) and is fail-closed: if Yoetz cannot prove the requested
surface is available, it stops
instead of silently downgrading. The built-in Claude recipe applies the same
contract to claude.ai with exactly Fable 5 and Effort Max.
The bundled gemini recipe remains for compatibility, but it is
legacy/experimental: it is a minimal agent-browser action sequence and does
not implement the typed, fail-closed contract used by ChatGPT and Claude.
yoetz browser check --format json
yoetz browser recipe --recipe chatgpt --bundle ~/.yoetz/sessions/<id>/<named-bundle>.md --format json
yoetz browser check --claude --format json
yoetz browser recipe --recipe claude --bundle ~/.yoetz/sessions/<id>/<named-bundle>.md --format jsonBoth recipes support chrome-devtools-mcp, dev-browser, agent-browser, and
chrome-extension-native. The default browser stack is extension-free unless a
connected native extension advertises the selected site. That recipe then
selects chrome-extension-native as its only default transport and fails
closed. Use an explicit --transport <name> to select another browser
transport.
The native path is one pinned multi-site package named Yoetz Native Transport, so existing ChatGPT installations keep the same extension ID and gain Claude support without re-pairing. Choose the site scope when managing or checking it:
yoetz browser extension setup --chatgpt --open-chrome
yoetz browser extension doctor --chatgpt
yoetz browser extension status --chatgpt --format json
yoetz browser recipe --recipe chatgpt --transport chrome-extension-native --bundle <named-bundle>.md --format json
yoetz browser extension setup --claude --open-chrome
yoetz browser extension doctor --claude
yoetz browser extension status --claude --format json
yoetz browser extension canary --claude
yoetz browser recipe --recipe claude --transport chrome-extension-native --bundle <named-bundle>.md --format jsonLoad the managed $YOETZ_DIR/chatgpt-native-extension directory unpacked in
the Chrome profile that hosts the target AI sessions; do not load a repo
checkout. Setup, update, and reload share machine-global native-host and managed
extension state. Recipe runs hold a shared lifecycle lock; setup, update,
reload, and auto-heal require its exclusive side and fail with
extension_lifecycle_busy instead of changing the loaded artifact mid-run.
Independent ChatGPT and Claude recipe runs may share one connected extension
profile: each job owns a separate background tab, and profile selectors are
routing controls, not a prerequisite for parallelism. On released v0.5.42, a
live run proved exactly two concurrent Claude recipes in one connected profile
on an Enterprise workspace account. The jobs overlapped for 125s and used
distinct conversations. Both verified Fable 5 and Effort Max. Tab
non-activation has separate evidence: the released adapter sets
activateOnCreate:false, a single-job live probe measured tab_active=false at
every phase, and service-worker coverage asserts that no tab activation call
occurs. The concurrency evidence covers two jobs, not higher fanout or other
account types. Service-worker coverage separately proves two Claude jobs use
distinct background tabs through overlapping phases and that cancelling one
does not affect the other. Both sites may run only against one frozen loaded
artifact.
Give each parallel recipe its own Yoetz bundle session directory; reusing one
managed named Markdown bundle fails with session_busy rather than overwriting that
session's response.json or followup.json.
Upgrade the installed CLI before another lane runs extension auto-heal after a release. An older CLI can overwrite the newer stamped managed copy.
Claude conversation resume supports --var conversation=<uuid|url> and
--followup <session-id|conversation-id|url> on the native transport. For a
stable semantic address, use --thread <label>:
# Start a new conversation and point the label at its final conversation.
yoetz browser recipe --recipe chatgpt --bundle "$BUNDLE" \
--thread release-review --fresh --format json
# Reuse the label. Fail immediately if another process owns it.
yoetz browser recipe --recipe chatgpt --bundle "$BUNDLE" \
--thread release-review --on-thread-conflict fail --format json
# Other collision policies: wait indefinitely, wait with a bound, or fork an
# unlabelled conversation without moving the existing label.
yoetz browser recipe --recipe chatgpt --bundle "$BUNDLE" \
--thread release-review --on-thread-conflict wait:5m --format json
yoetz browser recipe --recipe chatgpt --bundle "$BUNDLE" \
--thread release-review --on-thread-conflict fork --format json--fresh and --on-thread-conflict require --thread; the default conflict
policy is fail. A wait:<duration> bound accepts ms, s, m, or h and
fails with thread_busy_timeout when it expires. --thread, --followup, and
--var conversation= are mutually exclusive. Use --keep-tab to retain a
successful Yoetz-owned browser tab. Use --browser-id <id> to select a local
Chrome instance by its published /devtools/browser/<id> suffix.
inline_warn_tokens defaults to 150,000 and warns when a bundle is likely to
become retrieval-backed; set it to 0 to disable the heuristic. This warning
does not change Yoetz's byte limits.
The native-host extension transport is currently macOS/Linux-only. Windows CLI
and Scoop installs work for the API-backed Yoetz flows, but
chrome-extension-native setup fails closed until Windows native messaging host
registration is implemented.
For multiple loaded Chrome profiles, select the connected bridge with
--var extension_instance_id=<id> from yoetz browser extension status --chatgpt (or --claude), or opt into profile_email routing with
yoetz browser extension grant-identity --chatgpt (or --claude).
The detailed browser transport model lives in ARCHITECTURE.md.
Capabilities vary by model and provider. Use yoetz models frontier,
yoetz models list, and yoetz models resolve against the live registry before
pinning examples.
| Provider | Text | Vision | Image Gen | Video Gen | Video Understanding |
|---|---|---|---|---|---|
| OpenRouter | Yes | Model-dependent | No | No | No |
| OpenAI | Yes | Yes | Yes | Yes (Sora) | No |
| Gemini | Yes | Yes | No | Yes (Veo) | Yes |
| Cursor CLI | Yes | No | No | No | No |
| Z.AI | Yes | Model-dependent | No | No | No |
| LiteLLM-compatible | Yes | Model-dependent | No | No | No |
Anthropic, xAI, and Z.AI models are commonly reached through OpenRouter, but can also be configured as direct providers when you need provider-specific routing.
Common API key variables:
| Variable | Used for |
|---|---|
OPENROUTER_API_KEY |
OpenRouter |
OPENAI_API_KEY |
OpenAI |
GEMINI_API_KEY |
Gemini |
ANTHROPIC_API_KEY |
Direct Anthropic-compatible provider configs |
XAI_API_KEY |
Direct xAI/OpenAI-compatible provider configs |
ZAI_API_KEY |
Direct Z.AI/OpenAI-compatible routing |
LITELLM_API_KEY |
LiteLLM proxy |
Bundles are prompt-input artifacts, not trusted control channels. Treat bundled repository content, issues, logs, and pasted browser output as untrusted input. Keep intent in explicit CLI flags and the user prompt, avoid bundling secrets, and review generated changes before applying them. Cursor calls add a second boundary: Yoetz copies only the rendered consult into an isolated temporary workspace and runs Cursor in Ask + sandbox mode without force, YOLO, or MCP auto-approval flags.
Project trust signals:
- CI covers Rust tests, formatting, linting, dependency policy, secret scanning, MSRV, extension script tests, and browser smoke checks.
- Release archives ship with
SHA256SUMS.txt. - Security policy: SECURITY.md.
- Code of conduct: CODE_OF_CONDUCT.md.
git clone https://github.com/avivsinai/yoetz.git
cd yoetz
cargo build
cargo test
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warningsOptional browser-extension checks:
./scripts/build-chatgpt-native-extension.sh --check
node --test extensions/chatgpt-native/tests/*.test.jsThe Rust workspace has two crates:
crates/yoetz-core: pure core types, bundling, config, registry, sessions.crates/yoetz-cli: async CLI, providers, browser transports, budgets.
See ARCHITECTURE.md and CONTRIBUTING.md for design and contribution details.
Releases are cut from main through ./scripts/release.sh X.Y.Z and the
resulting release PR. The merged release commit drives the tag, GitHub release
artifacts, Homebrew formula, Scoop manifest, and agent skill publication.