wizolt 0.37.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. wizolt-0.37.0/LICENSE +28 -0
  2. wizolt-0.37.0/MANIFEST.in +4 -0
  3. wizolt-0.37.0/PKG-INFO +121 -0
  4. wizolt-0.37.0/README.md +76 -0
  5. wizolt-0.37.0/pyproject.toml +97 -0
  6. wizolt-0.37.0/setup.cfg +4 -0
  7. wizolt-0.37.0/wizolt/__init__.py +5 -0
  8. wizolt-0.37.0/wizolt/__main__.py +163 -0
  9. wizolt-0.37.0/wizolt/base.py +603 -0
  10. wizolt-0.37.0/wizolt/builtin_skills/__init__.py +1 -0
  11. wizolt-0.37.0/wizolt/builtin_skills/wizolt-help/SKILL.md +264 -0
  12. wizolt-0.37.0/wizolt/cli/__init__.py +32 -0
  13. wizolt-0.37.0/wizolt/cli/commands.py +1007 -0
  14. wizolt-0.37.0/wizolt/cli/hints.py +102 -0
  15. wizolt-0.37.0/wizolt/cli/loop.py +1046 -0
  16. wizolt-0.37.0/wizolt/cli/modals.py +901 -0
  17. wizolt-0.37.0/wizolt/cli/runtime.py +268 -0
  18. wizolt-0.37.0/wizolt/cli/update.py +94 -0
  19. wizolt-0.37.0/wizolt/cli/view.py +577 -0
  20. wizolt-0.37.0/wizolt/cli/worker.py +307 -0
  21. wizolt-0.37.0/wizolt/compaction.py +442 -0
  22. wizolt-0.37.0/wizolt/config.py +674 -0
  23. wizolt-0.37.0/wizolt/context.py +544 -0
  24. wizolt-0.37.0/wizolt/engine.py +689 -0
  25. wizolt-0.37.0/wizolt/image.py +503 -0
  26. wizolt-0.37.0/wizolt/mcp/__init__.py +8 -0
  27. wizolt-0.37.0/wizolt/mcp/config.py +74 -0
  28. wizolt-0.37.0/wizolt/mcp/manager.py +1081 -0
  29. wizolt-0.37.0/wizolt/mcp/rendering.py +378 -0
  30. wizolt-0.37.0/wizolt/mcp/tokens.py +115 -0
  31. wizolt-0.37.0/wizolt/mentions.py +577 -0
  32. wizolt-0.37.0/wizolt/model/__init__.py +10 -0
  33. wizolt-0.37.0/wizolt/model/anthropic.py +420 -0
  34. wizolt-0.37.0/wizolt/model/chat.py +229 -0
  35. wizolt-0.37.0/wizolt/model/client.py +703 -0
  36. wizolt-0.37.0/wizolt/model/history.py +14 -0
  37. wizolt-0.37.0/wizolt/model/protocol.py +375 -0
  38. wizolt-0.37.0/wizolt/model/resilience.py +252 -0
  39. wizolt-0.37.0/wizolt/model/responses.py +352 -0
  40. wizolt-0.37.0/wizolt/prompts.py +234 -0
  41. wizolt-0.37.0/wizolt/providers/__init__.py +1 -0
  42. wizolt-0.37.0/wizolt/providers/catalog.json +1999 -0
  43. wizolt-0.37.0/wizolt/providers/catalog.py +728 -0
  44. wizolt-0.37.0/wizolt/providers/compat.py +691 -0
  45. wizolt-0.37.0/wizolt/providers/schema.py +334 -0
  46. wizolt-0.37.0/wizolt/providers/sync.py +316 -0
  47. wizolt-0.37.0/wizolt/render.py +1579 -0
  48. wizolt-0.37.0/wizolt/runner.py +578 -0
  49. wizolt-0.37.0/wizolt/session/__init__.py +630 -0
  50. wizolt-0.37.0/wizolt/session/codec.py +483 -0
  51. wizolt-0.37.0/wizolt/session/diffs.py +225 -0
  52. wizolt-0.37.0/wizolt/session/images.py +73 -0
  53. wizolt-0.37.0/wizolt/session/jobs.py +146 -0
  54. wizolt-0.37.0/wizolt/session/queue.py +55 -0
  55. wizolt-0.37.0/wizolt/session/store.py +619 -0
  56. wizolt-0.37.0/wizolt/skill.py +123 -0
  57. wizolt-0.37.0/wizolt/tools/__init__.py +73 -0
  58. wizolt-0.37.0/wizolt/tools/ask.py +95 -0
  59. wizolt-0.37.0/wizolt/tools/base.py +252 -0
  60. wizolt-0.37.0/wizolt/tools/delegate.py +507 -0
  61. wizolt-0.37.0/wizolt/tools/editplan.py +188 -0
  62. wizolt-0.37.0/wizolt/tools/files.py +821 -0
  63. wizolt-0.37.0/wizolt/tools/mcp.py +103 -0
  64. wizolt-0.37.0/wizolt/tools/memory.py +451 -0
  65. wizolt-0.37.0/wizolt/tools/search.py +558 -0
  66. wizolt-0.37.0/wizolt/tools/shell.py +624 -0
  67. wizolt-0.37.0/wizolt/tools/skill.py +34 -0
  68. wizolt-0.37.0/wizolt/tools/toolblocks.py +378 -0
  69. wizolt-0.37.0/wizolt/tools/tooloutput.py +253 -0
  70. wizolt-0.37.0/wizolt/tools/toolscript.py +458 -0
  71. wizolt-0.37.0/wizolt/tui/__init__.py +30 -0
  72. wizolt-0.37.0/wizolt/tui/app.py +1391 -0
  73. wizolt-0.37.0/wizolt/tui/views.py +601 -0
  74. wizolt-0.37.0/wizolt/vision.py +57 -0
  75. wizolt-0.37.0/wizolt.egg-info/PKG-INFO +121 -0
  76. wizolt-0.37.0/wizolt.egg-info/SOURCES.txt +78 -0
  77. wizolt-0.37.0/wizolt.egg-info/dependency_links.txt +1 -0
  78. wizolt-0.37.0/wizolt.egg-info/entry_points.txt +2 -0
  79. wizolt-0.37.0/wizolt.egg-info/requires.txt +17 -0
  80. wizolt-0.37.0/wizolt.egg-info/top_level.txt +1 -0
wizolt-0.37.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.
@@ -0,0 +1,4 @@
1
+ include LICENSE
2
+ recursive-include wizolt/builtin_skills *.md
3
+ prune demo*
4
+ prune tests
wizolt-0.37.0/PKG-INFO ADDED
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: wizolt
3
+ Version: 0.37.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: openai>=3.0.0
33
+ Requires-Dist: pathspec>=1.0
34
+ Requires-Dist: pillow>=11.0
35
+ Requires-Dist: prompt-toolkit>=3.0.53
36
+ Requires-Dist: rich>=13.0
37
+ Requires-Dist: socksio>=1.0.0
38
+ Requires-Dist: websockets>=15.0.1
39
+ Provides-Extra: dev
40
+ Requires-Dist: pyright>=1.1.411; extra == "dev"
41
+ Requires-Dist: pytest>=8.0; extra == "dev"
42
+ Requires-Dist: pytest-xdist>=3.8; extra == "dev"
43
+ Requires-Dist: ruff==0.16.0; extra == "dev"
44
+ Dynamic: license-file
45
+
46
+ <h1 align="center">wizolt</h1>
47
+
48
+ <p align="center">
49
+ <img src="https://raw.githubusercontent.com/hit9/wizolt/master/snapshots/wizolt1.gif" alt="wizolt editing code and running tools" width="600">
50
+ </p>
51
+
52
+ <p align="center">
53
+ A terminal coding agent I use, maintain, and customize, shipped as a self-contained Python package.
54
+ </p>
55
+
56
+ ## Safety
57
+
58
+ **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.
59
+
60
+ ## What it is
61
+
62
+ 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.
63
+
64
+ 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.
65
+
66
+ Wizolt is the former minacode, which began as the single-file nanocode. The implementation outgrew both earlier names; the project history remains continuous.
67
+
68
+ <p align="center">
69
+ <img src="https://raw.githubusercontent.com/hit9/wizolt/master/snapshots/wizolt2.gif" alt="wizolt resuming a saved session" width="600">
70
+ </p>
71
+ <p align="center"><sub>Resuming a saved session with its conversation and tool history.</sub></p>
72
+
73
+ ## Highlights
74
+
75
+ - **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.
76
+ - **Forced reply language:** `/language` or `[runtime] language` pins the reply language for the session.
77
+ - **Smarter retries:** exponential backoff with jitter and provider `Retry-After`, shown as a live `retrying` phase with a countdown.
78
+ - **Prompt-cache aware:** stable request prefixes let supported providers reuse work and can reach 90–99% cache hit rates; `/status` shows the reported result.
79
+ - **Code navigation:** jump to definitions, callers, and implementations with a searchable code index.
80
+ - **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.
81
+ - **Anchored edits:** structured edits use `line:hash` anchors and reject stale file content.
82
+ - **Resumable sessions:** conversation, tool calls, diffs, and working memory survive `-c` or `--resume`.
83
+ - **Built-in diff viewer:** `/diff` shows the latest round and the net session result.
84
+ - **MCP and skills:** connect Model Context Protocol servers and load Markdown instruction packs on demand.
85
+ - **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.
86
+ - **Provider compatibility:** OpenAI-compatible APIs and Anthropic.
87
+
88
+ ## Install
89
+
90
+ Requires macOS or Linux, Python 3.11+, and [uv](https://docs.astral.sh/uv/).
91
+
92
+ ```sh
93
+ uv tool install wizolt
94
+ wizolt --init-config
95
+ ```
96
+
97
+ Add your provider to `~/.wizolt/config.toml`:
98
+
99
+ ```toml
100
+ [provider]
101
+ active = "default"
102
+
103
+ [provider.default]
104
+ url = "https://api.deepseek.com"
105
+ key = "sk-..."
106
+ model = "deepseek-v4-flash"
107
+ ```
108
+
109
+ Then run:
110
+
111
+ ```sh
112
+ wizolt
113
+ ```
114
+
115
+ Upgrade with `uv tool upgrade wizolt`.
116
+
117
+ ## Links
118
+
119
+ - [Documentation](https://wizolt.readthedocs.io/en/latest/) — full usage guide and reference.
120
+ - [Blog post](https://hit9.dev/post/nanocode) — why and how it was built.
121
+ - [code-symbol-index](https://github.com/hit9/code-symbol-index) — the code index library wizolt uses.
@@ -0,0 +1,76 @@
1
+ <h1 align="center">wizolt</h1>
2
+
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/hit9/wizolt/master/snapshots/wizolt1.gif" alt="wizolt editing code and running tools" width="600">
5
+ </p>
6
+
7
+ <p align="center">
8
+ A terminal coding agent I use, maintain, and customize, shipped as a self-contained Python package.
9
+ </p>
10
+
11
+ ## Safety
12
+
13
+ **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.
14
+
15
+ ## What it is
16
+
17
+ 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.
18
+
19
+ 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.
20
+
21
+ Wizolt is the former minacode, which began as the single-file nanocode. The implementation outgrew both earlier names; the project history remains continuous.
22
+
23
+ <p align="center">
24
+ <img src="https://raw.githubusercontent.com/hit9/wizolt/master/snapshots/wizolt2.gif" alt="wizolt resuming a saved session" width="600">
25
+ </p>
26
+ <p align="center"><sub>Resuming a saved session with its conversation and tool history.</sub></p>
27
+
28
+ ## Highlights
29
+
30
+ - **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.
31
+ - **Forced reply language:** `/language` or `[runtime] language` pins the reply language for the session.
32
+ - **Smarter retries:** exponential backoff with jitter and provider `Retry-After`, shown as a live `retrying` phase with a countdown.
33
+ - **Prompt-cache aware:** stable request prefixes let supported providers reuse work and can reach 90–99% cache hit rates; `/status` shows the reported result.
34
+ - **Code navigation:** jump to definitions, callers, and implementations with a searchable code index.
35
+ - **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.
36
+ - **Anchored edits:** structured edits use `line:hash` anchors and reject stale file content.
37
+ - **Resumable sessions:** conversation, tool calls, diffs, and working memory survive `-c` or `--resume`.
38
+ - **Built-in diff viewer:** `/diff` shows the latest round and the net session result.
39
+ - **MCP and skills:** connect Model Context Protocol servers and load Markdown instruction packs on demand.
40
+ - **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.
41
+ - **Provider compatibility:** OpenAI-compatible APIs and Anthropic.
42
+
43
+ ## Install
44
+
45
+ Requires macOS or Linux, Python 3.11+, and [uv](https://docs.astral.sh/uv/).
46
+
47
+ ```sh
48
+ uv tool install wizolt
49
+ wizolt --init-config
50
+ ```
51
+
52
+ Add your provider to `~/.wizolt/config.toml`:
53
+
54
+ ```toml
55
+ [provider]
56
+ active = "default"
57
+
58
+ [provider.default]
59
+ url = "https://api.deepseek.com"
60
+ key = "sk-..."
61
+ model = "deepseek-v4-flash"
62
+ ```
63
+
64
+ Then run:
65
+
66
+ ```sh
67
+ wizolt
68
+ ```
69
+
70
+ Upgrade with `uv tool upgrade wizolt`.
71
+
72
+ ## Links
73
+
74
+ - [Documentation](https://wizolt.readthedocs.io/en/latest/) — full usage guide and reference.
75
+ - [Blog post](https://hit9.dev/post/nanocode) — why and how it was built.
76
+ - [code-symbol-index](https://github.com/hit9/code-symbol-index) — the code index library wizolt uses.
@@ -0,0 +1,97 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "wizolt"
7
+ version = "0.37.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
+ "openai>=3.0.0",
44
+ "pathspec>=1.0",
45
+ "pillow>=11.0",
46
+ # 3.0.53 fixed Application.invalidate() so it is safe to call from any thread; older versions
47
+ # crash with "no running event loop" when the agent thread asks for a redraw.
48
+ "prompt-toolkit>=3.0.53",
49
+ "rich>=13.0",
50
+ "socksio>=1.0.0",
51
+ "websockets>=15.0.1",
52
+ ]
53
+
54
+ [project.urls]
55
+ Homepage = "https://github.com/hit9/wizolt"
56
+ Repository = "https://github.com/hit9/wizolt"
57
+ Issues = "https://github.com/hit9/wizolt/issues"
58
+ Documentation = "https://wizolt.readthedocs.io"
59
+
60
+ [project.scripts]
61
+ wizolt = "wizolt.__main__:main"
62
+
63
+ [project.optional-dependencies]
64
+ dev = [
65
+ "pyright>=1.1.411",
66
+ "pytest>=8.0",
67
+ "pytest-xdist>=3.8",
68
+ "ruff==0.16.0",
69
+ ]
70
+
71
+ [tool.setuptools]
72
+ packages = ["wizolt", "wizolt.builtin_skills", "wizolt.cli", "wizolt.model", "wizolt.mcp", "wizolt.providers", "wizolt.session", "wizolt.tools", "wizolt.tui"]
73
+
74
+ [tool.setuptools.package-data]
75
+ wizolt = ["builtin_skills/*/SKILL.md", "providers/catalog.json"]
76
+
77
+ [tool.pytest.ini_options]
78
+ testpaths = ["tests"]
79
+ addopts = ["-n", "auto"]
80
+
81
+ [tool.ruff]
82
+ line-length = 160
83
+ target-version = "py311"
84
+
85
+ [tool.ruff.lint]
86
+ extend-select = ["F403"]
87
+ per-file-ignores = { "wizolt/__init__.py" = ["F401"] }
88
+
89
+ [tool.pyright]
90
+ include = ["wizolt"]
91
+ # `uv build` leaves a full copy of the package under build/lib. An editor that analyzes any file in
92
+ # the workspace type-checks that copy as a second definition of every class, so an assignment there
93
+ # reports a mismatch between two same-named types (`ToolRunner* is not assignable to ToolRunner`).
94
+ # `include` alone does not stop it, because the file is analyzed when opened.
95
+ exclude = ["build", "dist", "**/__pycache__", "**/.venv"]
96
+ pythonVersion = "3.11"
97
+ typeCheckingMode = "basic"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """A terminal coding agent."""
2
+
3
+ from wizolt.base import __version__
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,163 @@
1
+ """wizolt entry point: command-line argument parsing and dispatch.
2
+
3
+ Invoked through the ``wizolt`` console script or ``python -m wizolt``.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import contextlib
10
+ import os
11
+ import subprocess
12
+ import sys
13
+ import threading
14
+
15
+ from wizolt.base import ConfigError, UpdateStatus, WizoltError, __version__, configure_logging
16
+ from wizolt.cli import CommandLoop
17
+ from wizolt.cli.update import UpdateChecker
18
+ from wizolt.config import (
19
+ Config,
20
+ ConfigFile,
21
+ RuntimeSettings,
22
+ )
23
+ from wizolt.engine import Agent
24
+ from wizolt.providers.schema import CatalogError
25
+ from wizolt.providers.sync import CatalogRuntime
26
+ from wizolt.render import Theme
27
+ from wizolt.session import Session
28
+
29
+
30
+ def run_update() -> int:
31
+ """Check PyPI for a newer wizolt and upgrade it via the detected package manager."""
32
+ print(f"wizolt {__version__}")
33
+ try:
34
+ latest = UpdateChecker.fetch_latest()
35
+ except Exception as error: # noqa: BLE001 - update failures from any network/backend layer are reported uniformly.
36
+ print(f"Error: could not check the latest version: {error}", file=sys.stderr)
37
+ return 1
38
+ if not UpdateStatus(latest=latest).newer_than(__version__):
39
+ print(f"already up to date ({__version__})")
40
+ return 0
41
+ command = UpdateChecker.upgrade_command()
42
+ print(f"updating {__version__} -> {latest}: {' '.join(command)}")
43
+ try:
44
+ return subprocess.call(command)
45
+ except OSError as error:
46
+ print(f"Error: could not run the upgrade command: {error}", file=sys.stderr)
47
+ return 1
48
+
49
+
50
+ def warm_provider_sdks() -> None:
51
+ """Import the provider SDKs off the main thread so the prompt accepts input immediately.
52
+
53
+ ModelClient imports them lazily because they cost ~0.8s, which was the whole of the delay
54
+ before a fresh prompt echoed keystrokes. Loading them here in the background keeps the prompt
55
+ instant without moving that cost onto the first request: the user's first message takes far
56
+ longer to type than the import takes to finish.
57
+
58
+ Racing this thread against the request path is safe, and deliberately so:
59
+
60
+ - CPython locks imports per module (`importlib._bootstrap._ModuleLock`), so a request-path
61
+ `from openai import OpenAI` that lands mid-warm-up blocks on that module's lock and then
62
+ reads the finished module from `sys.modules`. It cannot observe a half-initialized module,
63
+ and both threads therefore bind the same class object.
64
+ - Per-module locks can deadlock only on an import cycle entered from two threads at once.
65
+ `anthropic` and `openai` do not import each other, and their shared dependencies form a DAG,
66
+ so the lock-wait graph has no cycle. `_DeadlockError` detection is the backstop if that ever
67
+ stops being true.
68
+ - The thread is a daemon because warming must never delay exit. CPython freezes daemon threads
69
+ at finalization rather than letting them run against a torn-down import system, so quitting
70
+ mid-import is silent.
71
+
72
+ Verified by stress test: a barrier-synchronized four-way race and repeated immediate-exit runs
73
+ produce no deadlock, no exception, and no stderr noise.
74
+ """
75
+
76
+ def load() -> None:
77
+ # Warming is only an optimization, and an uncaught failure here would print a thread
78
+ # traceback over the live prompt. Any real problem resurfaces on the request path, which
79
+ # imports the same modules and reports the failure to the user.
80
+ with contextlib.suppress(Exception):
81
+ import anthropic # noqa: F401 - imported for its side effect of populating sys.modules
82
+ import openai # noqa: F401
83
+
84
+ threading.Thread(target=load, name="sdk-warmup", daemon=True).start()
85
+
86
+
87
+ def main(argv: list[str] | None = None) -> int:
88
+ configure_logging()
89
+ if sys.platform == "win32":
90
+ print("Error: wizolt does not support native Windows; use WSL instead.", file=sys.stderr)
91
+ return 1
92
+
93
+ parser = argparse.ArgumentParser(prog="wizolt", epilog="Documentation: https://wizolt.readthedocs.io")
94
+ parser.add_argument("--config", default=None, help="Path to config TOML")
95
+ parser.add_argument("--init-config", action="store_true", help="Create a default config file")
96
+ parser.add_argument("--yolo", action="store_true", help="Skip confirmations for mutating tools")
97
+ parser.add_argument(
98
+ "--theme", choices=["auto", "light", "dark"], default="", help="Color theme (defaults to runtime.theme, then auto-detect via COLORFGBG)"
99
+ )
100
+ resume = parser.add_mutually_exclusive_group()
101
+ resume.add_argument(
102
+ "--resume",
103
+ default="",
104
+ nargs="?",
105
+ const="latest",
106
+ help='Resume a session by UID, uid prefix, or name, or "latest"/"last" for this project\'s most recent',
107
+ )
108
+ resume.add_argument("-c", "--last", "--latest", dest="continue_project", action="store_true", help="Resume the latest session in the current project")
109
+ parser.add_argument("-v", "--version", action="store_true", help="Show version")
110
+ parser.add_argument(
111
+ "command", nargs="?", choices=["update", "upgrade"], default=None, help="Maintenance command: update/upgrade wizolt to the latest version"
112
+ )
113
+ args = parser.parse_args(argv)
114
+ if args.version:
115
+ print(__version__)
116
+ return 0
117
+ if args.command in {"update", "upgrade"}:
118
+ return run_update()
119
+ try:
120
+ if args.init_config:
121
+ path, created = ConfigFile.init(args.config)
122
+ print(("Created" if created else "Exists") + " config: " + path)
123
+ return 0
124
+ # Switching sessions ends one run and starts the next rather than re-pointing a live
125
+ # object graph at another Session: everything below is built around one, and this is the
126
+ # only moment nothing is running. Teardown stays in the `finally` that already does it.
127
+ resume = args.resume or ("latest" if args.continue_project else "")
128
+ while True:
129
+ if resume:
130
+ data = ConfigFile.load(args.config)
131
+ catalog = CatalogRuntime(Config.data_dir_from(data))
132
+ config = Config.from_dict(data, policy=catalog.policy)
133
+ session = Session.load_snapshot(
134
+ resume,
135
+ config=config,
136
+ settings=RuntimeSettings.from_dict(data, yolo=args.yolo, theme=args.theme),
137
+ cwd=os.getcwd(),
138
+ catalog=catalog,
139
+ )
140
+ else:
141
+ session = Session.from_config_file(path=args.config, yolo=args.yolo, theme=args.theme)
142
+ Theme.set_mode(Theme.resolve(session.settings.theme))
143
+ warm_provider_sdks()
144
+ command_loop = CommandLoop(Agent(session))
145
+ try:
146
+ code = command_loop.run()
147
+ finally:
148
+ command_loop.close_background_output()
149
+ if session.mcp is not None:
150
+ session.mcp.close()
151
+ resume = command_loop.resume_request
152
+ if not resume:
153
+ return code
154
+ except ConfigError as error:
155
+ print("ConfigError: " + str(error), file=sys.stderr)
156
+ return 2
157
+ except (WizoltError, CatalogError) as error:
158
+ print("Error: " + str(error), file=sys.stderr)
159
+ return 1
160
+
161
+
162
+ if __name__ == "__main__":
163
+ raise SystemExit(main())