wizolt 0.44.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.
- wizolt-0.44.0/LICENSE +28 -0
- wizolt-0.44.0/MANIFEST.in +4 -0
- wizolt-0.44.0/PKG-INFO +126 -0
- wizolt-0.44.0/README.md +79 -0
- wizolt-0.44.0/pyproject.toml +105 -0
- wizolt-0.44.0/setup.cfg +4 -0
- wizolt-0.44.0/wizolt/__init__.py +5 -0
- wizolt-0.44.0/wizolt/__main__.py +228 -0
- wizolt-0.44.0/wizolt/base.py +637 -0
- wizolt-0.44.0/wizolt/builtin_skills/__init__.py +1 -0
- wizolt-0.44.0/wizolt/builtin_skills/wizolt-help/SKILL.md +264 -0
- wizolt-0.44.0/wizolt/cli/__init__.py +32 -0
- wizolt-0.44.0/wizolt/cli/commands.py +1084 -0
- wizolt-0.44.0/wizolt/cli/hints.py +102 -0
- wizolt-0.44.0/wizolt/cli/loop.py +1354 -0
- wizolt-0.44.0/wizolt/cli/modals.py +921 -0
- wizolt-0.44.0/wizolt/cli/runtime.py +741 -0
- wizolt-0.44.0/wizolt/cli/update.py +136 -0
- wizolt-0.44.0/wizolt/cli/view.py +619 -0
- wizolt-0.44.0/wizolt/cli/worker.py +319 -0
- wizolt-0.44.0/wizolt/compaction.py +444 -0
- wizolt-0.44.0/wizolt/config.py +674 -0
- wizolt-0.44.0/wizolt/context.py +584 -0
- wizolt-0.44.0/wizolt/engine.py +756 -0
- wizolt-0.44.0/wizolt/image.py +628 -0
- wizolt-0.44.0/wizolt/mcp/__init__.py +8 -0
- wizolt-0.44.0/wizolt/mcp/config.py +74 -0
- wizolt-0.44.0/wizolt/mcp/manager.py +1008 -0
- wizolt-0.44.0/wizolt/mcp/rendering.py +378 -0
- wizolt-0.44.0/wizolt/mcp/tokens.py +127 -0
- wizolt-0.44.0/wizolt/mentions.py +671 -0
- wizolt-0.44.0/wizolt/model/__init__.py +10 -0
- wizolt-0.44.0/wizolt/model/anthropic.py +426 -0
- wizolt-0.44.0/wizolt/model/chat.py +232 -0
- wizolt-0.44.0/wizolt/model/client.py +778 -0
- wizolt-0.44.0/wizolt/model/history.py +14 -0
- wizolt-0.44.0/wizolt/model/protocol.py +396 -0
- wizolt-0.44.0/wizolt/model/resilience.py +252 -0
- wizolt-0.44.0/wizolt/model/responses.py +355 -0
- wizolt-0.44.0/wizolt/paste.py +42 -0
- wizolt-0.44.0/wizolt/prompts.py +184 -0
- wizolt-0.44.0/wizolt/providers/__init__.py +1 -0
- wizolt-0.44.0/wizolt/providers/catalog.json +2045 -0
- wizolt-0.44.0/wizolt/providers/catalog.py +728 -0
- wizolt-0.44.0/wizolt/providers/compat.py +691 -0
- wizolt-0.44.0/wizolt/providers/schema.py +334 -0
- wizolt-0.44.0/wizolt/providers/sync.py +412 -0
- wizolt-0.44.0/wizolt/render.py +1853 -0
- wizolt-0.44.0/wizolt/runner.py +1016 -0
- wizolt-0.44.0/wizolt/session/__init__.py +772 -0
- wizolt-0.44.0/wizolt/session/codec.py +590 -0
- wizolt-0.44.0/wizolt/session/diffs.py +225 -0
- wizolt-0.44.0/wizolt/session/images.py +73 -0
- wizolt-0.44.0/wizolt/session/jobs.py +146 -0
- wizolt-0.44.0/wizolt/session/queue.py +54 -0
- wizolt-0.44.0/wizolt/session/store.py +706 -0
- wizolt-0.44.0/wizolt/skill.py +123 -0
- wizolt-0.44.0/wizolt/source/__init__.py +57 -0
- wizolt-0.44.0/wizolt/source/output.py +301 -0
- wizolt-0.44.0/wizolt/source/relocate.py +57 -0
- wizolt-0.44.0/wizolt/source/view.py +216 -0
- wizolt-0.44.0/wizolt/tools/__init__.py +73 -0
- wizolt-0.44.0/wizolt/tools/ask.py +97 -0
- wizolt-0.44.0/wizolt/tools/base.py +283 -0
- wizolt-0.44.0/wizolt/tools/delegate.py +526 -0
- wizolt-0.44.0/wizolt/tools/editplan.py +345 -0
- wizolt-0.44.0/wizolt/tools/files.py +1093 -0
- wizolt-0.44.0/wizolt/tools/mcp.py +103 -0
- wizolt-0.44.0/wizolt/tools/memory.py +446 -0
- wizolt-0.44.0/wizolt/tools/search.py +780 -0
- wizolt-0.44.0/wizolt/tools/shell.py +643 -0
- wizolt-0.44.0/wizolt/tools/skill.py +34 -0
- wizolt-0.44.0/wizolt/tools/toolblocks.py +378 -0
- wizolt-0.44.0/wizolt/tools/tooloutput.py +253 -0
- wizolt-0.44.0/wizolt/tools/toolscript.py +545 -0
- wizolt-0.44.0/wizolt/tui/__init__.py +30 -0
- wizolt-0.44.0/wizolt/tui/app.py +1797 -0
- wizolt-0.44.0/wizolt/tui/scrollback.py +266 -0
- wizolt-0.44.0/wizolt/tui/views.py +604 -0
- wizolt-0.44.0/wizolt/vision.py +56 -0
- wizolt-0.44.0/wizolt.egg-info/PKG-INFO +126 -0
- wizolt-0.44.0/wizolt.egg-info/SOURCES.txt +84 -0
- wizolt-0.44.0/wizolt.egg-info/dependency_links.txt +1 -0
- wizolt-0.44.0/wizolt.egg-info/entry_points.txt +2 -0
- wizolt-0.44.0/wizolt.egg-info/requires.txt +19 -0
- wizolt-0.44.0/wizolt.egg-info/top_level.txt +1 -0
wizolt-0.44.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, hit9
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
16
|
+
may be used to endorse or promote products derived from this software
|
|
17
|
+
without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT INCLUDING NEGLIGENCE OR OTHERWISE ARISING IN ANY WAY OUT OF THE USE OF
|
|
28
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
wizolt-0.44.0/PKG-INFO
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wizolt
|
|
3
|
+
Version: 0.44.0
|
|
4
|
+
Summary: A terminal coding agent written in Python
|
|
5
|
+
Author-email: hit9 <hit9@icloud.com>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/hit9/wizolt
|
|
8
|
+
Project-URL: Repository, https://github.com/hit9/wizolt
|
|
9
|
+
Project-URL: Issues, https://github.com/hit9/wizolt/issues
|
|
10
|
+
Project-URL: Documentation, https://wizolt.readthedocs.io
|
|
11
|
+
Keywords: ai,coding-agent,cli,terminal
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
23
|
+
Classifier: Topic :: Software Development
|
|
24
|
+
Classifier: Topic :: Terminals
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: anthropic>=1.0.0
|
|
29
|
+
Requires-Dist: code-symbol-index>=0.5.1
|
|
30
|
+
Requires-Dist: json-repair
|
|
31
|
+
Requires-Dist: fastmcp-slim[client]<4,>=3
|
|
32
|
+
Requires-Dist: httpx2>=2.12.0
|
|
33
|
+
Requires-Dist: openai>=3.0.0
|
|
34
|
+
Requires-Dist: pathspec>=1.0
|
|
35
|
+
Requires-Dist: pillow>=11.0
|
|
36
|
+
Requires-Dist: prompt-toolkit>=3.0.53
|
|
37
|
+
Requires-Dist: rich>=13.0
|
|
38
|
+
Requires-Dist: socksio>=1.0.0
|
|
39
|
+
Requires-Dist: websockets>=15.0.1
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Requires-Dist: pyright>=1.1.411; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest-asyncio>=1.0; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest-xdist>=3.8; extra == "dev"
|
|
45
|
+
Requires-Dist: ruff==0.16.0; extra == "dev"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
<h1 align="center">
|
|
49
|
+
<img src="https://raw.githubusercontent.com/hit9/wizolt/master/docs/_static/wizolt-logo.png" alt="Wizolt logo: a blue hooded terminal mage holding a lightning staff" width="112"><br>
|
|
50
|
+
wizolt
|
|
51
|
+
</h1>
|
|
52
|
+
|
|
53
|
+
<p align="center">
|
|
54
|
+
<img src="https://raw.githubusercontent.com/hit9/wizolt/master/snapshots/wizolt1.gif" alt="wizolt editing code and running tools" width="600">
|
|
55
|
+
</p>
|
|
56
|
+
|
|
57
|
+
<p align="center">
|
|
58
|
+
A terminal coding agent I use, maintain, and customize, shipped as a self-contained Python package.
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
## Safety
|
|
62
|
+
|
|
63
|
+
**Use at your own risk.** wizolt can edit files and run shell commands in the environment where it starts. It does not provide sandbox isolation; use a container or VM when needed.
|
|
64
|
+
|
|
65
|
+
## What it is
|
|
66
|
+
|
|
67
|
+
wizolt does not introduce a new kind of coding agent. It combines familiar features — reading and editing files, running commands, follow-ups, sessions, diffs, MCP, and skills — into a tool I use personally.
|
|
68
|
+
|
|
69
|
+
It works on real repositories, including its own: I use wizolt to build and maintain wizolt. Everything ships in one self-contained Python package, so I can change the behavior directly whenever I want the workflow to work differently.
|
|
70
|
+
|
|
71
|
+
Wizolt is the former minacode, which began as the single-file nanocode. The implementation outgrew both earlier names; the project history remains continuous.
|
|
72
|
+
|
|
73
|
+
<p align="center">
|
|
74
|
+
<img src="https://raw.githubusercontent.com/hit9/wizolt/master/snapshots/wizolt2.gif" alt="wizolt resuming a saved session" width="600">
|
|
75
|
+
</p>
|
|
76
|
+
<p align="center"><sub>Resuming a saved session with its conversation and tool history.</sub></p>
|
|
77
|
+
|
|
78
|
+
## Highlights
|
|
79
|
+
|
|
80
|
+
- **Worker delegation:** hand a bounded task to a second in-process session on its own provider with `/worker`; the `Delegate` tool keeps worker context across delegations until reset.
|
|
81
|
+
- **Forced reply language:** `/language` or `[runtime] language` pins the reply language for the session.
|
|
82
|
+
- **Smarter retries:** exponential backoff with jitter and provider `Retry-After`, shown as a live `retrying` phase with a countdown.
|
|
83
|
+
- **Prompt-cache aware:** stable request prefixes let supported providers reuse work and can reach 90–99% cache hit rates; `/status` shows the reported result.
|
|
84
|
+
- **Code navigation:** jump to definitions, callers, and implementations with a searchable code index.
|
|
85
|
+
- **Live follow-ups:** type while the agent works; `Enter` queues a message for the next model step, while `Ctrl-C` discards a draft or interrupts the task once the input is empty.
|
|
86
|
+
- **Evidence-checked edits:** patch from a numbered source view or exact unique text; stale and ambiguous targets are refused.
|
|
87
|
+
- **Resumable sessions:** conversation, tool calls, diffs, and working memory survive `-c` or `--resume`.
|
|
88
|
+
- **Built-in diff viewer:** `/diff` shows the latest round and the net session result.
|
|
89
|
+
- **MCP and skills:** connect Model Context Protocol servers and load Markdown instruction packs on demand.
|
|
90
|
+
- **Provider-side web search:** opt in to a provider's own search tool (OpenAI, Qwen, Anthropic, Z.AI) and see each search and its sources in the transcript.
|
|
91
|
+
- **Provider compatibility:** OpenAI-compatible APIs and Anthropic.
|
|
92
|
+
|
|
93
|
+
## Install
|
|
94
|
+
|
|
95
|
+
Requires macOS or Linux, Python 3.11+, and [uv](https://docs.astral.sh/uv/).
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
uv tool install wizolt
|
|
99
|
+
wizolt --init-config
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Add your provider to `~/.wizolt/config.toml`:
|
|
103
|
+
|
|
104
|
+
```toml
|
|
105
|
+
[provider]
|
|
106
|
+
active = "default"
|
|
107
|
+
|
|
108
|
+
[provider.default]
|
|
109
|
+
url = "https://api.deepseek.com"
|
|
110
|
+
key = "sk-..."
|
|
111
|
+
model = "deepseek-v4-flash"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Then run:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
wizolt
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Upgrade with `uv tool upgrade wizolt`.
|
|
121
|
+
|
|
122
|
+
## Links
|
|
123
|
+
|
|
124
|
+
- [Documentation](https://wizolt.readthedocs.io/en/latest/) — full usage guide and reference.
|
|
125
|
+
- [Blog post](https://hit9.dev/post/nanocode) — why and how it was built.
|
|
126
|
+
- [code-symbol-index](https://github.com/hit9/code-symbol-index) — the code index library wizolt uses.
|
wizolt-0.44.0/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/hit9/wizolt/master/docs/_static/wizolt-logo.png" alt="Wizolt logo: a blue hooded terminal mage holding a lightning staff" width="112"><br>
|
|
3
|
+
wizolt
|
|
4
|
+
</h1>
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
<img src="https://raw.githubusercontent.com/hit9/wizolt/master/snapshots/wizolt1.gif" alt="wizolt editing code and running tools" width="600">
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
A terminal coding agent I use, maintain, and customize, shipped as a self-contained Python package.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
## Safety
|
|
15
|
+
|
|
16
|
+
**Use at your own risk.** wizolt can edit files and run shell commands in the environment where it starts. It does not provide sandbox isolation; use a container or VM when needed.
|
|
17
|
+
|
|
18
|
+
## What it is
|
|
19
|
+
|
|
20
|
+
wizolt does not introduce a new kind of coding agent. It combines familiar features — reading and editing files, running commands, follow-ups, sessions, diffs, MCP, and skills — into a tool I use personally.
|
|
21
|
+
|
|
22
|
+
It works on real repositories, including its own: I use wizolt to build and maintain wizolt. Everything ships in one self-contained Python package, so I can change the behavior directly whenever I want the workflow to work differently.
|
|
23
|
+
|
|
24
|
+
Wizolt is the former minacode, which began as the single-file nanocode. The implementation outgrew both earlier names; the project history remains continuous.
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img src="https://raw.githubusercontent.com/hit9/wizolt/master/snapshots/wizolt2.gif" alt="wizolt resuming a saved session" width="600">
|
|
28
|
+
</p>
|
|
29
|
+
<p align="center"><sub>Resuming a saved session with its conversation and tool history.</sub></p>
|
|
30
|
+
|
|
31
|
+
## Highlights
|
|
32
|
+
|
|
33
|
+
- **Worker delegation:** hand a bounded task to a second in-process session on its own provider with `/worker`; the `Delegate` tool keeps worker context across delegations until reset.
|
|
34
|
+
- **Forced reply language:** `/language` or `[runtime] language` pins the reply language for the session.
|
|
35
|
+
- **Smarter retries:** exponential backoff with jitter and provider `Retry-After`, shown as a live `retrying` phase with a countdown.
|
|
36
|
+
- **Prompt-cache aware:** stable request prefixes let supported providers reuse work and can reach 90–99% cache hit rates; `/status` shows the reported result.
|
|
37
|
+
- **Code navigation:** jump to definitions, callers, and implementations with a searchable code index.
|
|
38
|
+
- **Live follow-ups:** type while the agent works; `Enter` queues a message for the next model step, while `Ctrl-C` discards a draft or interrupts the task once the input is empty.
|
|
39
|
+
- **Evidence-checked edits:** patch from a numbered source view or exact unique text; stale and ambiguous targets are refused.
|
|
40
|
+
- **Resumable sessions:** conversation, tool calls, diffs, and working memory survive `-c` or `--resume`.
|
|
41
|
+
- **Built-in diff viewer:** `/diff` shows the latest round and the net session result.
|
|
42
|
+
- **MCP and skills:** connect Model Context Protocol servers and load Markdown instruction packs on demand.
|
|
43
|
+
- **Provider-side web search:** opt in to a provider's own search tool (OpenAI, Qwen, Anthropic, Z.AI) and see each search and its sources in the transcript.
|
|
44
|
+
- **Provider compatibility:** OpenAI-compatible APIs and Anthropic.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
Requires macOS or Linux, Python 3.11+, and [uv](https://docs.astral.sh/uv/).
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
uv tool install wizolt
|
|
52
|
+
wizolt --init-config
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Add your provider to `~/.wizolt/config.toml`:
|
|
56
|
+
|
|
57
|
+
```toml
|
|
58
|
+
[provider]
|
|
59
|
+
active = "default"
|
|
60
|
+
|
|
61
|
+
[provider.default]
|
|
62
|
+
url = "https://api.deepseek.com"
|
|
63
|
+
key = "sk-..."
|
|
64
|
+
model = "deepseek-v4-flash"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then run:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
wizolt
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Upgrade with `uv tool upgrade wizolt`.
|
|
74
|
+
|
|
75
|
+
## Links
|
|
76
|
+
|
|
77
|
+
- [Documentation](https://wizolt.readthedocs.io/en/latest/) — full usage guide and reference.
|
|
78
|
+
- [Blog post](https://hit9.dev/post/nanocode) — why and how it was built.
|
|
79
|
+
- [code-symbol-index](https://github.com/hit9/code-symbol-index) — the code index library wizolt uses.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wizolt"
|
|
7
|
+
version = "0.44.0"
|
|
8
|
+
description = "A terminal coding agent written in Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "BSD-3-Clause"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "hit9", email = "hit9@icloud.com" },
|
|
15
|
+
]
|
|
16
|
+
keywords = ["ai", "coding-agent", "cli", "terminal"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Operating System :: MacOS",
|
|
22
|
+
"Operating System :: POSIX :: Linux",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Programming Language :: Python :: 3.15",
|
|
29
|
+
"Topic :: Software Development",
|
|
30
|
+
"Topic :: Terminals",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
dependencies = [
|
|
34
|
+
# anthropic 1.x and openai 3.x both moved their HTTP client to httpx2, which stops installing
|
|
35
|
+
# certifi and verifies TLS against the OS trust store instead (SSL_CERT_FILE/SSL_CERT_DIR
|
|
36
|
+
# override it). The floors pin that migration rather than leaving it to the resolver: an older
|
|
37
|
+
# pair would land on plain httpx, a combination retryable_error's transport-error matching is
|
|
38
|
+
# tested against but the rest of the tree no longer is.
|
|
39
|
+
"anthropic>=1.0.0",
|
|
40
|
+
"code-symbol-index>=0.5.1",
|
|
41
|
+
"json-repair",
|
|
42
|
+
"fastmcp-slim[client]>=3,<4",
|
|
43
|
+
# Direct, not transitive: the update check imports httpx2 itself. The provider SDKs bring it
|
|
44
|
+
# in too, but a dependency wizolt imports is wizolt's to declare.
|
|
45
|
+
"httpx2>=2.12.0",
|
|
46
|
+
"openai>=3.0.0",
|
|
47
|
+
"pathspec>=1.0",
|
|
48
|
+
"pillow>=11.0",
|
|
49
|
+
# 3.0.53 fixed Application.invalidate() so it is safe to call from any thread; older versions
|
|
50
|
+
# crash with "no running event loop" when the agent thread asks for a redraw.
|
|
51
|
+
"prompt-toolkit>=3.0.53",
|
|
52
|
+
"rich>=13.0",
|
|
53
|
+
"socksio>=1.0.0",
|
|
54
|
+
"websockets>=15.0.1",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[project.urls]
|
|
58
|
+
Homepage = "https://github.com/hit9/wizolt"
|
|
59
|
+
Repository = "https://github.com/hit9/wizolt"
|
|
60
|
+
Issues = "https://github.com/hit9/wizolt/issues"
|
|
61
|
+
Documentation = "https://wizolt.readthedocs.io"
|
|
62
|
+
|
|
63
|
+
[project.scripts]
|
|
64
|
+
wizolt = "wizolt.__main__:main"
|
|
65
|
+
|
|
66
|
+
[project.optional-dependencies]
|
|
67
|
+
dev = [
|
|
68
|
+
"pyright>=1.1.411",
|
|
69
|
+
"pytest>=8.0",
|
|
70
|
+
"pytest-asyncio>=1.0",
|
|
71
|
+
"pytest-xdist>=3.8",
|
|
72
|
+
"ruff==0.16.0",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
[tool.setuptools]
|
|
76
|
+
packages = ["wizolt", "wizolt.builtin_skills", "wizolt.cli", "wizolt.model", "wizolt.mcp", "wizolt.providers", "wizolt.session", "wizolt.source", "wizolt.tools", "wizolt.tui"]
|
|
77
|
+
|
|
78
|
+
[tool.setuptools.package-data]
|
|
79
|
+
wizolt = ["builtin_skills/*/SKILL.md", "providers/catalog.json"]
|
|
80
|
+
|
|
81
|
+
[tool.pytest.ini_options]
|
|
82
|
+
testpaths = ["tests"]
|
|
83
|
+
addopts = ["-n", "auto"]
|
|
84
|
+
# A test that awaits the code under test reads better than the same test wrapped in an
|
|
85
|
+
# asyncio.run() driver, so `async def` tests run without a per-test marker. Each gets its own
|
|
86
|
+
# loop, which is also what the production entry points do.
|
|
87
|
+
asyncio_mode = "auto"
|
|
88
|
+
|
|
89
|
+
[tool.ruff]
|
|
90
|
+
line-length = 160
|
|
91
|
+
target-version = "py311"
|
|
92
|
+
|
|
93
|
+
[tool.ruff.lint]
|
|
94
|
+
extend-select = ["F403"]
|
|
95
|
+
per-file-ignores = { "wizolt/__init__.py" = ["F401"] }
|
|
96
|
+
|
|
97
|
+
[tool.pyright]
|
|
98
|
+
include = ["wizolt"]
|
|
99
|
+
# `uv build` leaves a full copy of the package under build/lib. An editor that analyzes any file in
|
|
100
|
+
# the workspace type-checks that copy as a second definition of every class, so an assignment there
|
|
101
|
+
# reports a mismatch between two same-named types (`ToolRunner* is not assignable to ToolRunner`).
|
|
102
|
+
# `include` alone does not stop it, because the file is analyzed when opened.
|
|
103
|
+
exclude = ["build", "dist", "**/__pycache__", "**/.venv"]
|
|
104
|
+
pythonVersion = "3.11"
|
|
105
|
+
typeCheckingMode = "basic"
|
wizolt-0.44.0/setup.cfg
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""wizolt entry point: command-line argument parsing and dispatch.
|
|
2
|
+
|
|
3
|
+
Invoked through the ``wizolt`` console script or ``python -m wizolt``.
|
|
4
|
+
|
|
5
|
+
Deliberately imports nothing from the wizolt package at module level: argparse, help, version, and
|
|
6
|
+
config initialization should answer before the interactive CLI — prompt_toolkit, the tools, the
|
|
7
|
+
TUI, the session machinery — is imported. The interactive-CLI names `main` needs are reached
|
|
8
|
+
through the lazy `_cli` namespace below: reading `_cli.Session` imports it on first use and caches
|
|
9
|
+
it on this module, so tests can keep substituting fakes here without importing wizolt at module
|
|
10
|
+
load time.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import contextlib
|
|
17
|
+
import importlib
|
|
18
|
+
import os
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import threading
|
|
22
|
+
|
|
23
|
+
# Every interactive-CLI name `main` needs, as (module, attribute). Loaded on first use and cached
|
|
24
|
+
# on this module, so tests can keep substituting fakes here without `import wizolt.__main__`
|
|
25
|
+
# paying for the interactive CLI.
|
|
26
|
+
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
|
27
|
+
"__version__": ("wizolt.base", "__version__"),
|
|
28
|
+
"Agent": ("wizolt.engine", "Agent"),
|
|
29
|
+
"CatalogError": ("wizolt.providers.schema", "CatalogError"),
|
|
30
|
+
"CatalogRuntime": ("wizolt.providers.sync", "CatalogRuntime"),
|
|
31
|
+
"CommandLoop": ("wizolt.cli", "CommandLoop"),
|
|
32
|
+
"Config": ("wizolt.config", "Config"),
|
|
33
|
+
"ConfigError": ("wizolt.base", "ConfigError"),
|
|
34
|
+
"ConfigFile": ("wizolt.config", "ConfigFile"),
|
|
35
|
+
"RuntimeSettings": ("wizolt.config", "RuntimeSettings"),
|
|
36
|
+
"Session": ("wizolt.session", "Session"),
|
|
37
|
+
"Theme": ("wizolt.render", "Theme"),
|
|
38
|
+
"UpdateChecker": ("wizolt.cli.update", "UpdateChecker"),
|
|
39
|
+
"UpdateStatus": ("wizolt.base", "UpdateStatus"),
|
|
40
|
+
"WizoltError": ("wizolt.base", "WizoltError"),
|
|
41
|
+
"configure_logging": ("wizolt.base", "configure_logging"),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _import_lazy(name: str):
|
|
46
|
+
"""Import one interactive-CLI name and cache it on this module (PEP 562)."""
|
|
47
|
+
spec = _LAZY_IMPORTS[name]
|
|
48
|
+
value = getattr(importlib.import_module(spec[0]), spec[1])
|
|
49
|
+
globals()[name] = value
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def __getattr__(name: str):
|
|
54
|
+
"""Import a lazy interactive-CLI name on first attribute read (PEP 562)."""
|
|
55
|
+
if name not in _LAZY_IMPORTS:
|
|
56
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
57
|
+
return _import_lazy(name)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class _EntryCli:
|
|
61
|
+
"""The entry point's handle on the interactive CLI. Reading an attribute (`_cli.Session`)
|
|
62
|
+
imports that name on first use and caches it on this module, so later reads are plain
|
|
63
|
+
attribute lookups; a name a test has already bound here (a fake) is returned as-is. This is
|
|
64
|
+
the seam that keeps `wizolt.__main__` importable without the interactive CLI while `main`
|
|
65
|
+
still resolves the real classes through module attributes the way it always has."""
|
|
66
|
+
|
|
67
|
+
def __getattr__(self, name: str):
|
|
68
|
+
bound = globals().get(name)
|
|
69
|
+
if bound is not None:
|
|
70
|
+
return bound
|
|
71
|
+
if name not in _LAZY_IMPORTS:
|
|
72
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
73
|
+
return _import_lazy(name)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
_cli = _EntryCli()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def run_update() -> int:
|
|
80
|
+
"""Check PyPI for a newer wizolt and upgrade it via the detected package manager."""
|
|
81
|
+
print(f"wizolt {_cli.__version__}")
|
|
82
|
+
try:
|
|
83
|
+
latest = _cli.UpdateChecker.fetch_latest_sync()
|
|
84
|
+
except Exception as error: # noqa: BLE001 - update failures from any network/backend layer are reported uniformly.
|
|
85
|
+
print(f"Error: could not check the latest version: {error}", file=sys.stderr)
|
|
86
|
+
return 1
|
|
87
|
+
if not _cli.UpdateStatus(latest=latest).newer_than(_cli.__version__):
|
|
88
|
+
print(f"already up to date ({_cli.__version__})")
|
|
89
|
+
return 0
|
|
90
|
+
command = _cli.UpdateChecker.upgrade_command()
|
|
91
|
+
print(f"updating {_cli.__version__} -> {latest}: {' '.join(command)}")
|
|
92
|
+
try:
|
|
93
|
+
return subprocess.call(command)
|
|
94
|
+
except OSError as error:
|
|
95
|
+
print(f"Error: could not run the upgrade command: {error}", file=sys.stderr)
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def warm_provider_sdks() -> None:
|
|
100
|
+
"""Import the provider SDKs off the main thread so the prompt accepts input immediately.
|
|
101
|
+
|
|
102
|
+
ModelClient imports them lazily because they cost ~0.8s, which was the whole of the delay
|
|
103
|
+
before a fresh prompt echoed keystrokes. Loading them here in the background keeps the prompt
|
|
104
|
+
instant without moving that cost onto the first request: the user's first message takes far
|
|
105
|
+
longer to type than the import takes to finish.
|
|
106
|
+
|
|
107
|
+
Racing this thread against the request path is safe, and deliberately so:
|
|
108
|
+
|
|
109
|
+
- CPython locks imports per module (`importlib._bootstrap._ModuleLock`), so a request-path
|
|
110
|
+
`from openai import OpenAI` that lands mid-warm-up blocks on that module's lock and then
|
|
111
|
+
reads the finished module from `sys.modules`. It cannot observe a half-initialized module,
|
|
112
|
+
and both threads therefore bind the same class object.
|
|
113
|
+
- Per-module locks can deadlock only on an import cycle entered from two threads at once.
|
|
114
|
+
`anthropic` and `openai` do not import each other, and their shared dependencies form a DAG,
|
|
115
|
+
so the lock-wait graph has no cycle. `_DeadlockError` detection is the backstop if that ever
|
|
116
|
+
stops being true.
|
|
117
|
+
- The thread is a daemon because warming must never delay exit. CPython freezes daemon threads
|
|
118
|
+
at finalization rather than letting them run against a torn-down import system, so quitting
|
|
119
|
+
mid-import is silent.
|
|
120
|
+
|
|
121
|
+
Verified by stress test: a barrier-synchronized four-way race and repeated immediate-exit runs
|
|
122
|
+
produce no deadlock, no exception, and no stderr noise.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def load() -> None:
|
|
126
|
+
# Warming is only an optimization, and an uncaught failure here would print a thread
|
|
127
|
+
# traceback over the live prompt. Any real problem resurfaces on the request path, which
|
|
128
|
+
# imports the same modules and reports the failure to the user.
|
|
129
|
+
with contextlib.suppress(Exception):
|
|
130
|
+
import anthropic # noqa: F401 - imported for its side effect of populating sys.modules
|
|
131
|
+
import openai # noqa: F401
|
|
132
|
+
|
|
133
|
+
threading.Thread(target=load, name="sdk-warmup", daemon=True).start()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def main(argv: list[str] | None = None) -> int:
|
|
137
|
+
parser = argparse.ArgumentParser(prog="wizolt", epilog="Documentation: https://wizolt.readthedocs.io")
|
|
138
|
+
parser.add_argument("--config", default=None, help="Path to config TOML")
|
|
139
|
+
parser.add_argument("--init-config", action="store_true", help="Create a default config file")
|
|
140
|
+
parser.add_argument("--yolo", action="store_true", help="Skip confirmations for mutating tools")
|
|
141
|
+
parser.add_argument(
|
|
142
|
+
"--theme", choices=["auto", "light", "dark"], default="", help="Color theme (defaults to runtime.theme, then auto-detect via COLORFGBG)"
|
|
143
|
+
)
|
|
144
|
+
resume = parser.add_mutually_exclusive_group()
|
|
145
|
+
resume.add_argument(
|
|
146
|
+
"--resume",
|
|
147
|
+
default="",
|
|
148
|
+
nargs="?",
|
|
149
|
+
const="latest",
|
|
150
|
+
help='Resume a session by UID, uid prefix, or name, or "latest"/"last" for this project\'s most recent',
|
|
151
|
+
)
|
|
152
|
+
resume.add_argument("-c", "--last", "--latest", dest="continue_project", action="store_true", help="Resume the latest session in the current project")
|
|
153
|
+
parser.add_argument("-v", "--version", action="store_true", help="Show version")
|
|
154
|
+
parser.add_argument(
|
|
155
|
+
"command", nargs="?", choices=["update", "upgrade"], default=None, help="Maintenance command: update/upgrade wizolt to the latest version"
|
|
156
|
+
)
|
|
157
|
+
args = parser.parse_args(argv)
|
|
158
|
+
if sys.platform == "win32":
|
|
159
|
+
print("Error: wizolt does not support native Windows; use WSL instead.", file=sys.stderr)
|
|
160
|
+
return 1
|
|
161
|
+
# Cheap exits answer before the interactive CLI is loaded. The explicit update
|
|
162
|
+
# command loads its HTTP/update implementation here, but still never starts a session.
|
|
163
|
+
if args.version:
|
|
164
|
+
print(_cli.__version__)
|
|
165
|
+
return 0
|
|
166
|
+
if args.command in {"update", "upgrade"}:
|
|
167
|
+
return run_update()
|
|
168
|
+
if args.init_config:
|
|
169
|
+
path, created = _cli.ConfigFile.init(args.config)
|
|
170
|
+
print(("Created" if created else "Exists") + " config: " + path)
|
|
171
|
+
return 0
|
|
172
|
+
|
|
173
|
+
# This line needs only the lightweight version module. Put it on screen before importing the
|
|
174
|
+
# session and rendering stacks; the first CommandLoop consumes the handoff, while a session
|
|
175
|
+
# selected later through /resume prints its own banner normally.
|
|
176
|
+
banner_preprinted = sys.stdin.isatty() and sys.stdout.isatty()
|
|
177
|
+
preprinted_output = ""
|
|
178
|
+
if banner_preprinted:
|
|
179
|
+
preprinted_output = f"wizolt {_cli.__version__}. /help for commands.\n\n"
|
|
180
|
+
print(preprinted_output, end="", flush=True)
|
|
181
|
+
|
|
182
|
+
_cli.configure_logging()
|
|
183
|
+
try:
|
|
184
|
+
# Switching sessions ends one run and starts the next rather than re-pointing a live
|
|
185
|
+
# object graph at another Session: everything below is built around one, and this is the
|
|
186
|
+
# only moment nothing is running. Teardown stays in the `finally` that already does it.
|
|
187
|
+
resume = args.resume or ("latest" if args.continue_project else "")
|
|
188
|
+
while True:
|
|
189
|
+
if resume:
|
|
190
|
+
data = _cli.ConfigFile.load(args.config)
|
|
191
|
+
catalog = _cli.CatalogRuntime(_cli.Config.data_dir_from(data))
|
|
192
|
+
config = _cli.Config.from_dict(data, policy=catalog.policy)
|
|
193
|
+
session = _cli.Session.load_snapshot(
|
|
194
|
+
resume,
|
|
195
|
+
config=config,
|
|
196
|
+
settings=_cli.RuntimeSettings.from_dict(data, yolo=args.yolo, theme=args.theme),
|
|
197
|
+
cwd=os.getcwd(),
|
|
198
|
+
catalog=catalog,
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
session = _cli.Session.from_config_file(path=args.config, yolo=args.yolo, theme=args.theme)
|
|
202
|
+
_cli.Theme.set_mode(_cli.Theme.resolve(session.settings.theme))
|
|
203
|
+
warm_provider_sdks()
|
|
204
|
+
command_loop = _cli.CommandLoop(_cli.Agent(session))
|
|
205
|
+
try:
|
|
206
|
+
if banner_preprinted:
|
|
207
|
+
command_loop.preprinted_output = preprinted_output
|
|
208
|
+
code = command_loop.run(show_banner=False)
|
|
209
|
+
banner_preprinted = False
|
|
210
|
+
else:
|
|
211
|
+
code = command_loop.run()
|
|
212
|
+
finally:
|
|
213
|
+
# The runtime closes what the session opened, on the loop that opened it; all that
|
|
214
|
+
# is left here is the terminal-output gate, in case the runtime never got that far.
|
|
215
|
+
command_loop.close_background_output()
|
|
216
|
+
resume = command_loop.resume_request
|
|
217
|
+
if not resume:
|
|
218
|
+
return code
|
|
219
|
+
except _cli.ConfigError as error:
|
|
220
|
+
print("ConfigError: " + str(error), file=sys.stderr)
|
|
221
|
+
return 2
|
|
222
|
+
except (_cli.WizoltError, _cli.CatalogError) as error:
|
|
223
|
+
print("Error: " + str(error), file=sys.stderr)
|
|
224
|
+
return 1
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
if __name__ == "__main__":
|
|
228
|
+
raise SystemExit(main())
|