A secure coding agent for the terminal, written in TypeScript. CLI and SDK.
Animated terminal demo: slash commands are typed and answered, then a prompt asks the solution-architect agent to create a NestJS API. KODA thinks, replies, and asks for approval before writing a file.
Introduction
KODA reads your project, edits files and runs commands, always asking for approval on sensitive actions. It works with several LLM providers and is compatible with the OpenCode structure for agents, skills and commands.
| PROVIDER | DEFAULT MODEL |
|---|---|
| openai | gpt-4.1-mini |
| anthropic | claude-sonnet-4-6 |
| gemini | gemini-3.8-flash |
| openrouter | anthropic/claude-sonnet-4.5 |
| groq | moonshotai/kimi-k2-instruct |
| cerebras | qwen-3-coder-480b |
| together | moonshotai/Kimi-K2-Instruct |
| fireworks | accounts/fireworks/models/kimi-k2-instruct |
| deepinfra | moonshotai/Kimi-K2-Instruct |
| nebius | moonshotai/Kimi-K2-Instruct |
| baseten | moonshotai/Kimi-K2-Instruct |
| deepseek | deepseek-chat |
| xai | grok-code-fast-1 |
| mistral | devstral-medium-latest |
| moonshot | kimi-k2-0905-preview |
| zai | glm-4.6 |
| nvidia | moonshotai/kimi-k2-instruct |
| ollama | llama3.2 (local) |
| lmstudio | qwen2.5-coder-7b-instruct (local) |
| vllm | Qwen/Qwen2.5-Coder-7B-Instruct (local) |
| llamacpp | default (local) |
Every provider past the first three is OpenAI-compatible and served from the catalog with no extra dependency. Each uses <NAME>_API_KEY, <NAME>_MODEL and <NAME>_BASE_URL; connect with /connect and select with KODA_PROVIDER=<name>. Local servers (ollama, lmstudio, vllm, llamacpp) need no key.
Installation
Requires Node >= 22.
npm install -g @koda-tools/koda
Then just open a terminal in your project and run:
koda
To try it without installing, use npx @koda-tools/koda.
UPDATE
npm update -g @koda-tools/koda
Configuration
Provider and model are set through environment variables.
# choose the provider (default: openai)
export KODA_PROVIDER=anthropic
export ANTHROPIC_API_KEY=sk-...
export ANTHROPIC_MODEL=claude-sonnet-4-6
| PROVIDER | KEY | MODEL | BASE URL |
|---|---|---|---|
| openai | OPENAI_API_KEY | OPENAI_MODEL | — |
| anthropic | ANTHROPIC_API_KEY | ANTHROPIC_MODEL | — |
| gemini | GEMINI_API_KEY | GEMINI_MODEL | — |
| openrouter | OPENROUTER_API_KEY | OPENROUTER_MODEL | https://openrouter.ai/api/v1 |
| groq | GROQ_API_KEY | GROQ_MODEL | https://api.groq.com/openai/v1 |
| cerebras | CEREBRAS_API_KEY | CEREBRAS_MODEL | https://api.cerebras.ai/v1 |
| together | TOGETHER_API_KEY | TOGETHER_MODEL | https://api.together.xyz/v1 |
| fireworks | FIREWORKS_API_KEY | FIREWORKS_MODEL | https://api.fireworks.ai/inference/v1 |
| deepinfra | DEEPINFRA_API_KEY | DEEPINFRA_MODEL | https://api.deepinfra.com/v1/openai |
| nebius | NEBIUS_API_KEY | NEBIUS_MODEL | https://api.studio.nebius.com/v1 |
| baseten | BASETEN_API_KEY | BASETEN_MODEL | https://inference.baseten.co/v1 |
| deepseek | DEEPSEEK_API_KEY | DEEPSEEK_MODEL | https://api.deepseek.com/v1 |
| xai | XAI_API_KEY | XAI_MODEL | https://api.x.ai/v1 |
| mistral | MISTRAL_API_KEY | MISTRAL_MODEL | https://api.mistral.ai/v1 |
| moonshot | MOONSHOT_API_KEY | MOONSHOT_MODEL | https://api.moonshot.ai/v1 |
| zai | ZAI_API_KEY | ZAI_MODEL | https://api.z.ai/api/paas/v4 |
| nvidia | NVIDIA_API_KEY | NVIDIA_MODEL | https://integrate.api.nvidia.com/v1 |
| ollama | OLLAMA_API_KEY optional | OLLAMA_MODEL | http://127.0.0.1:11434/v1 |
| lmstudio | LMSTUDIO_API_KEY optional | LMSTUDIO_MODEL | http://127.0.0.1:1234/v1 |
| vllm | VLLM_API_KEY optional | VLLM_MODEL | http://127.0.0.1:8000/v1 |
| llamacpp | LLAMACPP_API_KEY optional | LLAMACPP_MODEL | http://127.0.0.1:8080/v1 |
Each <NAME>_BASE_URL overrides the default shown above. Hosted providers require their key; local servers do not.
CONNECT A PROVIDER (/CONNECT)
Instead of exporting variables, save the key with /connect inside KODA, or with koda connect straight from the terminal (requires an interactive terminal).
# pick the provider from a list (↑/↓, Enter, Esc cancels)
/connect
# or pass the provider directly
/connect anthropic
koda connect anthropic
Available providers: openai, anthropic and gemini. Ollama is local and needs no key. The list shows the state of each one: connected, from environment or not connected.
After choosing, type the key (the input is hidden). It is saved to ~/.config/koda/auth.json with permission 600.
KODA_PROVIDER=name).OLLAMA (LOCAL)
export KODA_PROVIDER=ollama
export OLLAMA_BASE_URL=http://127.0.0.1:11434/v1
export OLLAMA_TOOL_SUPPORT=true
Commands
Type / at the prompt to see suggestions.
| COMMAND | DESCRIPTION |
|---|---|
| /help | Shows the built-in commands |
| /commands | Lists custom commands |
| /model [name|number] | Switches the model |
| /agents | Lists agents |
| /agent [name] | Switches the primary agent |
| /skills | Lists skills |
| /connect [provider] | Saves a provider's API key |
| /mcp | Shows MCP servers and their status |
| /init | Creates the .koda/ folder in the project |
| /clear | Clears the conversation context (asks for confirmation) |
| /exit | Exits KODA |
Use @agent task to delegate to a subagent.
START A PROJECT (INIT)
Run koda init in the project root, or /init inside a session, to create the folders KODA reads its extensions from. It needs no API key and no interactive terminal.
# from the terminal
koda init
# or inside KODA
/init
Created .koda/commands
Created .koda/skills
Created .koda/agents
Exists. If a file named .koda is in the way, init fails with an error and creates nothing. Only the exact command koda init runs this; koda init something is still sent to the model as a prompt.Agents
| NAME | MODE | DESCRIPTION |
|---|---|---|
| build | primary | Default. All tools available. |
| plan | primary | Analyzes and proposes plans, without editing files. |
| general | subagent | Research and multi-step tasks. |
| explore | subagent | Read-only. Searches files and code. |
Tools
| TOOL | WHAT IT DOES |
|---|---|
| readFile | Reads files, with line ranges |
| writeFile | Creates or changes files (with approval) |
| listDirectory | Lists directories |
| searchFiles | Searches the project |
| getFileInfo | File metadata |
| runCommand | Runs commands with risk classification, bounded output and an allowlisted env |
MCP servers
KODA connects to MCP servers using the same mcp section as OpenCode, in koda.json or opencode.json (project root, .koda/, .opencode/ or the global ~/.config/koda).
{
"mcp": {
"filesystem": {
"type": "local",
"command": ["npx", "-y", "@modelcontextprotocol/server-filesystem", "."],
"environment": { "LOG_LEVEL": "info" }
},
"docs": {
"type": "remote",
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer {env:DOCS_TOKEN}" }
},
"old-server": { "enabled": false }
}
}
| FIELD | APPLIES TO | DESCRIPTION |
|---|---|---|
| type | both | local (stdio) or remote (streamable HTTP, then SSE) |
| command | local | Executable and arguments, as an array |
| environment | local | Extra variables for the server process |
| url | remote | Server endpoint (http or https) |
| headers | remote | HTTP headers, e.g. Authorization |
| enabled | both | Defaults to true. { "enabled": false } alone switches off a server defined elsewhere |
| timeout | both | Milliseconds to connect and list tools (default 30000) |
{env:NAME} in any value is replaced by that environment variable. Each tool is exposed as <server>_<tool>, and /mcp shows each server's status and tool count. A server that fails never blocks the session.
Local servers only receive a safe set of variables (PATH, HOME...) plus the environment you list, never your API keys.
PERMISSIONS
MCP calls are allowed by default. Use the mcp key to restrict them; patterns match the tool name.
permission:
mcp:
"*": allow
"docs_*": ask
"filesystem_write_file": deny
koda.json / opencode.json in projects you did not write.Custom commands
Create a Markdown file in .koda/commands/ (run koda init to create the folder). The file name becomes the command.
# .koda/commands/review.md
---
description: Reviews a file
agent: plan
model: openai/gpt-4.1-mini
subagent: false
---
Review the file $1 focusing on $2.
Full arguments: $ARGUMENTS
Usage: /review src/index.ts security
.koda/ → koda.json → .opencode/ → opencode.json → global configuration. Duplicates at the same level cause an error.
Custom agents
Defined in Markdown (body = prompt) or JSON, in the same directories as commands.
---
description: Security reviewer
mode: subagent
model: anthropic/claude-sonnet-4-6
temperature: 0.1
steps: 15
permission:
edit: deny
bash: ask
---
You review code looking for vulnerabilities.
Skills
Each skill is a folder with a SKILL.md.
.koda/skills/<name>/SKILL.md
The .opencode, .claude and .agents folders are also read. List them with /skills.
Permissions
Each action resolves to allow, ask or deny. With glob patterns, the last matching rule wins.
permission:
read: allow
edit: ask
bash:
"*": ask
"git status": allow
"rm *": deny
Keys: read, edit, list, glob, grep, bash, task, skill, mcp.
Use as a library
The SDK is its own package, @koda-tools/core, with no terminal dependencies, so it is light to install and safe to bundle in other hosts such as an editor extension. @koda-tools/koda re-exports everything from it, so existing imports keep working.
npm install @koda-tools/core
import { bootstrapKoda, loadConnection } from "@koda-tools/core";
const connection = await loadConnection({ env: process.env });
const koda = await bootstrapKoda({
cwd: process.cwd(),
connection,
policies: { approve: async (title) => askUser(title) },
});
await koda.close();
Approvals are supplied by the host, as an object or as a function run once the provider is built (a missing API key fails before it is called). The terminal UI (runInteractive, renderHeader) is a separate export:
import { runInteractive } from "@koda-tools/koda/tui";