Metadata-Version: 2.4
Name: johnalin
Version: 0.1.0
Summary: Self-contained agentic CLI for local LLMs — REPL + one-shot, with tool whitelisting, SQLite audit, and per-call artifact bundles.
Author: wolfmib
License: MIT License
        
        Copyright (c) 2026 wolfmib
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/wolfmib/johnalin
Project-URL: Issues, https://github.com/wolfmib/johnalin/issues
Project-URL: Repository, https://github.com/wolfmib/johnalin
Keywords: llm,ollama,agent,cli,repl,tool-calling,openai-compatible
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27
Requires-Dist: prompt_toolkit>=3.0
Requires-Dist: rich>=13.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Dynamic: license-file

# 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](https://img.shields.io/pypi/v/johnalin?color=blue)](https://pypi.org/project/johnalin/)
[![Python](https://img.shields.io/pypi/pyversions/johnalin)](https://pypi.org/project/johnalin/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

---

## What you get

```bash
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:

```bash
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:
```json
{
  "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:

```sql
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 |

```bash
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:
```bash
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)

```json
{
  "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

```bash
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:
```bash
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

```bash
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](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](https://github.com/open-webui/open-webui), [Continue](https://github.com/continuedev/continue), [LangChain](https://github.com/langchain-ai/langchain), [AutoGPT](https://github.com/Significant-Gravitas/AutoGPT) — all powerful, none combine all four differentiators above in one ~600-line agentic CLI you can pip install in 5 seconds.
