2026-09-12 10:14:55 +02:00
2026-09-12 10:14:55 +02:00
2026-09-12 10:14:55 +02:00
2026-09-12 10:14:55 +02:00
2026-09-12 00:35:26 +02:00
2026-09-12 10:14:55 +02:00
2026-09-12 10:14:55 +02:00
2026-09-12 10:14:55 +02:00
2026-09-12 10:14:55 +02:00
2026-09-12 00:35:26 +02:00
2026-09-12 10:14:55 +02:00
2026-09-12 10:14:55 +02:00
2026-09-12 10:14:55 +02:00
2026-09-11 14:29:38 +00:00
2026-09-12 10:14:55 +02:00
2026-09-12 10:14:55 +02:00
2026-09-12 10:14:55 +02:00

sjcode

a terminal coding agent, powered by the ollama API

What makes it an agent

The client is not a chat front end: the model can really work in your codebase. It runs shell commands, reads and edits files — on your machine, in the directory you started ./coder from. Every model reply can contain tool calls; the client executes them and feeds the real output back to the model until the task is done.

Tools the agent can use:

tool what it does asks first
run run one shell command in the project directory yes
read read a file (numbered lines, start/limit) no
files list project files (skips .git, caches, ...) no
write create a file, or overwrite a small one yes
edit exact-text replace in a file, shows a diff yes

Before every run/write/edit you see exactly what will happen (command line, or a unified diff for file changes) and press Enter to allow it, n to decline, a to stop the agent — or just type a short instruction ("use ripgrep instead") and the model adapts. --yes (or /auto in the session) runs everything without asking; the sandbox keeps all file tools inside the project directory.

Setup

cp server/config.toml.example server/config.toml   # then edit port/model/limits
python3 server/server.py                            # starts the daemon + admin CLI

config.toml is machine-local and git-ignored; config.toml.example is the template. On first start the daemon creates the SQLite database (sjcode.db).

Pick a coder model in [ollama] model — one that follows structured output well, e.g. qwen2.5-coder:7b or bigger. The agent transcript is long, so the daemon requests a large context window from ollama:

[ollama]
num_ctx = 16384   # context window; do not go below 8192 for agent mode

Daemon (server/server.py)

On startup it prints its settings and drops into a minimal admin CLI (accounts are stored in SQLite):

command effect
create <name> [role] create account (asks for password, default role user)
list show all accounts
delete <name> remove account
role <name> <role> change role
passwd <name> set a new password
usage [name] used minutes today vs. limit
reset [name] reset the daily usage counter
status show daemon settings
quit stop the daemon

While the CLI runs, an HTTP API is served in the background:

  • POST /login — {"username", "password"} → {"token", ...}
  • POST /chat — {"token", "message", "model"?, "stream"?} → reply as JSON, or (with "stream": true) as NDJSON lines {"chunk": ...} ending with one final {"response", "used_minutes", "remaining_minutes", ...} line
  • GET /models?token=... — list ollama models
  • GET /status — health check

Every user row stores: username, password_hash, last_logged_in, role, used_today (minutes of AI usage; resets on a new day).

SJCODE_CONFIG=/path/to/config.toml lets you run a second daemon (tests, staging) next to a regular one.

Roles and limits

In config.toml each role gets a daily limit in minutes:

[limits]
user  = 120   # default role for new accounts
admin = -1    # negative = unlimited

Login and every chat request are blocked once used_today reaches the limit.

Client (client.py)

./coder                               # or: python3 client.py
python3 client.py 10.0.0.5:8420       # explicit daemon address
python3 client.py --model qwen3-coder # skip the model picker
python3 client.py --yes               # auto-approve every tool call (careful)
python3 client.py --no-probe          # skip the reachability check (use behind
                                      # HTTP proxies that answer on every port)

On start the client asks which server to connect to: pick one of the saved ones, or add a new one (n). Every server you use is saved in ~/.config/sjcode/client.json together with the username and the last model, so the next start is just Enter-Enter. Addresses without a port use 8420; host:port, http(s)://... URLs and IPv6 literals are accepted.

After login and model choice you get the agent banner and a REPL. Type tasks, not chat messages — "rename the function foo to bar in all files" is a task, "what is a monad" is just a question the agent answers in place.

Commands:

command effect
/help list commands
/server show the connected server
/models list models on the server
/model <n> switch model (saved for this server)
/limit remaining minutes today (last known)
/auto toggle confirmations (auto-approve all)
/state auto mode, project dir, history size
/cwd show the project directory
/clear forget the conversation history
/quit exit

The agent keeps the last few finished tasks as context. It stops after 30 model round-trips per task; the daemon API itself is stateless.

Requirements

  • Python 3.11+ (stdlib only; tomllib is included)
  • ollama listening on the address from config.toml

Tests

python3 -m unittest test_client.py -v   # unit tests (no network)
python3 test_e2e.py                     # fake ollama + daemon + client
S
Description
ein terminal coding agent, der die ollama API nutzt
Readme MIT
78 KiB
Languages
Python 99.8%
Shell 0.2%