ollama-coding-agent 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hanlin Chen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,222 @@
1
+ Metadata-Version: 2.4
2
+ Name: ollama-coding-agent
3
+ Version: 0.1.0
4
+ Summary: Local coding agent driving Qwen Coder (or any Ollama-compatible model) through a sandboxed toolset via MCP
5
+ License-Expression: MIT
6
+ Classifier: Environment :: Console
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Topic :: Software Development :: Build Tools
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: httpx>=0.28
14
+ Requires-Dist: rich>=15.0
15
+ Requires-Dist: typer>=0.27
16
+ Requires-Dist: mcp>=1.27
17
+ Dynamic: license-file
18
+
19
+ # Local Coding Agent (Ollama + Qwen Coder)
20
+
21
+ ## Setup
22
+ ```bash
23
+ ollama pull qwen3-coder:30b
24
+ pip install -r requirements.txt
25
+ ```
26
+ No `ollama` python package required — the agent talks to an OpenAI-compatible
27
+ chat-completions endpoint (`/api/v1/chat/completions`) directly over HTTP via
28
+ `httpx`. This works against Ollama itself or a gateway in front of it (e.g.
29
+ Open WebUI) — point `--ollama-host` at whichever one you're running.
30
+
31
+ ## Run
32
+ ```bash
33
+ python cli.py "Add type hints to utils.py, then run the test suite" \
34
+ --project-root ./myrepo
35
+ ```
36
+ Add `--auto-approve` to skip confirmation prompts (only in an already-sandboxed
37
+ environment, e.g. a container you're fine getting wiped). Run `python cli.py --help`
38
+ for the full option list — it's a Typer app, so `--help` is auto-generated and
39
+ kept in sync with the code.
40
+
41
+ Omit the task string to drop into an interactive session instead of a
42
+ one-shot run — see [Session management](#session-management) below.
43
+
44
+ ## What makes this "production grade" vs. the first draft
45
+
46
+ | Concern | First draft | This version |
47
+ |---|---|---|
48
+ | Editing existing files | Only full overwrite via `write_file` | `edit_file` does exact unique-match replace + shows a unified diff, `write_file` refuses to clobber existing files |
49
+ | Path safety | None — agent could read/write anywhere | Every path resolved and checked against `project_root`; escapes raise `SandboxError` |
50
+ | Shell safety | Ran anything, unbounded | Denylist for destructive patterns (`rm -rf /`, `sudo`, fork bombs, etc.), timeout, output truncation |
51
+ | Human oversight | None | Write/edit/shell calls pause for approval unless the tool is in `safe_tools` or `auto_approve=True` |
52
+ | Model reliability | Assumed clean tool-call JSON | Retries with backoff on API errors; malformed tool-call args are caught and reported back to the model instead of crashing |
53
+ | Context window | Unbounded growth | Char-budget trimming keeps the running conversation under `context_char_budget` |
54
+ | Observability | `print()` only | Structured log file (`agent_run.log`) recording every model call, tool call, args, and result |
55
+ | Config | Hardcoded constants | `AgentConfig` dataclass — one place to tune model, sandbox root, limits, policy |
56
+ | Sessions | Each run started from a blank conversation | Every message is persisted to SQLite (`session_store.py`); resume by id or name, or run interactively |
57
+ | Codebase search | `grep` piped through a subprocess | Pure-Python `search_files` (regex + glob filter, skips `.git`/`node_modules`/etc.) and a `glob_files` tool for pattern-based file discovery |
58
+
59
+ ## Still recommended before real production use
60
+
61
+ 1. **Run it in a container**, not on your host. The path sandbox and shell
62
+ denylist reduce risk but are not a substitute for OS-level isolation —
63
+ treat `run_shell` as "can execute arbitrary code" and contain the blast
64
+ radius accordingly (Docker, gVisor, a disposable VM).
65
+ 2. **Version control everything.** Require the project root to be a git repo
66
+ and commit before each run, so any agent change is a reviewable diff you
67
+ can revert.
68
+ 3. **Rate/step limits per user** if this is exposed to a team, not just you.
69
+ 4. **Swap the char-based context trimming for a real tokenizer** if you hit
70
+ context issues in practice — it's a rough approximation.
71
+ 5. **Add tests for the tools module** (`tools.py`) in your CI — the sandbox
72
+ check is the one thing you really don't want to regress silently.
73
+ 6. Qwen3-Coder's native tool-calling is solid but not perfect at this size —
74
+ watch the log for `BAD ARGS` entries; if they're frequent, consider a
75
+ larger quant or `qwen2.5-coder:32b` (dense, less agentic-tuned but very
76
+ reliable on straightforward edits).
77
+
78
+ ## Intent parsing
79
+
80
+ Before the agent takes any action, the raw task string is parsed by the model
81
+ (in strict JSON mode, no tools) into structured intent:
82
+
83
+ ```json
84
+ {
85
+ "task_type": "bugfix",
86
+ "summary": "Fix add() which subtracts instead of adding",
87
+ "target_files": ["math_utils.py"],
88
+ "constraints": [],
89
+ "risk_level": "low"
90
+ }
91
+ ```
92
+
93
+ This gets injected into the conversation as a system message (with each
94
+ target file tagged `exists` or `new` in the sandbox), so the model starts
95
+ with grounded structure instead of just the raw sentence. Two things follow
96
+ from this automatically:
97
+
98
+ - **High-risk tasks force approval**, even if you ran with `--auto-approve`.
99
+ Detected via `risk_level: "high"` (deletion, deploys, migrations, etc.).
100
+ - **Malformed or failed parsing degrades gracefully** — after retries, it
101
+ falls back to `task_type: "other"` with `confident=False` logged, and the
102
+ agent still runs on the raw task text rather than blocking.
103
+
104
+ Skip it with `--skip-intent-parsing` if you want lower latency on simple
105
+ tasks, or point it at a smaller/faster model with `--intent-model`.
106
+
107
+ ## Session management
108
+
109
+ Every message in the conversation — system, user, assistant, tool results —
110
+ is persisted to a SQLite file (`agent_sessions.db` by default, `--db-path` to
111
+ change it) as the run happens, via `session_store.py`. A session is done the
112
+ moment it's created; nothing extra to opt into.
113
+
114
+ **Resume a previous run:**
115
+ ```bash
116
+ python cli.py "Add type hints to utils.py" --session-name utils-typing
117
+ # ...later, in the same or a different terminal...
118
+ python cli.py --resume utils-typing "Also add docstrings"
119
+ ```
120
+ `--resume` accepts either the session id it printed at the end of a run, or
121
+ the `--session-name` you gave it. `--session-name` is optional — without it
122
+ you just get an 8-character id. When you resume, the prior conversation is
123
+ printed before the run continues (assistant replies rendered the same
124
+ Markdown-panel way they looked the first time), so it's visibly clear that
125
+ context carried over rather than just trusting it happened in the background.
126
+
127
+ **Browse saved sessions:**
128
+ ```bash
129
+ python cli.py --list-sessions
130
+ ```
131
+ Shows id, name, status (`running` / `done` / `max_steps` / `error`), last
132
+ updated time, model, and the original task for each session.
133
+
134
+ **Delete a session:**
135
+ ```bash
136
+ python cli.py --delete-session utils-typing
137
+ ```
138
+ Removes the session and its full message history. Also available as
139
+ `/delete <id-or-name>` from inside interactive mode.
140
+
141
+ **Interactive mode** — omit the task argument entirely to get a REPL instead
142
+ of a one-shot run:
143
+ ```bash
144
+ python cli.py --project-root ./myrepo # fresh session, prompts for input
145
+ python cli.py --resume utils-typing # resumes and prompts for input
146
+ ```
147
+ Type a task and press enter to run it; the conversation (and the MCP tool
148
+ connection) stays alive between turns, so follow-ups don't pay the cost of
149
+ re-parsing intent or re-spawning the tool server. Special inputs:
150
+ - `/sessions` — list saved sessions without leaving the REPL
151
+ - `/delete <id-or-name>` — delete a saved session without leaving the REPL
152
+ - `/exit` or `/quit` (or Ctrl-D / Ctrl-C) — leave
153
+
154
+ A spinner shows while waiting on the model (initial intent parsing and every
155
+ turn), including a live retry counter if a call fails transiently and gets
156
+ retried — so a slow or cold-loading model doesn't look like it's hung.
157
+
158
+ ## Terminal UI
159
+
160
+ `ui.py` renders everything through [rich](https://github.com/Textualize/rich):
161
+ banner + parsed intent as a panel, each step with a colored ✓/✗, `edit_file`
162
+ diffs and `write_file` new-file content syntax-highlighted by extension,
163
+ approval prompts that show the actual diff/command *before* you approve —
164
+ not just the raw args — and the final response rendered as Markdown (headers,
165
+ lists, code blocks) rather than literal text.
166
+
167
+ If `rich` isn't installed, `agent.py` and `cli.py` both detect the missing
168
+ import and fall back to plain `print()` — nothing breaks, it just looks
169
+ like the original CLI.
170
+
171
+ ## Tools as an MCP server
172
+
173
+ The sandboxed tools now live behind a real MCP server (`mcp_server.py`),
174
+ not inline in the agent. The agent is an MCP *client* — it spawns the
175
+ server as a subprocess (stdio transport) scoped to `--project-root`, fetches
176
+ the tool list, converts it to Ollama's function-calling schema, and calls
177
+ tools through the MCP session instead of Python function calls directly.
178
+
179
+ ```
180
+ cli.py → agent.py → mcp_client.py ⇄ (stdio) ⇄ mcp_server.py → tools.py
181
+ ```
182
+
183
+ What this buys you:
184
+ - **Any MCP client can use the same tools** — Claude Desktop, another agent
185
+ framework, a different model entirely — all sharing the identical
186
+ sandbox, denylist, and diff-preview logic in `tools.py`.
187
+ - **The server is independently runnable and testable**:
188
+ ```bash
189
+ AGENT_PROJECT_ROOT=/path/to/repo python mcp_server.py
190
+ ```
191
+ - Internal tools (`_preview_edit`, `_preview_write`, `_file_exists`) are
192
+ underscore-prefixed and filtered out of what's shown to the LLM in
193
+ `list_llm_tools()` — the agent still calls them directly for approval
194
+ previews and intent validation, the model never sees them.
195
+
196
+ The agent loop is now `async` end-to-end (an MCP session requires it);
197
+ `cli.py` runs it via `asyncio.run()`. Nothing about the approval flow,
198
+ retries, logging, or intent parsing changed — they just go through
199
+ `MCPToolClient` now instead of a local `Tools` instance.
200
+
201
+ ## Files
202
+ - `config.py` — all tunables in one dataclass
203
+ - `intent.py` — parses the freeform task into structured intent (task_type, target_files, constraints, risk_level)
204
+ - `tools.py` — sandboxed tool implementations (used by `mcp_server.py`, not called directly by the agent anymore)
205
+ - `mcp_server.py` — MCP server exposing those tools over stdio
206
+ - `mcp_client.py` — async MCP client the agent uses to reach the server
207
+ - `ollama_client.py` — raw `httpx` client for the model's OpenAI-compatible chat-completions endpoint — no `ollama` package dependency
208
+ - `session_store.py` — SQLite persistence for sessions and their full message history (resume/list/interactive mode)
209
+ - `ui.py` — rich terminal rendering (diffs, panels, approval prompts, session tables) — purely presentational
210
+ - `agent.py` — the loop: parse intent, call model, approve, execute via MCP, persist, repeat
211
+ - `cli.py` — command-line entry point (Typer — `python cli.py --help` for auto-generated, always-in-sync docs)
212
+
213
+ Point at a non-default Ollama host with `--ollama-host http://some-host:11434`
214
+ or the `OLLAMA_HOST` env var (checked in that order).
215
+
216
+ If your Ollama endpoint sits behind an authenticated proxy, set the key via
217
+ environment variable rather than the CLI flag — it avoids the token landing
218
+ in your shell history:
219
+ ```bash
220
+ export OLLAMA_API_KEY="sk-..."
221
+ python cli.py "task" --project-root ./repo --ollama-host http://your-host:8080
222
+ ```
@@ -0,0 +1,204 @@
1
+ # Local Coding Agent (Ollama + Qwen Coder)
2
+
3
+ ## Setup
4
+ ```bash
5
+ ollama pull qwen3-coder:30b
6
+ pip install -r requirements.txt
7
+ ```
8
+ No `ollama` python package required — the agent talks to an OpenAI-compatible
9
+ chat-completions endpoint (`/api/v1/chat/completions`) directly over HTTP via
10
+ `httpx`. This works against Ollama itself or a gateway in front of it (e.g.
11
+ Open WebUI) — point `--ollama-host` at whichever one you're running.
12
+
13
+ ## Run
14
+ ```bash
15
+ python cli.py "Add type hints to utils.py, then run the test suite" \
16
+ --project-root ./myrepo
17
+ ```
18
+ Add `--auto-approve` to skip confirmation prompts (only in an already-sandboxed
19
+ environment, e.g. a container you're fine getting wiped). Run `python cli.py --help`
20
+ for the full option list — it's a Typer app, so `--help` is auto-generated and
21
+ kept in sync with the code.
22
+
23
+ Omit the task string to drop into an interactive session instead of a
24
+ one-shot run — see [Session management](#session-management) below.
25
+
26
+ ## What makes this "production grade" vs. the first draft
27
+
28
+ | Concern | First draft | This version |
29
+ |---|---|---|
30
+ | Editing existing files | Only full overwrite via `write_file` | `edit_file` does exact unique-match replace + shows a unified diff, `write_file` refuses to clobber existing files |
31
+ | Path safety | None — agent could read/write anywhere | Every path resolved and checked against `project_root`; escapes raise `SandboxError` |
32
+ | Shell safety | Ran anything, unbounded | Denylist for destructive patterns (`rm -rf /`, `sudo`, fork bombs, etc.), timeout, output truncation |
33
+ | Human oversight | None | Write/edit/shell calls pause for approval unless the tool is in `safe_tools` or `auto_approve=True` |
34
+ | Model reliability | Assumed clean tool-call JSON | Retries with backoff on API errors; malformed tool-call args are caught and reported back to the model instead of crashing |
35
+ | Context window | Unbounded growth | Char-budget trimming keeps the running conversation under `context_char_budget` |
36
+ | Observability | `print()` only | Structured log file (`agent_run.log`) recording every model call, tool call, args, and result |
37
+ | Config | Hardcoded constants | `AgentConfig` dataclass — one place to tune model, sandbox root, limits, policy |
38
+ | Sessions | Each run started from a blank conversation | Every message is persisted to SQLite (`session_store.py`); resume by id or name, or run interactively |
39
+ | Codebase search | `grep` piped through a subprocess | Pure-Python `search_files` (regex + glob filter, skips `.git`/`node_modules`/etc.) and a `glob_files` tool for pattern-based file discovery |
40
+
41
+ ## Still recommended before real production use
42
+
43
+ 1. **Run it in a container**, not on your host. The path sandbox and shell
44
+ denylist reduce risk but are not a substitute for OS-level isolation —
45
+ treat `run_shell` as "can execute arbitrary code" and contain the blast
46
+ radius accordingly (Docker, gVisor, a disposable VM).
47
+ 2. **Version control everything.** Require the project root to be a git repo
48
+ and commit before each run, so any agent change is a reviewable diff you
49
+ can revert.
50
+ 3. **Rate/step limits per user** if this is exposed to a team, not just you.
51
+ 4. **Swap the char-based context trimming for a real tokenizer** if you hit
52
+ context issues in practice — it's a rough approximation.
53
+ 5. **Add tests for the tools module** (`tools.py`) in your CI — the sandbox
54
+ check is the one thing you really don't want to regress silently.
55
+ 6. Qwen3-Coder's native tool-calling is solid but not perfect at this size —
56
+ watch the log for `BAD ARGS` entries; if they're frequent, consider a
57
+ larger quant or `qwen2.5-coder:32b` (dense, less agentic-tuned but very
58
+ reliable on straightforward edits).
59
+
60
+ ## Intent parsing
61
+
62
+ Before the agent takes any action, the raw task string is parsed by the model
63
+ (in strict JSON mode, no tools) into structured intent:
64
+
65
+ ```json
66
+ {
67
+ "task_type": "bugfix",
68
+ "summary": "Fix add() which subtracts instead of adding",
69
+ "target_files": ["math_utils.py"],
70
+ "constraints": [],
71
+ "risk_level": "low"
72
+ }
73
+ ```
74
+
75
+ This gets injected into the conversation as a system message (with each
76
+ target file tagged `exists` or `new` in the sandbox), so the model starts
77
+ with grounded structure instead of just the raw sentence. Two things follow
78
+ from this automatically:
79
+
80
+ - **High-risk tasks force approval**, even if you ran with `--auto-approve`.
81
+ Detected via `risk_level: "high"` (deletion, deploys, migrations, etc.).
82
+ - **Malformed or failed parsing degrades gracefully** — after retries, it
83
+ falls back to `task_type: "other"` with `confident=False` logged, and the
84
+ agent still runs on the raw task text rather than blocking.
85
+
86
+ Skip it with `--skip-intent-parsing` if you want lower latency on simple
87
+ tasks, or point it at a smaller/faster model with `--intent-model`.
88
+
89
+ ## Session management
90
+
91
+ Every message in the conversation — system, user, assistant, tool results —
92
+ is persisted to a SQLite file (`agent_sessions.db` by default, `--db-path` to
93
+ change it) as the run happens, via `session_store.py`. A session is done the
94
+ moment it's created; nothing extra to opt into.
95
+
96
+ **Resume a previous run:**
97
+ ```bash
98
+ python cli.py "Add type hints to utils.py" --session-name utils-typing
99
+ # ...later, in the same or a different terminal...
100
+ python cli.py --resume utils-typing "Also add docstrings"
101
+ ```
102
+ `--resume` accepts either the session id it printed at the end of a run, or
103
+ the `--session-name` you gave it. `--session-name` is optional — without it
104
+ you just get an 8-character id. When you resume, the prior conversation is
105
+ printed before the run continues (assistant replies rendered the same
106
+ Markdown-panel way they looked the first time), so it's visibly clear that
107
+ context carried over rather than just trusting it happened in the background.
108
+
109
+ **Browse saved sessions:**
110
+ ```bash
111
+ python cli.py --list-sessions
112
+ ```
113
+ Shows id, name, status (`running` / `done` / `max_steps` / `error`), last
114
+ updated time, model, and the original task for each session.
115
+
116
+ **Delete a session:**
117
+ ```bash
118
+ python cli.py --delete-session utils-typing
119
+ ```
120
+ Removes the session and its full message history. Also available as
121
+ `/delete <id-or-name>` from inside interactive mode.
122
+
123
+ **Interactive mode** — omit the task argument entirely to get a REPL instead
124
+ of a one-shot run:
125
+ ```bash
126
+ python cli.py --project-root ./myrepo # fresh session, prompts for input
127
+ python cli.py --resume utils-typing # resumes and prompts for input
128
+ ```
129
+ Type a task and press enter to run it; the conversation (and the MCP tool
130
+ connection) stays alive between turns, so follow-ups don't pay the cost of
131
+ re-parsing intent or re-spawning the tool server. Special inputs:
132
+ - `/sessions` — list saved sessions without leaving the REPL
133
+ - `/delete <id-or-name>` — delete a saved session without leaving the REPL
134
+ - `/exit` or `/quit` (or Ctrl-D / Ctrl-C) — leave
135
+
136
+ A spinner shows while waiting on the model (initial intent parsing and every
137
+ turn), including a live retry counter if a call fails transiently and gets
138
+ retried — so a slow or cold-loading model doesn't look like it's hung.
139
+
140
+ ## Terminal UI
141
+
142
+ `ui.py` renders everything through [rich](https://github.com/Textualize/rich):
143
+ banner + parsed intent as a panel, each step with a colored ✓/✗, `edit_file`
144
+ diffs and `write_file` new-file content syntax-highlighted by extension,
145
+ approval prompts that show the actual diff/command *before* you approve —
146
+ not just the raw args — and the final response rendered as Markdown (headers,
147
+ lists, code blocks) rather than literal text.
148
+
149
+ If `rich` isn't installed, `agent.py` and `cli.py` both detect the missing
150
+ import and fall back to plain `print()` — nothing breaks, it just looks
151
+ like the original CLI.
152
+
153
+ ## Tools as an MCP server
154
+
155
+ The sandboxed tools now live behind a real MCP server (`mcp_server.py`),
156
+ not inline in the agent. The agent is an MCP *client* — it spawns the
157
+ server as a subprocess (stdio transport) scoped to `--project-root`, fetches
158
+ the tool list, converts it to Ollama's function-calling schema, and calls
159
+ tools through the MCP session instead of Python function calls directly.
160
+
161
+ ```
162
+ cli.py → agent.py → mcp_client.py ⇄ (stdio) ⇄ mcp_server.py → tools.py
163
+ ```
164
+
165
+ What this buys you:
166
+ - **Any MCP client can use the same tools** — Claude Desktop, another agent
167
+ framework, a different model entirely — all sharing the identical
168
+ sandbox, denylist, and diff-preview logic in `tools.py`.
169
+ - **The server is independently runnable and testable**:
170
+ ```bash
171
+ AGENT_PROJECT_ROOT=/path/to/repo python mcp_server.py
172
+ ```
173
+ - Internal tools (`_preview_edit`, `_preview_write`, `_file_exists`) are
174
+ underscore-prefixed and filtered out of what's shown to the LLM in
175
+ `list_llm_tools()` — the agent still calls them directly for approval
176
+ previews and intent validation, the model never sees them.
177
+
178
+ The agent loop is now `async` end-to-end (an MCP session requires it);
179
+ `cli.py` runs it via `asyncio.run()`. Nothing about the approval flow,
180
+ retries, logging, or intent parsing changed — they just go through
181
+ `MCPToolClient` now instead of a local `Tools` instance.
182
+
183
+ ## Files
184
+ - `config.py` — all tunables in one dataclass
185
+ - `intent.py` — parses the freeform task into structured intent (task_type, target_files, constraints, risk_level)
186
+ - `tools.py` — sandboxed tool implementations (used by `mcp_server.py`, not called directly by the agent anymore)
187
+ - `mcp_server.py` — MCP server exposing those tools over stdio
188
+ - `mcp_client.py` — async MCP client the agent uses to reach the server
189
+ - `ollama_client.py` — raw `httpx` client for the model's OpenAI-compatible chat-completions endpoint — no `ollama` package dependency
190
+ - `session_store.py` — SQLite persistence for sessions and their full message history (resume/list/interactive mode)
191
+ - `ui.py` — rich terminal rendering (diffs, panels, approval prompts, session tables) — purely presentational
192
+ - `agent.py` — the loop: parse intent, call model, approve, execute via MCP, persist, repeat
193
+ - `cli.py` — command-line entry point (Typer — `python cli.py --help` for auto-generated, always-in-sync docs)
194
+
195
+ Point at a non-default Ollama host with `--ollama-host http://some-host:11434`
196
+ or the `OLLAMA_HOST` env var (checked in that order).
197
+
198
+ If your Ollama endpoint sits behind an authenticated proxy, set the key via
199
+ environment variable rather than the CLI flag — it avoids the token landing
200
+ in your shell history:
201
+ ```bash
202
+ export OLLAMA_API_KEY="sk-..."
203
+ python cli.py "task" --project-root ./repo --ollama-host http://your-host:8080
204
+ ```
@@ -0,0 +1,4 @@
1
+ """Local coding agent — drives Qwen Coder (or any Ollama-compatible model)
2
+ through a sandboxed set of file/shell tools via MCP."""
3
+
4
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Enables `python -m coding_agent`."""
2
+
3
+ from .cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()