closecode-ai 0.3.0__tar.gz → 0.5.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.
Files changed (47) hide show
  1. closecode_ai-0.5.0/PKG-INFO +284 -0
  2. closecode_ai-0.5.0/README.md +259 -0
  3. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/agent.py +10 -1
  4. closecode_ai-0.5.0/closecode_ai.egg-info/PKG-INFO +284 -0
  5. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/closecode_ai.egg-info/SOURCES.txt +15 -1
  6. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/closecode_ai.egg-info/requires.txt +3 -0
  7. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/closecode_ai.egg-info/top_level.txt +5 -0
  8. closecode_ai-0.5.0/harness.py +529 -0
  9. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/main.py +9 -0
  10. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/modes.py +3 -1
  11. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/pyproject.toml +19 -1
  12. closecode_ai-0.5.0/tests/test_config.py +175 -0
  13. closecode_ai-0.5.0/tests/test_guardrails.py +357 -0
  14. closecode_ai-0.5.0/tests/test_harness.py +678 -0
  15. closecode_ai-0.5.0/tests/test_llm.py +332 -0
  16. closecode_ai-0.5.0/tests/test_modes.py +262 -0
  17. closecode_ai-0.5.0/tests/test_search.py +274 -0
  18. closecode_ai-0.5.0/tests/test_session.py +395 -0
  19. closecode_ai-0.5.0/tests/test_todos.py +271 -0
  20. closecode_ai-0.5.0/tests/test_token_tracker.py +241 -0
  21. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/tools.py +33 -1
  22. closecode_ai-0.5.0/tui.py +372 -0
  23. closecode_ai-0.5.0/tui_messages.py +167 -0
  24. closecode_ai-0.5.0/tui_theme.py +170 -0
  25. closecode_ai-0.5.0/tui_widgets.py +421 -0
  26. closecode_ai-0.5.0/ui.py +272 -0
  27. closecode_ai-0.5.0/ui_prompts.py +180 -0
  28. closecode_ai-0.5.0/ui_theme.py +79 -0
  29. closecode_ai-0.3.0/PKG-INFO +0 -252
  30. closecode_ai-0.3.0/README.md +0 -229
  31. closecode_ai-0.3.0/closecode_ai.egg-info/PKG-INFO +0 -252
  32. closecode_ai-0.3.0/harness.py +0 -200
  33. closecode_ai-0.3.0/tui.py +0 -1041
  34. closecode_ai-0.3.0/ui.py +0 -422
  35. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/closecode_ai.egg-info/dependency_links.txt +0 -0
  36. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/closecode_ai.egg-info/entry_points.txt +0 -0
  37. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/config.py +0 -0
  38. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/debug_response.py +0 -0
  39. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/guardrails.py +0 -0
  40. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/llm.py +0 -0
  41. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/mcp_tools.py +0 -0
  42. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/render.py +0 -0
  43. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/search.py +0 -0
  44. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/session.py +0 -0
  45. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/setup.cfg +0 -0
  46. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/todos.py +0 -0
  47. {closecode_ai-0.3.0 → closecode_ai-0.5.0}/token_tracker.py +0 -0
@@ -0,0 +1,284 @@
1
+ Metadata-Version: 2.4
2
+ Name: closecode-ai
3
+ Version: 0.5.0
4
+ Summary: CloseCode — an agentic terminal coding assistant (LangGraph + OpenRouter + MCP)
5
+ Author: Om Gite
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: langchain
10
+ Requires-Dist: langgraph
11
+ Requires-Dist: langchain-huggingface
12
+ Requires-Dist: langsmith
13
+ Requires-Dist: huggingface_hub
14
+ Requires-Dist: python-dotenv
15
+ Requires-Dist: requests
16
+ Requires-Dist: langchain-openai
17
+ Requires-Dist: langchain-mcp-adapters
18
+ Requires-Dist: mcp-server-git
19
+ Requires-Dist: langchain-openrouter
20
+ Requires-Dist: rich
21
+ Requires-Dist: pyfiglet
22
+ Requires-Dist: textual
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=8; extra == "dev"
25
+
26
+ <div align="center">
27
+
28
+ # CloseCode
29
+
30
+ **An agentic terminal coding assistant — LangGraph loop, sandboxed execution, and a full-screen TUI.**
31
+
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
33
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
34
+ <!-- Once CI exists, add: [![CI](https://github.com/omgite333/CLOSECODE/actions/workflows/ci.yml/badge.svg)](https://github.com/omgite333/CLOSECODE/actions) -->
35
+
36
+ <!-- SCREENSHOT: full-screen TUI on startup — banner, model name, workdir, tool list.
37
+ Capture: run `closecode`, wait for the banner, screenshot the terminal.
38
+ Save as: docs/banner.png -->
39
+ ![CloseCode banner](docs/banner.png)
40
+
41
+ </div>
42
+
43
+ ---
44
+
45
+ CloseCode reads a task, decides what to do, runs a tool, looks at the result, and repeats —
46
+ the same loop as OpenCode or Claude Code, built from scratch on LangGraph (agent loop),
47
+ LangChain (tool + model abstraction), and OpenRouter (model access). It runs entirely in
48
+ your terminal, in a full-screen TUI or a classic line-based mode, and every action it takes
49
+ on your files or shell goes through a sandboxed harness with permission prompts.
50
+
51
+
52
+ ## Why
53
+
54
+ Most people can't `pip install openai` and get an agent — the hard part isn't calling a
55
+ model, it's the loop around it: binding the right tools per mode, confirming risky actions,
56
+ persisting sessions, and stopping the model from doing something destructive. CloseCode is
57
+ that scaffolding, built openly, with open models via OpenRouter instead of a closed API.
58
+
59
+ ## Features
60
+
61
+ - **Full-screen TUI** (Textual) or a classic line-based REPL — same agent loop underneath,
62
+ switchable with `--no-tui`
63
+ - **Sandboxed execution** — file and shell operations are confined to a working directory;
64
+ path traversal (`../../etc/passwd`, absolute paths, Windows drive prefixes) is blocked
65
+ at the harness level, not just by convention
66
+ - **Plan / Build modes** — Plan mode literally never binds write/edit/bash tools to the
67
+ model, so it can explore and propose a plan with no possibility of a side effect
68
+ - **Guardrails** — four layers: input-scope filtering, destructive-command blocking
69
+ (`rm -rf /`, fork bombs, `curl | sh`), malicious-write scanning, and output redaction
70
+ - **Persistent sessions** — SQLite-backed conversation history; `/resume`, `/sessions`,
71
+ `--continue`
72
+ - **Todo tracking** — the agent maintains a visible task list for multi-step work
73
+ (`todo_write` / `todo_read`)
74
+ - **Code-aware search** — dedicated `grep`/`glob` tools (not raw shell), sandboxed and
75
+ available even in Plan mode since they're read-only
76
+ - **Git tools via MCP** — status, diff, log, commit, branches, through `mcp-server-git`
77
+ - **Any OpenRouter model** — free-tier models by default; switch with `/model`, browse
78
+ with `/models`
79
+ - **Per-user config** — your API key and default model persist in `~/.closecode/config.json`
80
+ (0600 permissions), independent of which directory you launch from
81
+
82
+ ## Install
83
+
84
+ ```bash
85
+ pipx install closecode-ai
86
+ closecode
87
+ ```
88
+
89
+ <!-- Adjust this section once published — this is the target state, not necessarily
90
+ live yet. Until it's on PyPI, use the git-based install below instead. -->
91
+
92
+ **From source, right now:**
93
+
94
+ ```bash
95
+ git clone https://github.com/omgite333/CLOSECODE.git
96
+ cd CLOSECODE
97
+ pip install -e .
98
+ closecode
99
+ ```
100
+
101
+ `pipx` is recommended over `pip` for the packaged version since it installs CLI tools into
102
+ an isolated environment and puts them straight on your `PATH`.
103
+
104
+ ## Tests
105
+
106
+ ```bash
107
+ pip install -e ".[dev]"
108
+ pytest
109
+ ```
110
+
111
+ 429 tests covering guardrails, sandbox path resolution, undo/checkpoints, background
112
+ processes, todo-store invariants, plan-mode tool filtering, session persistence, config
113
+ permissions, and the search tools. The suite is fully isolated: `config.py` and
114
+ `session.py` are redirected to temp dirs, no test touches the network, and nothing is
115
+ written to your real `~/.closecode`.
116
+
117
+ Some tests are marked `xfail` for **known bugs** rather than fixed behaviour — they
118
+ document a gap and will flip to passing when it's fixed. Run `pytest -rx` to list them.
119
+
120
+ ## Quick start
121
+
122
+ 1. Get a free API key at [openrouter.ai/settings/keys](https://openrouter.ai/settings/keys)
123
+ 2. Run `closecode` — on first launch it prompts for the key (input hidden) and offers to
124
+ save it to `~/.closecode/config.json` so you're not asked again
125
+ 3. Type a task:
126
+
127
+ ```
128
+ > find every place we call the old auth API and list the files
129
+ ```
130
+
131
+ <!-- SCREENSHOT: a permission prompt in action — "Allow agent to run: `grep -r ...`?"
132
+ This is worth showing on its own since it's the project's core safety story.
133
+ Save as: docs/permission-prompt.png -->
134
+ ![Permission prompt](docs/permission-prompt.png)
135
+
136
+ ## How it works
137
+
138
+ ```
139
+ user input
140
+ │
141
+ ▼
142
+ ┌──────────┐ binds tools for current mode ┌───────────────┐
143
+ │ agent │ ───────────────────────────────▶ │ LangGraph loop│
144
+ │ (llm.py) │ │ (agent.py) │
145
+ └──────────┘ └──────┬────────┘
146
+ │ tool call
147
+ ▼
148
+ ┌───────────────────────┐
149
+ │ Harness │
150
+ │ sandboxed fs + shell │──▶ guardrails.py
151
+ └───────────────────────┘ (blocks/scans)
152
+ │ result
153
+ ▼
154
+ ┌──────────────────────┐
155
+ │ Renderer interface │
156
+ │ (render.py) │
157
+ └──────┬──────────┬────┘
158
+ ▼ ▼
159
+ tui.py + tui_*.py ui.py + ui_*.py
160
+ (Textual) (classic)
161
+ ```
162
+
163
+ A single `Renderer` interface (`render.py`) decouples the agent loop from presentation, so
164
+ the Textual TUI and the classic REPL are two implementations of the same contract rather
165
+ than two copies of the agent logic.
166
+
167
+ Each frontend is a small set of single-purpose modules behind a facade, so the entry module
168
+ stays readable and the parts can be reused or tested on their own:
169
+
170
+ | Frontend | Facade | Split into |
171
+ |---|---|---|
172
+ | Textual | `tui.py` — app, message handlers, input, suggestions | `tui_theme.py` (banner, palette, CSS) · `tui_messages.py` (Message classes, `TuiRenderer`) · `tui_widgets.py` (conversation blocks, input box, modals, `ConfirmBridge`) |
173
+ | Classic | `ui.py` — the `print_*` / `stream_*` renderers | `ui_theme.py` (shared `Console`, palette, banner) · `ui_prompts.py` (permission dialog, input, `EscListener`) |
174
+
175
+ Each facade re-exports the names its split modules define, so `from ui import ...` and
176
+ `from tui import ...` keep working exactly as before.
177
+
178
+
179
+ ## Modes
180
+
181
+ | Mode | Command | Tools available | Use for |
182
+ |---|---|---|---|
183
+ | **Plan** | `/plan` | Read-only: `read_file`, `list_dir`, `grep`, `glob`, `tavily_search` | Exploring a codebase, proposing an approach with zero risk of a side effect |
184
+ | **Build** | `/build` | Everything, including `write_file`, `edit_file`, `bash`, `run_tests`, git tools | Actually making changes |
185
+
186
+ Plan mode isn't a prompt instruction the model can ignore — the write/edit/bash tools are
187
+ never bound to the model in the first place.
188
+
189
+ ## Tools
190
+
191
+ | Tool | Description |
192
+ |---|---|
193
+ | `read_file` / `write_file` / `edit_file` | Read, overwrite, or targeted find-and-replace on a file |
194
+ | `list_dir` | List a directory's contents |
195
+ | `glob` / `grep` | Find files by pattern / search file contents by regex — sandboxed, available in Plan mode |
196
+ | `bash` | Run a shell command in the sandbox (capped timeout, guardrail-checked) |
197
+ | `run_tests` | Run the project's test command and report pass/fail |
198
+ | `todo_write` / `todo_read` | Maintain a visible multi-step task list |
199
+ | `tavily_search` | Web search for current docs/APIs (requires `TAVILY_API_KEY`) |
200
+ | git tools (`status`, `diff`, `log`, `commit`, branches) | Via `mcp-server-git`, enabled with `AGENT_ENABLE_GIT=true` |
201
+
202
+ ## Commands
203
+
204
+ ```
205
+ /plan, /build switch modes
206
+ /models [filter] list OpenRouter models (free-tier first)
207
+ /model <id|number> switch model
208
+ /key update your OpenRouter API key
209
+ /sessions list saved conversations
210
+ /resume <id> switch to a saved session
211
+ /delete <id> delete a saved session
212
+ /usage token usage for this session
213
+ /clear start a new session
214
+ /help show all commands
215
+ ```
216
+
217
+ ## Configuration
218
+
219
+ All settings are environment variables (`.env`, or exported in your shell) — see
220
+ `.env.example` for the full list. Key ones:
221
+
222
+ | Variable | Default | Purpose |
223
+ |---|---|---|
224
+ | `OPENROUTER_API_KEY` | — | Required (or saved via `/key` into `~/.closecode/config.json`) |
225
+ | `OPENROUTER_MODEL` | `nvidia/nemotron-3.5-lightning:free` | Default model |
226
+ | `AGENT_WORKDIR` | `./sandbox` | Directory the agent is confined to |
227
+ | `AGENT_AUTO_APPROVE` | `false` | Skip permission prompts (guardrails still apply) |
228
+ | `AGENT_ENABLE_GIT` | `false` | Load git tools via MCP |
229
+ | `AGENT_DISABLE_GUARDRAILS` | `false` | Disable guardrails — trusted/isolated testing only |
230
+ | `TAVILY_API_KEY` | — | Enables `tavily_search` |
231
+ | `LANGCHAIN_API_KEY` | — | Enables LangSmith tracing |
232
+
233
+ ## Safety model
234
+
235
+ This is layered defense, not a single mechanism:
236
+
237
+ 1. **Input scope** — off-topic requests are redirected; clearly malicious requests
238
+ (keyloggers, phishing kits, account-hacking) are refused before reaching the model
239
+ 2. **Command blocking** — destructive shell commands are blocked before execution, even
240
+ with auto-approve on
241
+ 3. **Write scanning** — file writes/edits are scanned for malware indicators before
242
+ they're applied
243
+ 4. **Output redaction** — flagged content is scrubbed from conversation history
244
+ 5. **Sandboxed paths** — every file operation resolves through the harness, which refuses
245
+ to write outside the configured working directory regardless of how the path is phrased
246
+
247
+ These are conservative heuristics layered on top of the sandbox and per-action permission
248
+ prompts — not a formal guarantee. Shell commands currently run on the host inside a
249
+ path-restricted directory, not inside a container; see [Roadmap](#roadmap).
250
+
251
+ ## A note on model choice
252
+
253
+ Tool-calling reliability varies a lot across open models — this is the single biggest
254
+ factor in how well the agent performs. Frontier closed models are heavily trained for
255
+ reliable tool use; open models are improving but inconsistent. Roughly in order of
256
+ reliability, worth trying via `/model`:
257
+
258
+ - `qwen/qwen-2.5-coder-32b-instruct` — code-specialized, solid tool use, free tier available
259
+ - `qwen/qwen-2.5-72b-instruct` — strong, reliable, paid
260
+ - `meta-llama/llama-3.1-70b-instruct` — strong, reliable, paid
261
+ - `meta-llama/llama-3.1-8b-instruct` — fastest/cheapest, least reliable
262
+
263
+ If a smaller model frequently fails to call tools or hallucinates arguments, that's a
264
+ known gap between open and closed models on agentic tasks, not a bug here. Switching
265
+ models is the first thing to try before changing anything else.
266
+
267
+ ## Roadmap
268
+
269
+ - [x] Test suite (`pytest tests/`) — guardrails, sandbox path resolution, todo-store invariants
270
+ - [ ] CI (run the suite on push)
271
+ - [ ] Docker-based sandbox for shell execution, not just path restriction
272
+ - [ ] Client/server split — `build_graph()` behind FastAPI/WebSocket, thin streaming client
273
+ - [ ] PyPI release + prebuilt binaries (PyInstaller) for no-Python-required installs
274
+ - [ ] Homebrew tap
275
+
276
+ ## Contributing
277
+
278
+ Issues and PRs welcome. If you're adding a tool, follow the pattern in `tools.py` /
279
+ `search.py`: bind state via a module-level `bind_*()` function, keep it sandboxed to the
280
+ harness root, and add it to the Plan-mode allowlist only if it's genuinely read-only.
281
+
282
+ ## License
283
+
284
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,259 @@
1
+ <div align="center">
2
+
3
+ # CloseCode
4
+
5
+ **An agentic terminal coding assistant — LangGraph loop, sandboxed execution, and a full-screen TUI.**
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
9
+ <!-- Once CI exists, add: [![CI](https://github.com/omgite333/CLOSECODE/actions/workflows/ci.yml/badge.svg)](https://github.com/omgite333/CLOSECODE/actions) -->
10
+
11
+ <!-- SCREENSHOT: full-screen TUI on startup — banner, model name, workdir, tool list.
12
+ Capture: run `closecode`, wait for the banner, screenshot the terminal.
13
+ Save as: docs/banner.png -->
14
+ ![CloseCode banner](docs/banner.png)
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ CloseCode reads a task, decides what to do, runs a tool, looks at the result, and repeats —
21
+ the same loop as OpenCode or Claude Code, built from scratch on LangGraph (agent loop),
22
+ LangChain (tool + model abstraction), and OpenRouter (model access). It runs entirely in
23
+ your terminal, in a full-screen TUI or a classic line-based mode, and every action it takes
24
+ on your files or shell goes through a sandboxed harness with permission prompts.
25
+
26
+
27
+ ## Why
28
+
29
+ Most people can't `pip install openai` and get an agent — the hard part isn't calling a
30
+ model, it's the loop around it: binding the right tools per mode, confirming risky actions,
31
+ persisting sessions, and stopping the model from doing something destructive. CloseCode is
32
+ that scaffolding, built openly, with open models via OpenRouter instead of a closed API.
33
+
34
+ ## Features
35
+
36
+ - **Full-screen TUI** (Textual) or a classic line-based REPL — same agent loop underneath,
37
+ switchable with `--no-tui`
38
+ - **Sandboxed execution** — file and shell operations are confined to a working directory;
39
+ path traversal (`../../etc/passwd`, absolute paths, Windows drive prefixes) is blocked
40
+ at the harness level, not just by convention
41
+ - **Plan / Build modes** — Plan mode literally never binds write/edit/bash tools to the
42
+ model, so it can explore and propose a plan with no possibility of a side effect
43
+ - **Guardrails** — four layers: input-scope filtering, destructive-command blocking
44
+ (`rm -rf /`, fork bombs, `curl | sh`), malicious-write scanning, and output redaction
45
+ - **Persistent sessions** — SQLite-backed conversation history; `/resume`, `/sessions`,
46
+ `--continue`
47
+ - **Todo tracking** — the agent maintains a visible task list for multi-step work
48
+ (`todo_write` / `todo_read`)
49
+ - **Code-aware search** — dedicated `grep`/`glob` tools (not raw shell), sandboxed and
50
+ available even in Plan mode since they're read-only
51
+ - **Git tools via MCP** — status, diff, log, commit, branches, through `mcp-server-git`
52
+ - **Any OpenRouter model** — free-tier models by default; switch with `/model`, browse
53
+ with `/models`
54
+ - **Per-user config** — your API key and default model persist in `~/.closecode/config.json`
55
+ (0600 permissions), independent of which directory you launch from
56
+
57
+ ## Install
58
+
59
+ ```bash
60
+ pipx install closecode-ai
61
+ closecode
62
+ ```
63
+
64
+ <!-- Adjust this section once published — this is the target state, not necessarily
65
+ live yet. Until it's on PyPI, use the git-based install below instead. -->
66
+
67
+ **From source, right now:**
68
+
69
+ ```bash
70
+ git clone https://github.com/omgite333/CLOSECODE.git
71
+ cd CLOSECODE
72
+ pip install -e .
73
+ closecode
74
+ ```
75
+
76
+ `pipx` is recommended over `pip` for the packaged version since it installs CLI tools into
77
+ an isolated environment and puts them straight on your `PATH`.
78
+
79
+ ## Tests
80
+
81
+ ```bash
82
+ pip install -e ".[dev]"
83
+ pytest
84
+ ```
85
+
86
+ 429 tests covering guardrails, sandbox path resolution, undo/checkpoints, background
87
+ processes, todo-store invariants, plan-mode tool filtering, session persistence, config
88
+ permissions, and the search tools. The suite is fully isolated: `config.py` and
89
+ `session.py` are redirected to temp dirs, no test touches the network, and nothing is
90
+ written to your real `~/.closecode`.
91
+
92
+ Some tests are marked `xfail` for **known bugs** rather than fixed behaviour — they
93
+ document a gap and will flip to passing when it's fixed. Run `pytest -rx` to list them.
94
+
95
+ ## Quick start
96
+
97
+ 1. Get a free API key at [openrouter.ai/settings/keys](https://openrouter.ai/settings/keys)
98
+ 2. Run `closecode` — on first launch it prompts for the key (input hidden) and offers to
99
+ save it to `~/.closecode/config.json` so you're not asked again
100
+ 3. Type a task:
101
+
102
+ ```
103
+ > find every place we call the old auth API and list the files
104
+ ```
105
+
106
+ <!-- SCREENSHOT: a permission prompt in action — "Allow agent to run: `grep -r ...`?"
107
+ This is worth showing on its own since it's the project's core safety story.
108
+ Save as: docs/permission-prompt.png -->
109
+ ![Permission prompt](docs/permission-prompt.png)
110
+
111
+ ## How it works
112
+
113
+ ```
114
+ user input
115
+ │
116
+ ▼
117
+ ┌──────────┐ binds tools for current mode ┌───────────────┐
118
+ │ agent │ ───────────────────────────────▶ │ LangGraph loop│
119
+ │ (llm.py) │ │ (agent.py) │
120
+ └──────────┘ └──────┬────────┘
121
+ │ tool call
122
+ ▼
123
+ ┌───────────────────────┐
124
+ │ Harness │
125
+ │ sandboxed fs + shell │──▶ guardrails.py
126
+ └───────────────────────┘ (blocks/scans)
127
+ │ result
128
+ ▼
129
+ ┌──────────────────────┐
130
+ │ Renderer interface │
131
+ │ (render.py) │
132
+ └──────┬──────────┬────┘
133
+ ▼ ▼
134
+ tui.py + tui_*.py ui.py + ui_*.py
135
+ (Textual) (classic)
136
+ ```
137
+
138
+ A single `Renderer` interface (`render.py`) decouples the agent loop from presentation, so
139
+ the Textual TUI and the classic REPL are two implementations of the same contract rather
140
+ than two copies of the agent logic.
141
+
142
+ Each frontend is a small set of single-purpose modules behind a facade, so the entry module
143
+ stays readable and the parts can be reused or tested on their own:
144
+
145
+ | Frontend | Facade | Split into |
146
+ |---|---|---|
147
+ | Textual | `tui.py` — app, message handlers, input, suggestions | `tui_theme.py` (banner, palette, CSS) · `tui_messages.py` (Message classes, `TuiRenderer`) · `tui_widgets.py` (conversation blocks, input box, modals, `ConfirmBridge`) |
148
+ | Classic | `ui.py` — the `print_*` / `stream_*` renderers | `ui_theme.py` (shared `Console`, palette, banner) · `ui_prompts.py` (permission dialog, input, `EscListener`) |
149
+
150
+ Each facade re-exports the names its split modules define, so `from ui import ...` and
151
+ `from tui import ...` keep working exactly as before.
152
+
153
+
154
+ ## Modes
155
+
156
+ | Mode | Command | Tools available | Use for |
157
+ |---|---|---|---|
158
+ | **Plan** | `/plan` | Read-only: `read_file`, `list_dir`, `grep`, `glob`, `tavily_search` | Exploring a codebase, proposing an approach with zero risk of a side effect |
159
+ | **Build** | `/build` | Everything, including `write_file`, `edit_file`, `bash`, `run_tests`, git tools | Actually making changes |
160
+
161
+ Plan mode isn't a prompt instruction the model can ignore — the write/edit/bash tools are
162
+ never bound to the model in the first place.
163
+
164
+ ## Tools
165
+
166
+ | Tool | Description |
167
+ |---|---|
168
+ | `read_file` / `write_file` / `edit_file` | Read, overwrite, or targeted find-and-replace on a file |
169
+ | `list_dir` | List a directory's contents |
170
+ | `glob` / `grep` | Find files by pattern / search file contents by regex — sandboxed, available in Plan mode |
171
+ | `bash` | Run a shell command in the sandbox (capped timeout, guardrail-checked) |
172
+ | `run_tests` | Run the project's test command and report pass/fail |
173
+ | `todo_write` / `todo_read` | Maintain a visible multi-step task list |
174
+ | `tavily_search` | Web search for current docs/APIs (requires `TAVILY_API_KEY`) |
175
+ | git tools (`status`, `diff`, `log`, `commit`, branches) | Via `mcp-server-git`, enabled with `AGENT_ENABLE_GIT=true` |
176
+
177
+ ## Commands
178
+
179
+ ```
180
+ /plan, /build switch modes
181
+ /models [filter] list OpenRouter models (free-tier first)
182
+ /model <id|number> switch model
183
+ /key update your OpenRouter API key
184
+ /sessions list saved conversations
185
+ /resume <id> switch to a saved session
186
+ /delete <id> delete a saved session
187
+ /usage token usage for this session
188
+ /clear start a new session
189
+ /help show all commands
190
+ ```
191
+
192
+ ## Configuration
193
+
194
+ All settings are environment variables (`.env`, or exported in your shell) — see
195
+ `.env.example` for the full list. Key ones:
196
+
197
+ | Variable | Default | Purpose |
198
+ |---|---|---|
199
+ | `OPENROUTER_API_KEY` | — | Required (or saved via `/key` into `~/.closecode/config.json`) |
200
+ | `OPENROUTER_MODEL` | `nvidia/nemotron-3.5-lightning:free` | Default model |
201
+ | `AGENT_WORKDIR` | `./sandbox` | Directory the agent is confined to |
202
+ | `AGENT_AUTO_APPROVE` | `false` | Skip permission prompts (guardrails still apply) |
203
+ | `AGENT_ENABLE_GIT` | `false` | Load git tools via MCP |
204
+ | `AGENT_DISABLE_GUARDRAILS` | `false` | Disable guardrails — trusted/isolated testing only |
205
+ | `TAVILY_API_KEY` | — | Enables `tavily_search` |
206
+ | `LANGCHAIN_API_KEY` | — | Enables LangSmith tracing |
207
+
208
+ ## Safety model
209
+
210
+ This is layered defense, not a single mechanism:
211
+
212
+ 1. **Input scope** — off-topic requests are redirected; clearly malicious requests
213
+ (keyloggers, phishing kits, account-hacking) are refused before reaching the model
214
+ 2. **Command blocking** — destructive shell commands are blocked before execution, even
215
+ with auto-approve on
216
+ 3. **Write scanning** — file writes/edits are scanned for malware indicators before
217
+ they're applied
218
+ 4. **Output redaction** — flagged content is scrubbed from conversation history
219
+ 5. **Sandboxed paths** — every file operation resolves through the harness, which refuses
220
+ to write outside the configured working directory regardless of how the path is phrased
221
+
222
+ These are conservative heuristics layered on top of the sandbox and per-action permission
223
+ prompts — not a formal guarantee. Shell commands currently run on the host inside a
224
+ path-restricted directory, not inside a container; see [Roadmap](#roadmap).
225
+
226
+ ## A note on model choice
227
+
228
+ Tool-calling reliability varies a lot across open models — this is the single biggest
229
+ factor in how well the agent performs. Frontier closed models are heavily trained for
230
+ reliable tool use; open models are improving but inconsistent. Roughly in order of
231
+ reliability, worth trying via `/model`:
232
+
233
+ - `qwen/qwen-2.5-coder-32b-instruct` — code-specialized, solid tool use, free tier available
234
+ - `qwen/qwen-2.5-72b-instruct` — strong, reliable, paid
235
+ - `meta-llama/llama-3.1-70b-instruct` — strong, reliable, paid
236
+ - `meta-llama/llama-3.1-8b-instruct` — fastest/cheapest, least reliable
237
+
238
+ If a smaller model frequently fails to call tools or hallucinates arguments, that's a
239
+ known gap between open and closed models on agentic tasks, not a bug here. Switching
240
+ models is the first thing to try before changing anything else.
241
+
242
+ ## Roadmap
243
+
244
+ - [x] Test suite (`pytest tests/`) — guardrails, sandbox path resolution, todo-store invariants
245
+ - [ ] CI (run the suite on push)
246
+ - [ ] Docker-based sandbox for shell execution, not just path restriction
247
+ - [ ] Client/server split — `build_graph()` behind FastAPI/WebSocket, thin streaming client
248
+ - [ ] PyPI release + prebuilt binaries (PyInstaller) for no-Python-required installs
249
+ - [ ] Homebrew tap
250
+
251
+ ## Contributing
252
+
253
+ Issues and PRs welcome. If you're adding a tool, follow the pattern in `tools.py` /
254
+ `search.py`: bind state via a module-level `bind_*()` function, keep it sandboxed to the
255
+ harness root, and add it to the Plan-mode allowlist only if it's genuinely read-only.
256
+
257
+ ## License
258
+
259
+ MIT — see [LICENSE](LICENSE).
@@ -9,7 +9,8 @@ from llm import get_llm
9
9
 
10
10
  SYSTEM_PROMPT = """You are a terminal coding agent running in a sandboxed working directory.
11
11
  You have tools for reading, writing, and editing files, running shell commands and tests,
12
- listing directories, and interacting with git (status, diff, log, commit, branches).
12
+ listing directories, managing background processes, undoing your own file changes,
13
+ and interacting with git (status, diff, log, commit, branches).
13
14
 
14
15
  Note: your available tools change depending on the current mode. In "plan" mode only
15
16
  read-only tools are bound to you (you literally cannot call write/edit/bash/commit tools
@@ -25,6 +26,14 @@ Rules:
25
26
  - Verify your work: after making a change, run a command, run tests, or read the
26
27
  file back to confirm it did what you intended. Never report a task complete
27
28
  without verifying — bugs are unacceptable, so test before you say "done".
29
+ - Long-running processes (dev servers, watchers, tunnels) go through
30
+ start_background, never a foreground bash call that would hang until timeout.
31
+ Use tail_logs to watch the process's output, fix what crashes, and restart
32
+ with kill_background + start_background. Always kill servers you started
33
+ when the task no longer needs them.
34
+ - Every write_file/edit_file is snapshotted before the change, and the user can
35
+ revert with /undo — treat that as a safety net, not a workflow: verify with
36
+ tests instead of writing sloppily and undoing.
28
37
  - Use git tools deliberately: check status/diff before committing, and never force-push
29
38
  or hard-reset unless the user explicitly asked for that specific action.
30
39
  - When the task is complete, reply with plain text summarizing what you did.