Command line
koda [OPTIONS] [PROMPT]...A bare prompt seeds the TUI with a first message. With -p it runs headless: the answer
streams to stdout and koda exits.
| Flag | What it does |
|---|---|
[PROMPT]... |
First message. Without -p it seeds the TUI. |
-p, --print |
Headless: stream the answer to stdout and exit. |
-m, --model <MODEL> |
Model name, e.g. qwen2.5-coder:14b. |
-u, --url <BASE_URL> |
OpenAI-compatible base URL, e.g. http://localhost:1234/v1. |
--api-key <KEY> |
API key, if the server needs one. |
-C, --dir <DIR> |
Workspace root. Defaults to the current directory. |
-y, --yolo |
Approve file writes and commands without asking. |
--protocol <PROTOCOL> |
Tool-call protocol: auto, native, or text. |
--no-sandbox |
Allow file tools outside the workspace root. |
-t, --temperature <T> |
Sampling temperature. |
--theme <THEME> |
Palette name — see Themes. |
--icons <ICONS> |
Glyphs: auto, unicode, ascii. |
--mode <MODE> |
Start in plan, execute or vibe. |
-c, --continue (--resume) |
Reopen the most recent conversation in this project. |
Subcommands
Section titled “Subcommands”| Subcommand | What it does |
|---|---|
koda models |
List the models the endpoint reports. |
koda skills [--init] |
List skills; --init writes a starter skill into <project>/.koda/skills/. |
koda config [--init] |
Show the effective configuration; --init writes a commented starter file. |
Headless mode
Section titled “Headless mode”koda -p "which module owns the retry logic?"-p runs the same agent as the TUI with the screen taken away: same tools, same
modes, same config. The answer goes to stdout and tool activity to stderr,
so you can keep one without the other:
koda -p "summarise what changed in src/ this week" > summary.md # answer onlykoda -p "find the retry logic" 2> trace.log # activity onlyChoosing how much it may do
Section titled “Choosing how much it may do”Headless has nobody to answer an approval prompt, so what koda is allowed to do
has to be decided before it starts. Two independent flags decide it: --mode is
what kind of work it may do, and -y is whether it may do it without asking.
| Command | What it may do | Exit status |
|---|---|---|
koda -p --mode plan "..." |
Read and think. Writes and commands are refused. | 0 |
koda -p --mode execute "..." |
Nothing: the first write is denied, unanswered. | 2 |
koda -p -y --mode execute "..." |
Read, write, and run commands. | 0 |
koda -p -y --mode vibe "..." |
The same, after writing a spec and checking itself against it. | 0 |
--mode execute is the default, so a bare koda -p "fix the test" is the second
row: it will plan, read, and then stop at the first edit. That is deliberate —
the flag that lets an unattended agent write to your disk should be one you typed
on purpose.
plan — read-only, and safe to run anywhere
Section titled “plan — read-only, and safe to run anywhere”koda -p --mode plan "the add function is subtracting - what would you change?"Nothing on disk changes. If the model tries to edit, the tool is refused and koda says so on stderr, then carries on and answers:
· plan mode blocked edit_file; re-run with --mode executeThe exit status is still 0, because a plan is the deliverable here, not a
failure. This is the mode to reach for in a hook, a CI job, or anything running
against a checkout you care about — it cannot damage a working tree.
execute — the working mode
Section titled “execute — the working mode”koda -p -y --mode execute "fix the failing test in tests/test_calc.py"git diff --statWithout -y, the first write is denied and koda exits 2:
✗ edit_file needs approval; re-run with --yolo to allow itWith -y it edits and runs commands unattended. Check the result the way you
would check a colleague’s: git diff. koda writes through the same edit tool the
TUI shows you a diff for, so everything it did is in the working tree, and
git checkout . undoes all of it.
vibe — execute, with a spec and a self-check
Section titled “vibe — execute, with a spec and a self-check”koda -p -y --mode vibe "add a --json flag to the report command"Vibe is execute plus two extra steps of its own: it writes an explicit spec before starting, and checks its work (and any subagent’s work) against that spec before it finishes. It costs more model calls and takes longer, so it earns its place on a task with enough shape to be got wrong — not on a one-line fix.
Exit statuses
Section titled “Exit statuses”| Status | Meaning |
|---|---|
0 |
The turn finished. In plan mode this includes refused writes. |
1 |
koda could not start: no endpoint, no model, a bad flag. |
2 |
A tool needed approval nobody could give, or the turn failed. |
So a script can branch on it:
if koda -p -y --mode execute "fix the failing test"; then git diff --statelse echo "koda could not finish unattended" >&2fiScripting it
Section titled “Scripting it”Everything else about a run can be set on the command line, so a headless call does not depend on whatever is in your config:
koda -p -y \ --mode execute \ -C /path/to/project \ -u http://localhost:11434/v1 \ -m qwen2.5-coder:14b \ --protocol native \ "update the changelog for the last three commits"A few things worth knowing before this runs unattended:
ask_usergets no answer. If the agent asks a question, koda reports it and continues without one. A task that needs a decision belongs in the TUI.- The sandbox still applies. File tools stay inside
-Cunless you pass--no-sandbox. - Destructive commands are still confirmed when
confirm_destructiveis on — which, headless, means they are refused rather than run. That is the intended behaviour:-ymeans “do the work”, not “do anything”. - Sessions are still written, so
koda --continuein that directory reopens what the headless run did, in the TUI.
Running things yourself
Section titled “Running things yourself”Two prefixes skip the agent entirely. Neither costs tokens, and neither enters the conversation the model sees — the output is yours, not context.
| Prefix | What it runs |
|---|---|
! |
A shell command in the workspace: !git status, !npm test, !git commit -am wip. |
$ |
Python: $ print(sum(range(1, 11))) |
$ import json; print(json.load(open("package.json"))["version"])$ uses python3 when it is on your PATH and falls back to python. Both show their
output in the transcript as a tool block, exactly like a command the agent ran — the
difference is invisible to you and total to the model.
This is the answer to “I need a number, not a guess”: an interpreter’s arithmetic instead of a language model’s.
Editing what you typed — #
Section titled “Editing what you typed — #”Press # at the end of what you have written to open a palette of actions on the text
itself. None of them reach the model or cost a turn.
| Action | What it does |
|---|---|
#copy |
Copy the whole prompt to the clipboard. |
#copyline |
Copy the line the caret is on. |
#cutline |
Delete that line. |
#start / #end |
Move the caret to the beginning or the end. |
#clear |
Empty the input. |
#undo |
Restore what the last action removed. |
#paste |
Insert the clipboard’s text. |
Type a few letters to narrow the list (#cl → #clear), ↑/↓ to
pick, enter to run, esc to close and keep what you wrote. A # in
the middle of a sentence is left alone as ordinary text, so fix #3 in the parser behaves
normally.