Skip to main content

Self-contained agentic CLI for local LLMs — REPL + one-shot, with tool whitelisting, SQLite audit, and per-call artifact bundles.

Project description

johnalin

Self-contained agentic CLI for local LLMs. REPL + one-shot CLI, with tool whitelisting, SQLite audit, and per-call artifact bundles. The production-grade discipline most local-LLM frameworks lack — in 1 ~MB pip install.

PyPI Python License: MIT


What you get

pip install johnalin
ollama pull qwen2.5:7b              # or any OAI-compatible local model
johnalin                            # interactive REPL
johnalin-cli --tools glob,bash -m "count .py files in this repo"   # one-shot

Two binaries, one package, zero servers to run separately. Talks to ollama (or any OpenAI-compatible local backend) directly, runs the tool loop in-process, writes a forensic audit bundle for every call.

Why this exists — four things most local-agent CLIs skip

1. Tool whitelisting per call (system-prompt 12K → 2K)

Most frameworks dump every registered tool into the system prompt on every turn. johnalin lets you specify exactly which tools the agent gets per call:

johnalin-cli --tools file_read,grep -m "find TODO comments under src/"
johnalin-cli --tools bash -m "show the last 3 git commits"

Smaller system prompt = lower latency, less "lazy" model behaviour, less cost on hosted backends.

2. Instrumented artifact bundles

Every call writes a forensic record under ~/.johnalin/artifacts/YYYYMMDD/iter_<utc>Z/<run_id>/:

metadata.json   — run_id, agent_tag, host, timings, context-fit verdict
request.json    — what we sent (message + tools + cwd + model)
response.json   — what we got (text + stopped reason + status)
trace.json      — every event the engine emitted (turn_start, usage,
                  tool_call (with args + result), turn_end, etc.)

Real metadata.json from the showcase below:

{
  "run_id": "cli_f4ab113608f5",
  "executor": "johnalin",
  "duration_ms": 3830,
  "context": {
    "peak_prompt_tokens": 498,
    "max_context": 16384,
    "utilization": 0.03,
    "fits": true,
    "risk": false
  }
}

3. SQLite history shared by REPL + CLI

Both johnalin and johnalin-cli log every turn to ~/.johnalin/history.db with the same schema. Grep across modes, query timings, replay turns:

SELECT run_id, model, stopped, turns, in_tokens, out_tokens, duration_ms, substr(input_text,1,55)
FROM turns ORDER BY ts DESC LIMIT 5;

4. Exit-code discipline (compose meta-agents reliably)

johnalin-cli returns distinct exit codes so wrappers can branch deterministically:

Exit Meaning
0 stop — model finished cleanly
1 tool_calls_unparseable — model emitted broken JSON args
2 repeat_loop — same tool call twice in a row, killed
3 max_turns — tool loop hit the cap without finishing
4 http/network error reaching ollama
5 bad CLI args
johnalin-cli --tools file_read,grep -m "$prompt" || handle_failure $?

Showcase (live output, captured 2026-05-07)

Three real johnalin-cli invocations against a local Qwen 27B running in ollama. Replicate with:

export OLLAMA_MODEL=qwen2.5:7b   # or whatever model you pulled
export JOHNALIN_HOME=/tmp/johnalin_showcase

Demo 1 — plain chat (no tools)

$ johnalin-cli --tools NONE -m "/no_think Reply with exactly: hello from johnalin v0.1.0"

The user wants me to reply with a specific string. I will output that string.

hello from johnalin v0.1.0
run_id=cli_0b5b0a23321e stopped=stop duration_ms=10461

Demo 2 — tool-use (glob counts files)

$ johnalin-cli --tools glob -m "Use glob with pattern '**/*.py' and root='johnalin'.
                                Reply with just the count." --max-turns 4

18
run_id=cli_f4ab113608f5 stopped=stop duration_ms=3830

The model emitted a single glob({"pattern":"**/*.py","root":"johnalin"}) call, got 18 file paths back, and replied with the integer. 2 turns, 3.8 seconds.

Demo 3 — tool-use (file_read summarises)

$ johnalin-cli --tools file_read -m "Read /tmp/test_readme.md.
                                     Reply with just the line count." --max-turns 3

5
run_id=cli_3b18c9f746da stopped=stop duration_ms=1831

What landed on disk

$ ls /tmp/johnalin_showcase/
artifacts/    history.db

$ find /tmp/johnalin_showcase/artifacts -name '*.json' | wc -l
12   # 4 files × 3 demos

$ sqlite3 /tmp/johnalin_showcase/history.db \
    "SELECT run_id, stopped, turns, in_tokens, duration_ms FROM turns"
cli_0b5b0a23321e | stop | 1 | 135 | 10461
cli_f4ab113608f5 | stop | 2 | 498 | 3830
cli_3b18c9f746da | stop | 2 | 438 | 1831

A real trace.json excerpt (Demo 2)

{
  "events": [
    { "type": "system_prompt_built", "systemPromptChars": 440, "toolCount": 1 },
    { "type": "turn_start", "turn": 0 },
    { "type": "usage", "turn": 0, "prompt_tokens": 348, "completion_tokens": 82 },
    { "type": "assistant_message", "turn": 0, "finish_reason": "tool_calls", "tool_call_count": 1 },
    { "type": "tool_call", "turn": 0, "name": "glob",
      "args": { "pattern": "**/*.py", "root": "johnalin" }, "ok": true,
      "result_preview": "johnalin/__init__.py\njohnalin/__main__.py\njohnalin/audit.py\n..." },
    { "type": "turn_end", "turn": 0, "stopped": "continued" },
    { "type": "turn_start", "turn": 1 },
    { "type": "assistant_message", "turn": 1, "finish_reason": "stop" },
    { "type": "turn_end", "turn": 1, "stopped": "stop" }
  ]
}

Every tool call captured with name, args, ok, result_preview, result_chars — the exact debug surface most local-LLM CLIs don't give you.


Install

pip install johnalin

# Backend: any OpenAI-compatible local LLM
# Easiest is ollama:
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen2.5:7b              # ~4.7 GB, runs on 8 GB VRAM
ollama serve &                       # in another terminal

Then:

johnalin                                       # REPL
johnalin-cli --tools NONE -m "say hi"         # one-shot smoke test

Configuration

All paths configurable. Zero hardcoded paths anywhere in the package.

Env var Default Purpose
OLLAMA_BASE_URL http://127.0.0.1:11434/v1 OpenAI-compat endpoint
OLLAMA_MODEL qwen2.5:7b Model id sent to the backend
JOHNALIN_NUM_CTX 16384 Context window passed as options.num_ctx
JOHNALIN_HOME ~/.johnalin Base dir for db + artifacts
JOHNALIN_DB $JOHNALIN_HOME/history.db SQLite history
JOHNALIN_ARTIFACTS $JOHNALIN_HOME/artifacts Per-call bundle root
JOHNALIN_MAX_TURNS 10 Tool-loop cap
JOHNALIN_TIMEOUT 900 HTTP timeout to backend (seconds)
JOHNALIN_AGENT_TAG user Stamped into every metadata.json

CLI flags (--model, --num-ctx, --base-url, --max-turns, --cwd) override env per call.

Built-in tools (v0.1)

Tool Schema Purpose
bash command, [timeout] Run a shell command; returns [exit N]\n<output> capped at 16 KB
file_read path, [offset], [limit] Read UTF-8 file with line numbers, 64 KB cap
file_write path, content Overwrite text file (creates parent dirs)
glob pattern, [root] Find files by glob (supports **), max 200 results
grep pattern, [path], [glob] Search file contents (uses rg if available)

Custom tools register at runtime — see examples/custom_tool.py.

REPL (johnalin)

  • Multi-line input: paste freely, Esc-Enter (or Alt-Enter) submits
  • Slash commands: /tools, /notools, /stats, /model, /help, /exit
  • Per-turn debug panel: input, assistant reply (markdown rendered), tool calls with args + result preview, summary table with token counts + context-fit verdict + duration + artifact path
  • Every prompt runs against a fresh context (no chat-history accumulation in memory; SQLite + scrollback are your durable history)
  • Auto-detects upstream health on startup; gives you a clear "ollama not running, start it like this" message if down

CLI (johnalin-cli)

johnalin-cli --tools <list>|NONE  --message <text>
             [--max-turns N]      [--cwd DIR]
             [--model M]          [--base-url URL]   [--num-ctx N]
             [--no-audit]         [--quiet]

stdout: model reply text only. stderr: run_id=... stopped=... duration_ms=... (and warnings).

Development

git clone https://github.com/wolfmib/johnalin.git
cd johnalin
python -m venv .venv && source .venv/bin/activate
pip install -e .[dev]
pytest tests/ -v        # 26 tests
ruff check johnalin tests

License

MIT — see LICENSE.

Acknowledgements

The architecture (tool whitelisting + audit bundling + exit-code discipline) was iterated on over months in a private fork before being lifted into this generic, self-contained release. If you've built local-LLM agents and hit the "system-prompt bloat" problem, this is the production-grade discipline that fixes it.

Comparable projects: OpenWebUI, Continue, LangChain, AutoGPT — all powerful, none combine all four differentiators above in one ~600-line agentic CLI you can pip install in 5 seconds.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

johnalin-0.1.0.tar.gz (32.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

johnalin-0.1.0-py3-none-any.whl (31.7 kB view details)

Uploaded Python 3

File details

Details for the file johnalin-0.1.0.tar.gz.

File metadata

  • Download URL: johnalin-0.1.0.tar.gz
  • Upload date:
  • Size: 32.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for johnalin-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6dc6c6b792465007730a3215ede284f370c2112e81ab664e7c3810febad2aea4
MD5 54b63123b88ccf917ee9beac418d4bf5
BLAKE2b-256 8ce0509014d0cefda14ea236ac5e1dbbc3002f0ceaf80fd9700fa08d385ad3b3

See more details on using hashes here.

File details

Details for the file johnalin-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: johnalin-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for johnalin-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 328a256214f0ac477d595f7c5727d4a9371907867fa374790ddc8078484bcdb5
MD5 f78b5be212a2d94e9519de8e4e9752a1
BLAKE2b-256 5fe89a3768f7a073ae5557c16a9d723b037d3caebb82b7cd0b39fb9f8f5c85e3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page