A minimal CLI for running LLM agents.

Oneshot mode, daemon mode over WebSocket, configured in Lua and JSON. Talk to any OpenAI-compatible endpoint.

$ cargo install yuke
$ yuke -p "summarize src/"
[ streams one assistant turn ]

One Rust binary. Sessions persist to ~/.yuke/workspaces/ for resume.

Works with

Oneshot mode

Run one assistant turn in-process, no server. The CLI loads ~/.yuke/providers.json for the catalog and ~/.yuke/init.lua for tools and hooks. Sessions persist under ~/.yuke/workspaces/ so --resume picks them back up.

  • One turn, one binary, no server
  • Resume a session later with --resume <id>
  • Scriptable: --output-format json returns a result with session_id
$ yuke -p "refactor the parser to use the new Error type"

  read src/parse.rs
  read src/error.rs

  Reading the current implementation and the new Error type.
  The change touches 47 lines across 3 functions.

   Done. 4 rounds, 12.4k tokens.

How it fits together

One binary, two modes. The CLI is the client; yuke daemon runs an optional long-lived server that any WebSocket client can talk to.

  • CLI and daemon share one binary
  • Multiple clients see one live event stream
  • Wire protocol is documented, generated from Rust types
  ┌──────────┐    ws://127.0.0.1:7878/ws    ┌──────────────┐
  │ yuke CLI │ ◄──────────────────────────► │ yuke daemon  │
  └──────────┘                              └──────┬───────┘
                                                   │
                          ┌────────────────────────┼────────────────┐
                          │                        │                │
                          ▼                        ▼                ▼
                   ┌──────────┐             ┌──────────┐     ┌──────────┐
                   │ TUI      │             │ editor   │     │ your own │
                   │ client   │             │ plugin   │     │ client   │
                   └──────────┘             └──────────┘     └──────────┘

Daemon mode

Start the daemon once, connect any client. Sessions persist under ~/.yuke/workspaces/ and stream events to every connected client in real time.

  • Long-lived sessions, shareable across clients
  • JSON-over-WebSocket, generated from Rust types
  • Build a TUI in Elm, Ratatui, Bubble Tea, GPUI

Full schema at /asyncapi.json · human-readable guide →

# Terminal 1
$ yuke daemon
  [daemon] listening on ws://127.0.0.1:7878/ws

# Terminal 2: any WebSocket client
$ yuke -p "explain this codebase"

# Or wire-protocol level:
   { "Hello": { "models": [...] } }
   { "CreateSession": { "path": "...", "config": {} } }
   { "Event": { "event": { "Session": { "seq": 1,
  "event": { "Agent": { "TextDelta": { "id": "...", "delta": "..." } } } } } }

What you get

Oneshot mode

Run one turn in-process. Loads ~/.yuke/providers.json, auth.json, and the optional ~/.yuke/init.lua on startup. Resume a session with --resume.

Daemon mode

Long-lived session on ws://127.0.0.1:7878/ws. Any client that can hold a WebSocket open can connect.

JSON catalog

Model catalog is plain JSON in providers.json. Edit with any tool; the daemon reloads it on the next session.

Lua for behavior

Session policy, tools, and hooks are Lua in init.lua. Async runtime, sandboxed standard library, full IO primitives.

Async tools

Tool handlers run on an async runtime. File IO, HTTP, subprocesses: all non-blocking, all available from Lua.

Hooks at every step

before_tool, after_tool, turn_start, done: gate, rewrite, or audit the agent loop.

Open wire protocol

The daemon speaks a documented JSON-over-WebSocket protocol (asyncapi.json). Build your own client in any language.

yuke-tui

An interactive terminal client that ships with yuke. Its UI is fully Lua-scriptable: rebind keys, swap layouts, register custom commands, paint your own widgets — all from ~/.yuke/tui/init.lua.

Single binary

cargo install yuke drops one binary on your PATH. No Electron, no background processes when not running.

MIT licensed

No telemetry, no accounts, no hosted service. Credentials live in ~/.yuke/auth.json (mode 0600); the keys themselves stay in your shell.

Three files, no DSL

The model catalog is JSON, edited with any tool and reloaded by the daemon on demand. Credentials live in their own JSON file (mode 0600), separate from the catalog. Tools and hooks are Lua, optional, and run once per session.

providers.json ~/.yuke/providers.json

// The model catalog. Edit with any tool;
// reloaded on the next session.
{
  "providers": [
    {
      "name": "openai",
      "base_url": "https://api.openai.com/v1",
      "models": [
        { "name": "gpt-4o" },
        { "name": "o3", "protocol": "codex" }
      ]
    }
  ]
}

init.lua ~/.yuke/init.lua (optional)

-- Session policy, tools, hooks.
-- This file is optional.
yuke.tool {
  name        = "read",
  description = "Read a file with line numbers.",
  params      = { path = "string" },
  handler     = function(args)
    return yuke.fs.read(args.path)
  end,
}

yuke.on("before_tool", function(call)
  if call.name == "bash"
     and call.arguments.command:find("rm %-rf") then
    return { deny = "refusing" }
  end
end)

Provider catalog → · init.lua API →

Different by design

Minimal

One binary, opt-in daemon, no background processes when idle. The runtime is small enough to read in a weekend and small enough to keep in your head.

Yours to extend

Tools and hooks are Lua, in ~/.yuke/init.lua. The TUI's UI is Lua too, in ~/.yuke/tui/init.lua. The model catalog is plain JSON. The wire protocol is a documented asyncapi.json.

Yours to own

MIT licensed. No telemetry, no accounts, no hosted service. Credentials live in ~/.yuke/auth.json (mode 0600); the keys themselves stay in your shell.

The mascot has moods

The bull has states. Use them as loading hints, status indicators, or just decoration in your own docs. PNGs are free to take.

idle
idle
charge
charge
sleep
sleep
demon
error
skull
failed

Five of nine available. The full set lives in /bull/.

What people build with it

Daily-driver CLI

Replace a hosted coding agent with a local install. No telemetry, no vendor lock-in, no Electron app.

Editor integration

Connect your editor to a daemon over WebSocket. Drive sessions from keybindings, stream output in real time.

Custom frontends

yuke-tui is the bundled reference client; build your own in any stack (Elm, Ratatui, Bubble Tea, GPUI). The wire protocol is documented and stable.

Try it.

Install yuke, drop a providers.json next to init.lua, set the API key in your shell. One prompt in.

$ cargo install yuke
$ mkdir -p ~/.yuke && cat > ~/.yuke/providers.json <<'EOF'
{
"providers": [
{
"name": "openai",
"models": [ { "name": "gpt-4o" } ]
}
]
}
EOF
$ cat > ~/.yuke/auth.json <<'EOF'
{ "providers": { "openai": { "type": "api_key_env", "env": "OPENAI_API_KEY" } } }
EOF
$ export OPENAI_API_KEY=*** yuke -p "summarize src/"