comodor 0.2.3__tar.gz → 0.3.1__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.
- {comodor-0.2.3 → comodor-0.3.1}/PKG-INFO +83 -21
- {comodor-0.2.3 → comodor-0.3.1}/README.md +82 -20
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/_version.py +2 -2
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/cli.py +99 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/config.py +3 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/doctor.py +44 -6
- comodor-0.3.1/src/comodor/tools/browser.py +398 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/tools/registry.py +19 -1
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/app.py +1 -0
- comodor-0.3.1/src/comodor/update.py +324 -0
- comodor-0.3.1/src/comodor/workspace.py +127 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/conftest.py +12 -0
- comodor-0.3.1/tests/test_browser.py +320 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_doctor.py +59 -0
- comodor-0.3.1/tests/test_update.py +325 -0
- {comodor-0.2.3 → comodor-0.3.1}/.gitignore +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/LICENSE +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/pyproject.toml +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/__main__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/agent/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/agent/context.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/agent/loop.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/agent/prompts.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/agent/tokens.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/catalogue.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/events.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/learning/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/learning/bm25.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/learning/hotindex.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/learning/memory.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/learning/progress.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/learning/reflect.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/learning/rules.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/learning/signals.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/learning/store.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/learning/writer.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/mcp/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/mcp/catalogue.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/mcp/commands.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/mcp/manager.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/mcp/protocol.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/net/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/net/http.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/net/sse.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/paths.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/providers/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/providers/anthropic.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/providers/base.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/providers/fake.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/providers/gateway.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/providers/openai_compat.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/providers/registry.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/safety/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/safety/checkpoints.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/safety/permissions.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/safety/redact.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/session/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/session/search.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/session/store.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/setup.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/skills/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/skills/examples.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/skills/loader.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/skills/propose.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/skills/registry.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/tools/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/tools/base.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/tools/fs.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/tools/history.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/tools/mcp.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/tools/search.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/tools/shell.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/tools/skills.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/tools/todo.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/tools/web.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/chooser.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/console.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/input/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/input/keys.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/input/reader.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/layout.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/markdown.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/screen.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/theme.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/widgets/__init__.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/widgets/buttons.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/widgets/chat.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/widgets/history.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/widgets/overlay.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/widgets/panel.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/widgets/progress.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/widgets/prompt.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/widgets/statusbar.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/ui/widgets/toast.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/src/comodor/uninstall.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/support/fake_mcp_server.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_agent_loop.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_app.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_chooser.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_history.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_input.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_layout.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_learning.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_markdown.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_mcp.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_performance.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_progress.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_propose.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_providers.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_reflex.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_run_loop.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_setup.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_skills.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_tools.py +0 -0
- {comodor-0.2.3 → comodor-0.3.1}/tests/test_uninstall.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: comodor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Comodor — a self-improving terminal coding agent with a Rich TUI
|
|
5
5
|
Project-URL: Homepage, https://comodor.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/ifekri/Comodor
|
|
@@ -28,6 +28,11 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
|
|
29
29
|
**A coding agent that lives in your terminal — and learns the way you correct it.**
|
|
30
30
|
|
|
31
|
+
[](https://pypi.org/project/comodor/)
|
|
32
|
+
[](https://pypi.org/project/comodor/)
|
|
33
|
+
[](https://github.com/ifekri/Comodor/actions/workflows/ci.yml)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
|
|
31
36
|
[comodor.ai](https://comodor.ai) · [Install](#install) · [What it can do](#what-it-can-do)
|
|
32
37
|
|
|
33
38
|
---
|
|
@@ -113,12 +118,27 @@ Four questions, once. Nothing to create beforehand — no config file, no
|
|
|
113
118
|
environment variable, no documentation to read first.
|
|
114
119
|
|
|
115
120
|
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
✓ provider Ollama (local)
|
|
122
|
+
✓ api key not needed
|
|
123
|
+
|
|
124
|
+
3/4 Which model?
|
|
125
|
+
┌─ Models ──────────────────────────────────────────────┐
|
|
126
|
+
│ › qwen2.5-coder:14b recommended │
|
|
127
|
+
│ llama3.3 │
|
|
128
|
+
│ deepseek-r1:14b │
|
|
129
|
+
└─────────────────────────────────────────────────────────┘
|
|
130
|
+
↑↓ move enter choose type filter esc cancel
|
|
120
131
|
```
|
|
121
132
|
|
|
133
|
+
One question per screen, answered with the arrow keys. Where a provider offers
|
|
134
|
+
sixty models, typing filters them. Piped or scripted, the same questions arrive
|
|
135
|
+
as a numbered list, so it can still be automated.
|
|
136
|
+
|
|
137
|
+
Then it shows you the directory it is about to work in and asks once — the
|
|
138
|
+
project root is found by walking upwards, and the answer is occasionally a
|
|
139
|
+
surprise worth seeing before anything reads it. Approved folders are
|
|
140
|
+
remembered.
|
|
141
|
+
|
|
122
142
|
You are not asked again. Change your mind later with `comodor setup`.
|
|
123
143
|
|
|
124
144
|
**No API key?** `comodor --demo` runs the whole interface offline — every
|
|
@@ -191,6 +211,35 @@ Everything you have ever asked is searchable.
|
|
|
191
211
|
The agent searches it too, on its own, when you refer to earlier work — *"like
|
|
192
212
|
we did last time"*, *"that bug from last week"*.
|
|
193
213
|
|
|
214
|
+
### It can browse, not just fetch
|
|
215
|
+
|
|
216
|
+
Most agents get one page at a time: download a URL, strip the markup, and the
|
|
217
|
+
links go with it — so the only way onward is guessing another URL. Comodor
|
|
218
|
+
browses.
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
› find out how the GitHub MCP server handles rate limits
|
|
222
|
+
|
|
223
|
+
⚙ search: github mcp server rate limit 1.2s
|
|
224
|
+
⚙ browse https://github.com/modelcontextprotocol/servers 0.8s
|
|
225
|
+
Links on this page:
|
|
226
|
+
1. src/github → …/tree/main/src/github
|
|
227
|
+
⚙ follow link 1 0.6s
|
|
228
|
+
⚙ find "rate limit" on the page 0.0s
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The links come back numbered and resolved, so the next move is `follow 4`
|
|
232
|
+
rather than a guess — and links inside the content rank above the navigation
|
|
233
|
+
bar that every page of a documentation site repeats. It is one session, so
|
|
234
|
+
cookies, redirects and consent pages survive the hop. Long pages are handed
|
|
235
|
+
over a screenful at a time with `find` to jump, instead of being cut off at
|
|
236
|
+
40,000 characters. Moving around a page it has already fetched touches no
|
|
237
|
+
network and asks no permission; a new host does.
|
|
238
|
+
|
|
239
|
+
There is no JavaScript engine, and there is not going to be one — that means a
|
|
240
|
+
real browser, which means a real dependency. A page that draws itself in the
|
|
241
|
+
client says so and points at the Puppeteer server below rather than pretending.
|
|
242
|
+
|
|
194
243
|
### It connects to other tools
|
|
195
244
|
|
|
196
245
|
Comodor speaks the [Model Context Protocol](https://modelcontextprotocol.io),
|
|
@@ -233,6 +282,34 @@ cache gets deleted. A corrupt config is reported and left exactly as it was,
|
|
|
233
282
|
because it holds your API key — the one thing on your machine that cannot be
|
|
234
283
|
regenerated.
|
|
235
284
|
|
|
285
|
+
### It updates itself
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
$ comodor update
|
|
289
|
+
|
|
290
|
+
Comodor 0.2.3
|
|
291
|
+
0.3.0 is available. https://pypi.org/project/comodor/0.3.0/
|
|
292
|
+
installed as a uv tool
|
|
293
|
+
uv tool upgrade comodor
|
|
294
|
+
|
|
295
|
+
updating…
|
|
296
|
+
now on 0.3.0
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
It uses whatever put it there — uv, pipx, pip, or the environment the installer
|
|
300
|
+
built — because guessing wrong is worse than not offering the command: a `pip
|
|
301
|
+
install --upgrade` inside a uv environment appears to work and leaves uv's
|
|
302
|
+
record pointing at a version that is gone.
|
|
303
|
+
|
|
304
|
+
Afterwards it runs the new one and asks what it is, and *that* is what gets
|
|
305
|
+
printed. An upgrade that reports success by echoing the number it was aiming at
|
|
306
|
+
is how a silently failed install goes unnoticed for a week. `--check` says
|
|
307
|
+
what is available and changes nothing, and `comodor doctor` mentions a new
|
|
308
|
+
release without ever installing one.
|
|
309
|
+
|
|
310
|
+
A source checkout is left alone: `git pull` is the upgrade, and overwriting a
|
|
311
|
+
working tree with a release throws away work that was never committed.
|
|
312
|
+
|
|
236
313
|
### It leaves when you ask it to
|
|
237
314
|
|
|
238
315
|
```
|
|
@@ -271,22 +348,6 @@ not by searching your disk. Three things it will not do: touch a source
|
|
|
271
348
|
checkout, take a directory off your PATH that other programs are still using,
|
|
272
349
|
or claim to have deleted a file the operating system would not let go of.
|
|
273
350
|
|
|
274
|
-
### It can prove it is improving
|
|
275
|
-
|
|
276
|
-
Every tool claims to get better over time. Comodor shows the numbers.
|
|
277
|
-
|
|
278
|
-
```
|
|
279
|
-
◈ Steps per task down 40% since the first tasks in this project.
|
|
280
|
-
|
|
281
|
-
metric trend now vs first
|
|
282
|
-
Steps per task ▇██▇▇▆▇▅▅▅▅▆▄▅▄▅▄▄▂▄▂▁▂▂▂▁▁▁▁▁ 5.3 ↓40%
|
|
283
|
-
Corrections per task ████▆▇▆█▆▆▆▇▆▆▅▃▃▅▆▆▅▃▆▃▆▃▂▁▁▃ 0.9 ↓65%
|
|
284
|
-
Approvals asked █▇▇▇▇▇▇▇▇▇▅▅▅▅▅▅▅▅▅▅▃▃▃▃▃▃▃▃▃▁ 0.8 ↓73%
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
The panel is built to under-claim: with too little history it says so, and a
|
|
288
|
-
fall from 0.4 to 0 is never allowed to headline as "down 100%".
|
|
289
|
-
|
|
290
351
|
---
|
|
291
352
|
|
|
292
353
|
## You stay in control
|
|
@@ -339,6 +400,7 @@ comodor --demo # offline walkthrough, no key needed
|
|
|
339
400
|
comodor run "fix the failing test" --yes # one task, headless, for scripts
|
|
340
401
|
comodor run "audit this module" --json # machine-readable, for pipelines
|
|
341
402
|
comodor doctor # check everything; --fix repairs it
|
|
403
|
+
comodor update # move to the newest release; --check first
|
|
342
404
|
comodor uninstall # remove it completely; --dry-run first
|
|
343
405
|
```
|
|
344
406
|
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
**A coding agent that lives in your terminal — and learns the way you correct it.**
|
|
4
4
|
|
|
5
|
+
[](https://pypi.org/project/comodor/)
|
|
6
|
+
[](https://pypi.org/project/comodor/)
|
|
7
|
+
[](https://github.com/ifekri/Comodor/actions/workflows/ci.yml)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
5
10
|
[comodor.ai](https://comodor.ai) · [Install](#install) · [What it can do](#what-it-can-do)
|
|
6
11
|
|
|
7
12
|
---
|
|
@@ -87,12 +92,27 @@ Four questions, once. Nothing to create beforehand — no config file, no
|
|
|
87
92
|
environment variable, no documentation to read first.
|
|
88
93
|
|
|
89
94
|
```
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
✓ provider Ollama (local)
|
|
96
|
+
✓ api key not needed
|
|
97
|
+
|
|
98
|
+
3/4 Which model?
|
|
99
|
+
┌─ Models ──────────────────────────────────────────────┐
|
|
100
|
+
│ › qwen2.5-coder:14b recommended │
|
|
101
|
+
│ llama3.3 │
|
|
102
|
+
│ deepseek-r1:14b │
|
|
103
|
+
└─────────────────────────────────────────────────────────┘
|
|
104
|
+
↑↓ move enter choose type filter esc cancel
|
|
94
105
|
```
|
|
95
106
|
|
|
107
|
+
One question per screen, answered with the arrow keys. Where a provider offers
|
|
108
|
+
sixty models, typing filters them. Piped or scripted, the same questions arrive
|
|
109
|
+
as a numbered list, so it can still be automated.
|
|
110
|
+
|
|
111
|
+
Then it shows you the directory it is about to work in and asks once — the
|
|
112
|
+
project root is found by walking upwards, and the answer is occasionally a
|
|
113
|
+
surprise worth seeing before anything reads it. Approved folders are
|
|
114
|
+
remembered.
|
|
115
|
+
|
|
96
116
|
You are not asked again. Change your mind later with `comodor setup`.
|
|
97
117
|
|
|
98
118
|
**No API key?** `comodor --demo` runs the whole interface offline — every
|
|
@@ -165,6 +185,35 @@ Everything you have ever asked is searchable.
|
|
|
165
185
|
The agent searches it too, on its own, when you refer to earlier work — *"like
|
|
166
186
|
we did last time"*, *"that bug from last week"*.
|
|
167
187
|
|
|
188
|
+
### It can browse, not just fetch
|
|
189
|
+
|
|
190
|
+
Most agents get one page at a time: download a URL, strip the markup, and the
|
|
191
|
+
links go with it — so the only way onward is guessing another URL. Comodor
|
|
192
|
+
browses.
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
› find out how the GitHub MCP server handles rate limits
|
|
196
|
+
|
|
197
|
+
⚙ search: github mcp server rate limit 1.2s
|
|
198
|
+
⚙ browse https://github.com/modelcontextprotocol/servers 0.8s
|
|
199
|
+
Links on this page:
|
|
200
|
+
1. src/github → …/tree/main/src/github
|
|
201
|
+
⚙ follow link 1 0.6s
|
|
202
|
+
⚙ find "rate limit" on the page 0.0s
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The links come back numbered and resolved, so the next move is `follow 4`
|
|
206
|
+
rather than a guess — and links inside the content rank above the navigation
|
|
207
|
+
bar that every page of a documentation site repeats. It is one session, so
|
|
208
|
+
cookies, redirects and consent pages survive the hop. Long pages are handed
|
|
209
|
+
over a screenful at a time with `find` to jump, instead of being cut off at
|
|
210
|
+
40,000 characters. Moving around a page it has already fetched touches no
|
|
211
|
+
network and asks no permission; a new host does.
|
|
212
|
+
|
|
213
|
+
There is no JavaScript engine, and there is not going to be one — that means a
|
|
214
|
+
real browser, which means a real dependency. A page that draws itself in the
|
|
215
|
+
client says so and points at the Puppeteer server below rather than pretending.
|
|
216
|
+
|
|
168
217
|
### It connects to other tools
|
|
169
218
|
|
|
170
219
|
Comodor speaks the [Model Context Protocol](https://modelcontextprotocol.io),
|
|
@@ -207,6 +256,34 @@ cache gets deleted. A corrupt config is reported and left exactly as it was,
|
|
|
207
256
|
because it holds your API key — the one thing on your machine that cannot be
|
|
208
257
|
regenerated.
|
|
209
258
|
|
|
259
|
+
### It updates itself
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
$ comodor update
|
|
263
|
+
|
|
264
|
+
Comodor 0.2.3
|
|
265
|
+
0.3.0 is available. https://pypi.org/project/comodor/0.3.0/
|
|
266
|
+
installed as a uv tool
|
|
267
|
+
uv tool upgrade comodor
|
|
268
|
+
|
|
269
|
+
updating…
|
|
270
|
+
now on 0.3.0
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
It uses whatever put it there — uv, pipx, pip, or the environment the installer
|
|
274
|
+
built — because guessing wrong is worse than not offering the command: a `pip
|
|
275
|
+
install --upgrade` inside a uv environment appears to work and leaves uv's
|
|
276
|
+
record pointing at a version that is gone.
|
|
277
|
+
|
|
278
|
+
Afterwards it runs the new one and asks what it is, and *that* is what gets
|
|
279
|
+
printed. An upgrade that reports success by echoing the number it was aiming at
|
|
280
|
+
is how a silently failed install goes unnoticed for a week. `--check` says
|
|
281
|
+
what is available and changes nothing, and `comodor doctor` mentions a new
|
|
282
|
+
release without ever installing one.
|
|
283
|
+
|
|
284
|
+
A source checkout is left alone: `git pull` is the upgrade, and overwriting a
|
|
285
|
+
working tree with a release throws away work that was never committed.
|
|
286
|
+
|
|
210
287
|
### It leaves when you ask it to
|
|
211
288
|
|
|
212
289
|
```
|
|
@@ -245,22 +322,6 @@ not by searching your disk. Three things it will not do: touch a source
|
|
|
245
322
|
checkout, take a directory off your PATH that other programs are still using,
|
|
246
323
|
or claim to have deleted a file the operating system would not let go of.
|
|
247
324
|
|
|
248
|
-
### It can prove it is improving
|
|
249
|
-
|
|
250
|
-
Every tool claims to get better over time. Comodor shows the numbers.
|
|
251
|
-
|
|
252
|
-
```
|
|
253
|
-
◈ Steps per task down 40% since the first tasks in this project.
|
|
254
|
-
|
|
255
|
-
metric trend now vs first
|
|
256
|
-
Steps per task ▇██▇▇▆▇▅▅▅▅▆▄▅▄▅▄▄▂▄▂▁▂▂▂▁▁▁▁▁ 5.3 ↓40%
|
|
257
|
-
Corrections per task ████▆▇▆█▆▆▆▇▆▆▅▃▃▅▆▆▅▃▆▃▆▃▂▁▁▃ 0.9 ↓65%
|
|
258
|
-
Approvals asked █▇▇▇▇▇▇▇▇▇▅▅▅▅▅▅▅▅▅▅▃▃▃▃▃▃▃▃▃▁ 0.8 ↓73%
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
The panel is built to under-claim: with too little history it says so, and a
|
|
262
|
-
fall from 0.4 to 0 is never allowed to headline as "down 100%".
|
|
263
|
-
|
|
264
325
|
---
|
|
265
326
|
|
|
266
327
|
## You stay in control
|
|
@@ -313,6 +374,7 @@ comodor --demo # offline walkthrough, no key needed
|
|
|
313
374
|
comodor run "fix the failing test" --yes # one task, headless, for scripts
|
|
314
375
|
comodor run "audit this module" --json # machine-readable, for pipelines
|
|
315
376
|
comodor doctor # check everything; --fix repairs it
|
|
377
|
+
comodor update # move to the newest release; --check first
|
|
316
378
|
comodor uninstall # remove it completely; --dry-run first
|
|
317
379
|
```
|
|
318
380
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.3.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -9,6 +9,8 @@ Three ways in:
|
|
|
9
9
|
* ``comodor setup`` — the first-run questions again, on demand.
|
|
10
10
|
* ``comodor doctor`` — what is configured, what is reachable, what the terminal
|
|
11
11
|
can do. The first thing to run when something is not working.
|
|
12
|
+
* ``comodor update`` — move to the newest published version, using whichever
|
|
13
|
+
of uv, pipx or pip put this copy here.
|
|
12
14
|
* ``comodor uninstall`` — takes every file, folder and PATH line back off the
|
|
13
15
|
machine, after showing you the list.
|
|
14
16
|
|
|
@@ -64,6 +66,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
64
66
|
doctor.add_argument("--fix", action="store_true",
|
|
65
67
|
help="apply every repair the check found")
|
|
66
68
|
|
|
69
|
+
upgrade = sub.add_parser(
|
|
70
|
+
"update", help="upgrade to the newest published version")
|
|
71
|
+
upgrade.add_argument("--check", action="store_true",
|
|
72
|
+
help="say what is available and change nothing")
|
|
73
|
+
|
|
67
74
|
remove = sub.add_parser(
|
|
68
75
|
"uninstall",
|
|
69
76
|
help="remove Comodor and everything it has written, completely")
|
|
@@ -303,6 +310,80 @@ def run_doctor(config: Config, fix: bool = False) -> int:
|
|
|
303
310
|
return 1 if report.worst is Status.FAIL else 0
|
|
304
311
|
|
|
305
312
|
|
|
313
|
+
def run_update(config: Config, check_only: bool = False) -> int:
|
|
314
|
+
"""Move to the newest published version.
|
|
315
|
+
|
|
316
|
+
Four steps, and each one can stop the run with something the user can act
|
|
317
|
+
on: what is out there, whether it is newer than this, how this copy would
|
|
318
|
+
be upgraded, and what version answers afterwards.
|
|
319
|
+
"""
|
|
320
|
+
from .ui import console as console_module
|
|
321
|
+
from . import update as updater
|
|
322
|
+
|
|
323
|
+
theme = console_module.prepare_theme(config.ui.theme, config.ui.ascii_borders,
|
|
324
|
+
no_color=False)
|
|
325
|
+
console = console_module.build(theme)
|
|
326
|
+
here = updater.current()
|
|
327
|
+
|
|
328
|
+
console.print(f"\n[title]Comodor {here}[/title]")
|
|
329
|
+
|
|
330
|
+
release = updater.latest()
|
|
331
|
+
if release is None:
|
|
332
|
+
console.print(" [bad]Could not reach the package index.[/bad] "
|
|
333
|
+
"[dim]Check the connection and try again.[/dim]\n")
|
|
334
|
+
return 1
|
|
335
|
+
|
|
336
|
+
step = updater.plan()
|
|
337
|
+
|
|
338
|
+
if not updater.is_newer(release.version, here):
|
|
339
|
+
# Ahead of the index rather than behind it: a development build, which
|
|
340
|
+
# is a different sentence from "up to date".
|
|
341
|
+
ahead = updater.is_newer(here, release.version)
|
|
342
|
+
console.print(
|
|
343
|
+
f" [good]{release.version} is the newest published version"
|
|
344
|
+
f"[/good][dim], and this is "
|
|
345
|
+
f"{'ahead of it' if ahead else 'it'}.[/dim]")
|
|
346
|
+
if step.detail:
|
|
347
|
+
console.print(f" [dim]{step.detail}[/dim]")
|
|
348
|
+
console.print("")
|
|
349
|
+
return 0
|
|
350
|
+
|
|
351
|
+
console.print(f" [accent]{release.version}[/accent] is available."
|
|
352
|
+
f" [dim]{release.url}[/dim]")
|
|
353
|
+
|
|
354
|
+
if step.blocked:
|
|
355
|
+
console.print(f"\n [warn]{step.blocked}[/warn]\n")
|
|
356
|
+
return 1
|
|
357
|
+
|
|
358
|
+
if step.detail:
|
|
359
|
+
console.print(f" [dim]{step.detail}[/dim]")
|
|
360
|
+
console.print(f" [dim]{' '.join(step.command)}[/dim]")
|
|
361
|
+
|
|
362
|
+
if check_only:
|
|
363
|
+
console.print("\n[dim]Nothing was changed: this was --check.[/dim]\n")
|
|
364
|
+
return 0
|
|
365
|
+
|
|
366
|
+
console.print("\n updating…")
|
|
367
|
+
# Run it, then ask what version answers. Asked, not assumed.
|
|
368
|
+
outcome = updater.upgrade(step, release.version)
|
|
369
|
+
|
|
370
|
+
if not outcome.ok:
|
|
371
|
+
console.print(f" [bad]failed[/bad] [dim]{outcome.message}[/dim]\n")
|
|
372
|
+
return 1
|
|
373
|
+
if outcome.deferred:
|
|
374
|
+
console.print(f" [good]{outcome.message}[/good]\n")
|
|
375
|
+
return 0
|
|
376
|
+
if outcome.forced:
|
|
377
|
+
# Worth saying out loud: something was pinned, and now it is not.
|
|
378
|
+
console.print(" [dim]the recorded requirement pinned the version, so it "
|
|
379
|
+
"was reinstalled from the index[/dim]")
|
|
380
|
+
if outcome.version:
|
|
381
|
+
console.print(f" [good]now on {outcome.version}[/good]\n")
|
|
382
|
+
else:
|
|
383
|
+
console.print(f" [good]done[/good] [dim]— {outcome.message}.[/dim]\n")
|
|
384
|
+
return 0
|
|
385
|
+
|
|
386
|
+
|
|
306
387
|
def run_uninstall(config: Config, dry_run: bool = False,
|
|
307
388
|
assume_yes: bool = False) -> int:
|
|
308
389
|
"""Show everything that would go, then go.
|
|
@@ -448,6 +529,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
448
529
|
|
|
449
530
|
if args.command == "doctor":
|
|
450
531
|
return run_doctor(config, fix=getattr(args, "fix", False))
|
|
532
|
+
if args.command == "update":
|
|
533
|
+
return run_update(config, check_only=args.check)
|
|
451
534
|
if args.command == "uninstall":
|
|
452
535
|
return run_uninstall(config, dry_run=args.dry_run, assume_yes=args.yes)
|
|
453
536
|
if args.command == "mcp":
|
|
@@ -484,6 +567,22 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
484
567
|
if config.needs_setup:
|
|
485
568
|
return 1
|
|
486
569
|
|
|
570
|
+
# Which directory is this about? Asked once per folder, before the agent
|
|
571
|
+
# exists — the project root is worked out by walking upwards, and the
|
|
572
|
+
# answer is occasionally a surprise worth seeing before anything reads it.
|
|
573
|
+
# `--cwd` is the user naming it themselves, so it does not ask again.
|
|
574
|
+
if not args.cwd:
|
|
575
|
+
from .ui import console as console_module
|
|
576
|
+
from .workspace import confirm
|
|
577
|
+
|
|
578
|
+
theme = console_module.prepare_theme(config.ui.theme,
|
|
579
|
+
config.ui.ascii_borders, no_color=False)
|
|
580
|
+
chosen = confirm(config, console_module.build(theme), theme)
|
|
581
|
+
if chosen is None:
|
|
582
|
+
return 0
|
|
583
|
+
if chosen != config.paths.project:
|
|
584
|
+
config = apply_overrides(load_config(str(chosen)), args)
|
|
585
|
+
|
|
487
586
|
from .ui.app import App
|
|
488
587
|
|
|
489
588
|
resume = None
|
|
@@ -227,6 +227,9 @@ class SafetyConfig:
|
|
|
227
227
|
deny_commands: list[str] = field(default_factory=lambda: list(DEFAULT_DENY))
|
|
228
228
|
workspace_only: bool = True
|
|
229
229
|
max_file_read_bytes: int = 512_000
|
|
230
|
+
#: Directories the user has confirmed as a workspace. Exact paths, never
|
|
231
|
+
#: prefixes: approving ~/work/api must not quietly approve ~/work.
|
|
232
|
+
trusted_folders: list[str] = field(default_factory=list)
|
|
230
233
|
|
|
231
234
|
|
|
232
235
|
# Patterns that are never worth running from an agent, however it is prompted.
|
|
@@ -92,13 +92,22 @@ class Report:
|
|
|
92
92
|
# --------------------------------------------------------------------------- #
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
def run_checks(config: Config) -> Report:
|
|
96
|
-
"""Every check, in the order a person would want to read them.
|
|
95
|
+
def run_checks(config: Config, online: bool = True) -> Report:
|
|
96
|
+
"""Every check, in the order a person would want to read them.
|
|
97
|
+
|
|
98
|
+
`online` exists because one check asks the package index whether there is a
|
|
99
|
+
newer release, and everything else here reads the local machine. A caller
|
|
100
|
+
that must not touch the network — a test, or a diagnostic being gathered
|
|
101
|
+
from somewhere with no route out — turns it off and gets the rest.
|
|
102
|
+
"""
|
|
103
|
+
checks = [_check_config, _check_config_permissions, _check_provider,
|
|
104
|
+
_check_saved_provider, _check_model, _check_brain,
|
|
105
|
+
_check_search_index, _check_skills, _check_leftovers, _check_mcp]
|
|
106
|
+
if online:
|
|
107
|
+
checks.append(_check_version)
|
|
108
|
+
|
|
97
109
|
report = Report()
|
|
98
|
-
for check in
|
|
99
|
-
_check_saved_provider, _check_model, _check_brain,
|
|
100
|
-
_check_search_index,
|
|
101
|
-
_check_skills, _check_leftovers, _check_mcp):
|
|
110
|
+
for check in checks:
|
|
102
111
|
try:
|
|
103
112
|
finding = check(config)
|
|
104
113
|
except Exception as error: # a check must never be the bug
|
|
@@ -450,3 +459,32 @@ def wait_for(condition: Callable[[], bool], timeout: float = 2.0,
|
|
|
450
459
|
return True
|
|
451
460
|
time.sleep(interval)
|
|
452
461
|
return condition()
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _check_version(config: Config) -> Finding | None:
|
|
465
|
+
"""Is there a newer release than the one running?
|
|
466
|
+
|
|
467
|
+
Doctor is where people look when something is not working, and "you are
|
|
468
|
+
four versions behind" is often the whole answer. It is not a repair, though
|
|
469
|
+
— `--fix` must never reach out and change what is installed under somebody
|
|
470
|
+
without being asked, so this states the command and stops.
|
|
471
|
+
|
|
472
|
+
On a short timeout, and silent when the network is not there. A diagnostic
|
|
473
|
+
that hangs for fifteen seconds on an aeroplane is a worse diagnostic than
|
|
474
|
+
one with a line missing.
|
|
475
|
+
"""
|
|
476
|
+
from . import __version__
|
|
477
|
+
from .update import is_newer, latest
|
|
478
|
+
|
|
479
|
+
release = latest(timeout=(2.0, 3.0))
|
|
480
|
+
if release is None:
|
|
481
|
+
return None # offline; not a fault to report
|
|
482
|
+
|
|
483
|
+
if not is_newer(release.version, __version__):
|
|
484
|
+
return Finding("version", Status.OK, f"{__version__} is current")
|
|
485
|
+
|
|
486
|
+
return Finding(
|
|
487
|
+
"version", Status.WARN,
|
|
488
|
+
f"{__version__} is installed; {release.version} is out",
|
|
489
|
+
remedy="comodor update",
|
|
490
|
+
)
|