comodor 0.8.8__tar.gz → 0.9.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.
- comodor-0.9.0/PKG-INFO +177 -0
- comodor-0.9.0/README.md +150 -0
- comodor-0.9.0/docs/README.md +103 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/_version.py +2 -2
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/agent/context.py +26 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/agent/loop.py +9 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/cli.py +23 -1
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/config.py +91 -2
- comodor-0.9.0/src/comodor/desktop/__init__.py +389 -0
- comodor-0.9.0/src/comodor/desktop/guard.py +242 -0
- comodor-0.9.0/src/comodor/desktop/keys.py +174 -0
- comodor-0.9.0/src/comodor/desktop/overlay.py +401 -0
- comodor-0.9.0/src/comodor/desktop/png.py +99 -0
- comodor-0.9.0/src/comodor/desktop/screen.py +179 -0
- comodor-0.9.0/src/comodor/desktop/win32.py +429 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/events.py +14 -0
- comodor-0.9.0/src/comodor/help.py +313 -0
- comodor-0.9.0/src/comodor/tools/computer.py +496 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/registry.py +23 -1
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/app.py +72 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/uninstall.py +12 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/web/commands.py +8 -1
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/web/index.html +67 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/web/server.py +16 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/web/session.py +27 -1
- comodor-0.9.0/tests/test_computer_tool.py +463 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_config_save.py +58 -0
- comodor-0.9.0/tests/test_desktop.py +410 -0
- comodor-0.9.0/tests/test_help.py +186 -0
- comodor-0.9.0/tests/test_overlay.py +114 -0
- comodor-0.9.0/tests/test_real_desktop.py +149 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_uninstall.py +26 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_web.py +100 -0
- comodor-0.8.8/PKG-INFO +0 -822
- comodor-0.8.8/README.md +0 -795
- {comodor-0.8.8 → comodor-0.9.0}/.gitignore +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/LICENSE +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/pyproject.toml +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/__main__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/agent/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/agent/prompts.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/agent/spawn.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/agent/tokens.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/browser/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/browser/cdp.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/browser/launch.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/browser/page.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/catalogue.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/doctor.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/learning/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/learning/associations.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/learning/bm25.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/learning/hotindex.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/learning/memory.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/learning/progress.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/learning/reflect.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/learning/rules.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/learning/signals.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/learning/store.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/learning/writer.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/mcp/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/mcp/catalogue.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/mcp/commands.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/mcp/http.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/mcp/manager.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/mcp/protocol.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/migrate.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/net/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/net/http.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/net/sse.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/paths.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/providers/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/providers/anthropic.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/providers/base.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/providers/caching.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/providers/fake.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/providers/gateway.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/providers/openai_compat.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/providers/registry.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/safety/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/safety/checkpoints.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/safety/permissions.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/safety/redact.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/session/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/session/search.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/session/store.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/setup.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/skills/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/skills/catalogue.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/skills/commands.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/skills/examples.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/skills/loader.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/skills/propose.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/skills/registry.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/base.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/browse.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/browser.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/delegate.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/fs.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/history.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/mcp.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/overflow.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/search.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/shell.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/skills.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/todo.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/tools/web.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/banner.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/bidi.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/chooser.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/console.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/input/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/input/keys.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/input/reader.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/layout.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/markdown.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/screen.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/theme.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/widgets/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/widgets/buttons.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/widgets/chat.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/widgets/history.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/widgets/overlay.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/widgets/panel.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/widgets/progress.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/widgets/prompt.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/widgets/statusbar.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/ui/widgets/toast.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/update.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/web/__init__.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/src/comodor/workspace.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/conftest.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/support/fake_mcp_server.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_agent_loop.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_app.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_associations.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_banner.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_bidi.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_browser.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_caching.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_chooser.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_delegate.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_doctor.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_history.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_import_command.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_input.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_layout.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_learning.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_markdown.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_mcp.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_mcp_http.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_migrate.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_overflow.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_performance.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_prefix_stability.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_progress.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_propose.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_providers.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_real_browser.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_reflex.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_run_loop.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_setup.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_setup_import.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_skill_budget.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_skill_catalogue.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_skills.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_switching_model.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_tools.py +0 -0
- {comodor-0.8.8 → comodor-0.9.0}/tests/test_update.py +0 -0
comodor-0.9.0/PKG-INFO
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: comodor
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Comodor — a self-improving terminal coding agent with a Rich TUI
|
|
5
|
+
Project-URL: Homepage, https://comodor.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/ifekri/Comodor
|
|
7
|
+
Project-URL: Issues, https://github.com/ifekri/Comodor/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/ifekri/Comodor/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: MMDRZA <mohammadreza.fekri.mojdehi@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agent,cli,coding-assistant,llm,rich,terminal,tui
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: rich>=13.7
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff>=0.14; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Comodor
|
|
29
|
+
|
|
30
|
+
**A coding agent that lives in your terminal — and learns the way you correct it.**
|
|
31
|
+
|
|
32
|
+
[](https://pypi.org/project/comodor/)
|
|
33
|
+
[](https://pypi.org/project/comodor/)
|
|
34
|
+
[](https://github.com/ifekri/Comodor/actions/workflows/ci.yml)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
|
|
37
|
+
**[Documentation](docs/README.md)** · [comodor.ai](https://comodor.ai) · [Changelog](CHANGELOG.md)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
Describe a job — *fix the failing test*, *add a health endpoint*, *work out why
|
|
42
|
+
the deploy broke* — and it does the work: reads your files, writes changes, runs
|
|
43
|
+
your tests, searches the web, and keeps going until the job is done or it needs
|
|
44
|
+
you.
|
|
45
|
+
|
|
46
|
+
It asks before it changes anything, shows you exactly what it is about to do,
|
|
47
|
+
and can undo it.
|
|
48
|
+
|
|
49
|
+
What makes it different from every other tool of this kind is what happens
|
|
50
|
+
afterwards. **When you fix something it wrote, it notices, and it does not make
|
|
51
|
+
that mistake again.** Not because you configured it. Because it watched.
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
› create defaults.py with 6 string constants
|
|
55
|
+
⚙ write src/defaults.py — 6 constants
|
|
56
|
+
|
|
57
|
+
… you open the file and change "30s" to '30s' …
|
|
58
|
+
|
|
59
|
+
› now add the timeout constants
|
|
60
|
+
◈ learned: Use single quotes for string literals. (31 of 34 literals)
|
|
61
|
+
⚙ write src/defaults.py — '30s', '5m'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
That is a real transcript. Nobody told it anything.
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
┌─ History ──────────────┐ ┌─ Chat ───────────────────────────────────────────────┐
|
|
68
|
+
│ TASKS 2/4 ──────────── │ │ › add a health endpoint and a test for it │
|
|
69
|
+
│ ● read the app factory │ │ ◈ recalled 3 lessons · skill: review │
|
|
70
|
+
│ ● add the /health rou… │ │ │
|
|
71
|
+
│ ◐ write the test │ │ I'll add the route, then a test. │
|
|
72
|
+
│ ○ run the suite │ │ │
|
|
73
|
+
│ │ │ ⚙ edit src/app.py 0.2s │
|
|
74
|
+
│ │ │ + @app.get('/health') │
|
|
75
|
+
│ │ │ ⚙ run: pytest -q 3.4s │
|
|
76
|
+
│ │ │ 4 passed in 0.42s │
|
|
77
|
+
└────────────────────────┘ └──────────────────────────────────────────────────────┘
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Install
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv tool install comodor # or: pipx install comodor, or: pip install comodor
|
|
86
|
+
comodor
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Python 3.11 or newer. It asks five questions the first time and never asks
|
|
90
|
+
again.
|
|
91
|
+
|
|
92
|
+
No key yet? `comodor --demo` runs the whole interface offline. Or choose
|
|
93
|
+
**Ollama** in the setup and pay nothing, ever.
|
|
94
|
+
|
|
95
|
+
Already use OpenClaw or Hermes? The first screen offers to bring your keys and
|
|
96
|
+
skills across. [How that works](docs/migrating.md).
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## What it can do
|
|
101
|
+
|
|
102
|
+
| | |
|
|
103
|
+
|---|---|
|
|
104
|
+
| **Learn from your corrections** | Edit what it wrote, or say so, and the next answer follows. `/progress` shows whether that is actually working — falling correction rates, not a claim. [More](docs/learning.md) |
|
|
105
|
+
| **Ask before it acts** | Reading is silent, writing shows a diff, commands ask. Every write is checkpointed; `/undo` puts it back. [More](docs/safety.md) |
|
|
106
|
+
| **Use a real browser** | One that runs JavaScript, keeps cookies and can log in — not a page fetcher. [More](docs/browser.md) |
|
|
107
|
+
| **Use your screen** | Mouse and keyboard, in any application, with a halo on screen showing where it is about to click. Windows so far. [More](docs/computer.md) |
|
|
108
|
+
| **Follow your procedures** | Write a skill once; it loads it when the work matches. [More](docs/skills.md) |
|
|
109
|
+
| **Run anywhere** | Terminal, browser, or a container. [Web](docs/web.md) · [Docker](docs/docker.md) |
|
|
110
|
+
| **Work with any model** | Seventeen providers, or anything with an OpenAI-compatible URL. Including ones on your own machine. [More](docs/models.md) |
|
|
111
|
+
| **Cost less than it should** | 86% of input tokens served from cache, measured. [More](docs/cost.md) |
|
|
112
|
+
|
|
113
|
+
**One dependency.** The HTTP client, the SSE reader, the WebSocket for the
|
|
114
|
+
browser, the PNG encoder for screenshots — all part of the package. Installing
|
|
115
|
+
Comodor pulls in `rich` and nothing else.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Everyday use
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
comodor # the interface
|
|
123
|
+
comodor run "fix the failing test" --yes # one task, no interface
|
|
124
|
+
comodor web # from a browser
|
|
125
|
+
comodor doctor # is everything alright?
|
|
126
|
+
comodor help # a written help page, not a flag dump
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
/help every command /undo restore the last change
|
|
131
|
+
/mode act · plan · chat /progress proof it is improving
|
|
132
|
+
/cost tokens and spend /computer let it use your screen
|
|
133
|
+
Esc stop it F3 cycle mode
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Documentation
|
|
139
|
+
|
|
140
|
+
**[docs/README.md](docs/README.md)** — everything, organised by what you are
|
|
141
|
+
trying to do.
|
|
142
|
+
|
|
143
|
+
| | |
|
|
144
|
+
|---|---|
|
|
145
|
+
| [Getting started](docs/getting-started.md) | Install, choose a model, first task |
|
|
146
|
+
| [The interface](docs/interface.md) | Panels, keys, and all 27 commands |
|
|
147
|
+
| [From the terminal](docs/cli.md) | Every command and flag |
|
|
148
|
+
| [What the agent can do](docs/tools.md) | The 13 tools, and when it uses each |
|
|
149
|
+
| [Safety and permissions](docs/safety.md) | What it can do, and what it never does |
|
|
150
|
+
| [How it learns](docs/learning.md) | Corrections, lessons, and the evidence |
|
|
151
|
+
| [Using your screen](docs/computer.md) | Mouse and keyboard, watched |
|
|
152
|
+
| [Configuration](docs/configuration.md) | Every setting, and what wins |
|
|
153
|
+
| [Cost](docs/cost.md) | Caching, budgets, paying less |
|
|
154
|
+
| [Troubleshooting](docs/troubleshooting.md) | When something is wrong |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
git clone https://github.com/ifekri/Comodor.git
|
|
162
|
+
cd Comodor
|
|
163
|
+
uv venv && uv pip install -e ".[dev]"
|
|
164
|
+
uv run pytest -q
|
|
165
|
+
uv run ruff check .
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Both must pass before anything is pushed, on Linux and on Windows.
|
|
169
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Licence
|
|
174
|
+
|
|
175
|
+
MIT. See [LICENSE](LICENSE).
|
|
176
|
+
|
|
177
|
+
Security: [SECURITY.md](SECURITY.md) — please do not open a public issue.
|
comodor-0.9.0/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Comodor
|
|
2
|
+
|
|
3
|
+
**A coding agent that lives in your terminal — and learns the way you correct it.**
|
|
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
|
+
|
|
10
|
+
**[Documentation](docs/README.md)** · [comodor.ai](https://comodor.ai) · [Changelog](CHANGELOG.md)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Describe a job — *fix the failing test*, *add a health endpoint*, *work out why
|
|
15
|
+
the deploy broke* — and it does the work: reads your files, writes changes, runs
|
|
16
|
+
your tests, searches the web, and keeps going until the job is done or it needs
|
|
17
|
+
you.
|
|
18
|
+
|
|
19
|
+
It asks before it changes anything, shows you exactly what it is about to do,
|
|
20
|
+
and can undo it.
|
|
21
|
+
|
|
22
|
+
What makes it different from every other tool of this kind is what happens
|
|
23
|
+
afterwards. **When you fix something it wrote, it notices, and it does not make
|
|
24
|
+
that mistake again.** Not because you configured it. Because it watched.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
› create defaults.py with 6 string constants
|
|
28
|
+
⚙ write src/defaults.py — 6 constants
|
|
29
|
+
|
|
30
|
+
… you open the file and change "30s" to '30s' …
|
|
31
|
+
|
|
32
|
+
› now add the timeout constants
|
|
33
|
+
◈ learned: Use single quotes for string literals. (31 of 34 literals)
|
|
34
|
+
⚙ write src/defaults.py — '30s', '5m'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
That is a real transcript. Nobody told it anything.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
┌─ History ──────────────┐ ┌─ Chat ───────────────────────────────────────────────┐
|
|
41
|
+
│ TASKS 2/4 ──────────── │ │ › add a health endpoint and a test for it │
|
|
42
|
+
│ ● read the app factory │ │ ◈ recalled 3 lessons · skill: review │
|
|
43
|
+
│ ● add the /health rou… │ │ │
|
|
44
|
+
│ ◐ write the test │ │ I'll add the route, then a test. │
|
|
45
|
+
│ ○ run the suite │ │ │
|
|
46
|
+
│ │ │ ⚙ edit src/app.py 0.2s │
|
|
47
|
+
│ │ │ + @app.get('/health') │
|
|
48
|
+
│ │ │ ⚙ run: pytest -q 3.4s │
|
|
49
|
+
│ │ │ 4 passed in 0.42s │
|
|
50
|
+
└────────────────────────┘ └──────────────────────────────────────────────────────┘
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv tool install comodor # or: pipx install comodor, or: pip install comodor
|
|
59
|
+
comodor
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Python 3.11 or newer. It asks five questions the first time and never asks
|
|
63
|
+
again.
|
|
64
|
+
|
|
65
|
+
No key yet? `comodor --demo` runs the whole interface offline. Or choose
|
|
66
|
+
**Ollama** in the setup and pay nothing, ever.
|
|
67
|
+
|
|
68
|
+
Already use OpenClaw or Hermes? The first screen offers to bring your keys and
|
|
69
|
+
skills across. [How that works](docs/migrating.md).
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## What it can do
|
|
74
|
+
|
|
75
|
+
| | |
|
|
76
|
+
|---|---|
|
|
77
|
+
| **Learn from your corrections** | Edit what it wrote, or say so, and the next answer follows. `/progress` shows whether that is actually working — falling correction rates, not a claim. [More](docs/learning.md) |
|
|
78
|
+
| **Ask before it acts** | Reading is silent, writing shows a diff, commands ask. Every write is checkpointed; `/undo` puts it back. [More](docs/safety.md) |
|
|
79
|
+
| **Use a real browser** | One that runs JavaScript, keeps cookies and can log in — not a page fetcher. [More](docs/browser.md) |
|
|
80
|
+
| **Use your screen** | Mouse and keyboard, in any application, with a halo on screen showing where it is about to click. Windows so far. [More](docs/computer.md) |
|
|
81
|
+
| **Follow your procedures** | Write a skill once; it loads it when the work matches. [More](docs/skills.md) |
|
|
82
|
+
| **Run anywhere** | Terminal, browser, or a container. [Web](docs/web.md) · [Docker](docs/docker.md) |
|
|
83
|
+
| **Work with any model** | Seventeen providers, or anything with an OpenAI-compatible URL. Including ones on your own machine. [More](docs/models.md) |
|
|
84
|
+
| **Cost less than it should** | 86% of input tokens served from cache, measured. [More](docs/cost.md) |
|
|
85
|
+
|
|
86
|
+
**One dependency.** The HTTP client, the SSE reader, the WebSocket for the
|
|
87
|
+
browser, the PNG encoder for screenshots — all part of the package. Installing
|
|
88
|
+
Comodor pulls in `rich` and nothing else.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Everyday use
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
comodor # the interface
|
|
96
|
+
comodor run "fix the failing test" --yes # one task, no interface
|
|
97
|
+
comodor web # from a browser
|
|
98
|
+
comodor doctor # is everything alright?
|
|
99
|
+
comodor help # a written help page, not a flag dump
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
/help every command /undo restore the last change
|
|
104
|
+
/mode act · plan · chat /progress proof it is improving
|
|
105
|
+
/cost tokens and spend /computer let it use your screen
|
|
106
|
+
Esc stop it F3 cycle mode
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Documentation
|
|
112
|
+
|
|
113
|
+
**[docs/README.md](docs/README.md)** — everything, organised by what you are
|
|
114
|
+
trying to do.
|
|
115
|
+
|
|
116
|
+
| | |
|
|
117
|
+
|---|---|
|
|
118
|
+
| [Getting started](docs/getting-started.md) | Install, choose a model, first task |
|
|
119
|
+
| [The interface](docs/interface.md) | Panels, keys, and all 27 commands |
|
|
120
|
+
| [From the terminal](docs/cli.md) | Every command and flag |
|
|
121
|
+
| [What the agent can do](docs/tools.md) | The 13 tools, and when it uses each |
|
|
122
|
+
| [Safety and permissions](docs/safety.md) | What it can do, and what it never does |
|
|
123
|
+
| [How it learns](docs/learning.md) | Corrections, lessons, and the evidence |
|
|
124
|
+
| [Using your screen](docs/computer.md) | Mouse and keyboard, watched |
|
|
125
|
+
| [Configuration](docs/configuration.md) | Every setting, and what wins |
|
|
126
|
+
| [Cost](docs/cost.md) | Caching, budgets, paying less |
|
|
127
|
+
| [Troubleshooting](docs/troubleshooting.md) | When something is wrong |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
git clone https://github.com/ifekri/Comodor.git
|
|
135
|
+
cd Comodor
|
|
136
|
+
uv venv && uv pip install -e ".[dev]"
|
|
137
|
+
uv run pytest -q
|
|
138
|
+
uv run ruff check .
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Both must pass before anything is pushed, on Linux and on Windows.
|
|
142
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Licence
|
|
147
|
+
|
|
148
|
+
MIT. See [LICENSE](LICENSE).
|
|
149
|
+
|
|
150
|
+
Security: [SECURITY.md](SECURITY.md) — please do not open a public issue.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Comodor documentation
|
|
2
|
+
|
|
3
|
+
A terminal coding agent that learns the way you correct it.
|
|
4
|
+
|
|
5
|
+
New here? **[Getting started](getting-started.md)** takes about five minutes and
|
|
6
|
+
ends with the agent doing something useful.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## By what you are trying to do
|
|
11
|
+
|
|
12
|
+
### Get going
|
|
13
|
+
|
|
14
|
+
| | |
|
|
15
|
+
|---|---|
|
|
16
|
+
| [Getting started](getting-started.md) | Install, choose a model, first task |
|
|
17
|
+
| [Coming from another agent](migrating.md) | Bring keys and skills from OpenClaw or Hermes |
|
|
18
|
+
| [Choosing a model](models.md) | Which provider, which model, what it costs |
|
|
19
|
+
|
|
20
|
+
### Use it
|
|
21
|
+
|
|
22
|
+
| | |
|
|
23
|
+
|---|---|
|
|
24
|
+
| [The interface](interface.md) | Panels, keys, modes, and all 27 commands |
|
|
25
|
+
| [From the terminal](cli.md) | Every command and flag, with examples |
|
|
26
|
+
| [What the agent can do](tools.md) | The 13 tools it has, and when it uses each |
|
|
27
|
+
| [Skills](skills.md) | Procedures you write once and it follows |
|
|
28
|
+
|
|
29
|
+
### Let it reach further
|
|
30
|
+
|
|
31
|
+
| | |
|
|
32
|
+
|---|---|
|
|
33
|
+
| [The real browser](browser.md) | A browser that runs JavaScript and can log in |
|
|
34
|
+
| [Using your screen](computer.md) | Mouse and keyboard, in any application |
|
|
35
|
+
| [From a browser](web.md) | The web interface, locally or on a server |
|
|
36
|
+
| [In Docker](docker.md) | One command, in a container |
|
|
37
|
+
| [MCP servers](mcp.md) | Tools from the Model Context Protocol |
|
|
38
|
+
|
|
39
|
+
### Understand it
|
|
40
|
+
|
|
41
|
+
| | |
|
|
42
|
+
|---|---|
|
|
43
|
+
| [How it learns](learning.md) | Corrections, lessons, rules, and the proof |
|
|
44
|
+
| [Safety and permissions](safety.md) | What it can do, what it asks, what it never does |
|
|
45
|
+
| [Cost](cost.md) | Caching, budgets, and paying less for the same work |
|
|
46
|
+
| [Configuration](configuration.md) | Every setting, where files live, what wins |
|
|
47
|
+
|
|
48
|
+
### When something is wrong
|
|
49
|
+
|
|
50
|
+
| | |
|
|
51
|
+
|---|---|
|
|
52
|
+
| [Troubleshooting](troubleshooting.md) | `doctor`, common problems, and how to report one |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## The shortest possible version
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install comodor # or: uv tool install comodor
|
|
60
|
+
comodor # it asks a few questions, once
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then type what you want. Correct it when it is wrong — edit the file, or just
|
|
64
|
+
say so — and it learns. `/progress` shows you whether that is actually working.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
comodor run "fix the failing test in tests/test_parser.py" # one task, no interface
|
|
68
|
+
comodor web # from a browser
|
|
69
|
+
comodor doctor # is everything alright?
|
|
70
|
+
comodor help # the written help page
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## What makes it different
|
|
74
|
+
|
|
75
|
+
**It learns from corrections, not from praise.** Most agents forget the moment
|
|
76
|
+
a session ends. Comodor watches what you change about its output and turns that
|
|
77
|
+
into a lesson with a confidence that rises when it holds and falls when it does
|
|
78
|
+
not. [How it learns](learning.md) explains the mechanism; `/progress` shows the
|
|
79
|
+
evidence.
|
|
80
|
+
|
|
81
|
+
**It asks before it acts, and everything is reversible.** Reading is silent.
|
|
82
|
+
Writing asks. Running a command asks louder. Every write is checkpointed, and
|
|
83
|
+
`/undo` puts the last one back. [Safety and permissions](safety.md).
|
|
84
|
+
|
|
85
|
+
**One dependency.** The HTTP client, the SSE reader, the WebSocket for the
|
|
86
|
+
browser, the PNG encoder for screenshots — all part of the package. Installing
|
|
87
|
+
Comodor pulls in `rich` and nothing else.
|
|
88
|
+
|
|
89
|
+
**It can use a real browser and a real desktop.** Not a text fetcher: a browser
|
|
90
|
+
that runs JavaScript and keeps cookies, and — on Windows — the mouse and
|
|
91
|
+
keyboard, with a halo on screen showing you where it is about to click.
|
|
92
|
+
[Browser](browser.md), [screen](computer.md).
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Also in the repository
|
|
97
|
+
|
|
98
|
+
| | |
|
|
99
|
+
|---|---|
|
|
100
|
+
| [CHANGELOG](../CHANGELOG.md) | What changed, and why |
|
|
101
|
+
| [CONTRIBUTING](../CONTRIBUTING.md) | Working on Comodor itself |
|
|
102
|
+
| [SECURITY](../SECURITY.md) | Reporting something sensitive |
|
|
103
|
+
| [RELEASING](../RELEASING.md) | How a release is cut |
|
|
@@ -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.9.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 9, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -136,6 +136,32 @@ class Conversation:
|
|
|
136
136
|
self.compactions += 1
|
|
137
137
|
return len(middle)
|
|
138
138
|
|
|
139
|
+
def forget_old_pictures(self, keep: int = 2) -> int:
|
|
140
|
+
"""Drop all but the newest screenshots. Returns how many went.
|
|
141
|
+
|
|
142
|
+
A picture costs the same every turn it stays in the history, and a
|
|
143
|
+
screen from twenty clicks ago is not what is on the screen now. The
|
|
144
|
+
message is left in place with a note where the image was, so the model
|
|
145
|
+
can see that it looked and when, without paying to look again.
|
|
146
|
+
|
|
147
|
+
`keep` is small on purpose. Two is enough to compare "before" with
|
|
148
|
+
"after"; a third is a screen two actions old, which is history.
|
|
149
|
+
"""
|
|
150
|
+
seen = 0
|
|
151
|
+
dropped = 0
|
|
152
|
+
for message in reversed(self.messages):
|
|
153
|
+
if not message.images:
|
|
154
|
+
continue
|
|
155
|
+
seen += 1
|
|
156
|
+
if seen <= keep:
|
|
157
|
+
continue
|
|
158
|
+
dropped += len(message.images)
|
|
159
|
+
message.images = []
|
|
160
|
+
note = "[the screenshot from this step is no longer in context]"
|
|
161
|
+
if note not in message.content:
|
|
162
|
+
message.content = (message.content + "\n" + note).strip()
|
|
163
|
+
return dropped
|
|
164
|
+
|
|
139
165
|
# -- introspection ---------------------------------------------------- #
|
|
140
166
|
|
|
141
167
|
def summary_line(self) -> str:
|
|
@@ -318,6 +318,15 @@ class AgentLoop:
|
|
|
318
318
|
|
|
319
319
|
def _maybe_compact(self, system_prompt: str, specs: list[ToolSpec]) -> None:
|
|
320
320
|
agent = self.config.agent
|
|
321
|
+
|
|
322
|
+
# Before measuring anything. Screenshots are the largest thing in a
|
|
323
|
+
# desktop run's history and the fastest to go stale, and dropping them
|
|
324
|
+
# is exact and free - where compaction is a model call. Doing it first
|
|
325
|
+
# also means the measurement below is of what will actually be sent.
|
|
326
|
+
gone = self.conversation.forget_old_pictures(
|
|
327
|
+
getattr(agent, "keep_screenshots", 2))
|
|
328
|
+
if gone:
|
|
329
|
+
self._emit_usage(system_prompt, specs)
|
|
321
330
|
limit = agent.context_limit or 128_000
|
|
322
331
|
if not self.conversation.needs_compaction(limit, agent.compact_at,
|
|
323
332
|
system_prompt, specs):
|
|
@@ -35,7 +35,15 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
35
35
|
parser = argparse.ArgumentParser(
|
|
36
36
|
prog="comodor",
|
|
37
37
|
description="Comodor — a terminal coding agent that learns as it works.",
|
|
38
|
+
# argparse's own help is a list of flags. That answers "what arguments
|
|
39
|
+
# does this take" and not "what is this, and what do I do now", which
|
|
40
|
+
# is what somebody typing --help is asking. See `help.py`. Only the top
|
|
41
|
+
# level is taken over: for one subcommand, the list of its flags really
|
|
42
|
+
# is the answer.
|
|
43
|
+
add_help=False,
|
|
38
44
|
)
|
|
45
|
+
parser.add_argument("-h", "--help", action="store_true", dest="want_help",
|
|
46
|
+
help="this page, or `comodor help <topic>` for one part")
|
|
39
47
|
parser.add_argument("--version", action="version", version=f"comodor {__version__}")
|
|
40
48
|
|
|
41
49
|
parser.add_argument("--provider", help="provider to use (openrouter, anthropic, …)")
|
|
@@ -62,6 +70,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
62
70
|
help="approve file writes and commands automatically")
|
|
63
71
|
run.add_argument("--max-steps", type=int, help="override the step limit")
|
|
64
72
|
|
|
73
|
+
written = sub.add_parser(
|
|
74
|
+
"help", help="what this is and how to use it, in full")
|
|
75
|
+
written.add_argument("topic", nargs="?", default="",
|
|
76
|
+
help="one part of it, in more detail")
|
|
77
|
+
|
|
65
78
|
sub.add_parser("setup", help="choose a provider and model, and save the answers")
|
|
66
79
|
brought = sub.add_parser(
|
|
67
80
|
"import", help="bring keys, model and skills over from another agent")
|
|
@@ -189,7 +202,7 @@ def run_headless(config: Config, args: argparse.Namespace) -> int:
|
|
|
189
202
|
permissions.on_denied = memory.on_denied
|
|
190
203
|
skills = load_skills(config)
|
|
191
204
|
mcp = MCPManager(config.mcp.servers) if config.mcp.enabled else None
|
|
192
|
-
tools = ToolRegistry(skills=skills, mcp=mcp,
|
|
205
|
+
tools = ToolRegistry(skills=skills, mcp=mcp, config=config,
|
|
193
206
|
spawn=spawner(config, gateway, bus, skills=skills, mcp=mcp))
|
|
194
207
|
agent = AgentLoop(config, gateway, tools, bus,
|
|
195
208
|
permissions, Conversation(), memory, skills=skills)
|
|
@@ -692,6 +705,15 @@ def run_import(config: Config, args: argparse.Namespace) -> int:
|
|
|
692
705
|
|
|
693
706
|
def main(argv: list[str] | None = None) -> int:
|
|
694
707
|
args = build_parser().parse_args(argv)
|
|
708
|
+
|
|
709
|
+
# Before the configuration is loaded: somebody who cannot get the program
|
|
710
|
+
# to start is exactly the person who needs the help page, and loading a
|
|
711
|
+
# broken config first would deny it to them.
|
|
712
|
+
if getattr(args, "want_help", False) or args.command == "help":
|
|
713
|
+
from .help import render
|
|
714
|
+
|
|
715
|
+
return render(topic=getattr(args, "topic", "") or "")
|
|
716
|
+
|
|
695
717
|
config = apply_overrides(load_config(args.cwd), args)
|
|
696
718
|
|
|
697
719
|
if args.command == "doctor":
|