chad-code 1.0.5__tar.gz → 1.0.6__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 (105) hide show
  1. {chad_code-1.0.5 → chad_code-1.0.6}/PKG-INFO +21 -6
  2. {chad_code-1.0.5 → chad_code-1.0.6}/README.md +17 -4
  3. {chad_code-1.0.5 → chad_code-1.0.6}/pyproject.toml +31 -2
  4. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/__init__.py +1 -1
  5. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/agent.py +43 -20
  6. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/cli.py +6 -6
  7. chad_code-1.0.6/src/chad/parakeet/LICENSE +201 -0
  8. chad_code-1.0.6/src/chad/parakeet/__init__.py +27 -0
  9. chad_code-1.0.6/src/chad/parakeet/alignment.py +287 -0
  10. chad_code-1.0.6/src/chad/parakeet/attention.py +640 -0
  11. chad_code-1.0.6/src/chad/parakeet/audio.py +183 -0
  12. chad_code-1.0.6/src/chad/parakeet/cache.py +156 -0
  13. chad_code-1.0.6/src/chad/parakeet/conformer.py +423 -0
  14. chad_code-1.0.6/src/chad/parakeet/ctc.py +34 -0
  15. chad_code-1.0.6/src/chad/parakeet/parakeet.py +1055 -0
  16. chad_code-1.0.6/src/chad/parakeet/rnnt.py +155 -0
  17. chad_code-1.0.6/src/chad/parakeet/tokenizer.py +3 -0
  18. chad_code-1.0.6/src/chad/parakeet/utils.py +112 -0
  19. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/prove.py +1 -1
  20. chad_code-1.0.6/src/chad/speech.py +565 -0
  21. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/tools.py +1 -1
  22. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/tui.py +296 -13
  23. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad_code.egg-info/PKG-INFO +21 -6
  24. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad_code.egg-info/SOURCES.txt +15 -0
  25. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad_code.egg-info/requires.txt +4 -1
  26. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_agent_e2e.py +14 -14
  27. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_engine.py +1 -1
  28. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_garble_invariant.py +1 -1
  29. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_gate.py +67 -11
  30. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_lever_bite.py +4 -4
  31. chad_code-1.0.6/tests/test_speech.py +284 -0
  32. chad_code-1.0.6/tests/test_speech_tui.py +508 -0
  33. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_subagent.py +7 -5
  34. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_tui.py +50 -1
  35. {chad_code-1.0.5 → chad_code-1.0.6}/LICENSE +0 -0
  36. {chad_code-1.0.5 → chad_code-1.0.6}/setup.cfg +0 -0
  37. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/atif.py +0 -0
  38. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/base_engine.py +0 -0
  39. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/bench.py +0 -0
  40. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/compaction.py +0 -0
  41. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/completion_engine.py +0 -0
  42. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/config.py +0 -0
  43. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/diag.py +0 -0
  44. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/engine.py +0 -0
  45. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/guardrails.py +0 -0
  46. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/ignore.py +0 -0
  47. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/levers.py +0 -0
  48. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/lsp.py +0 -0
  49. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/mcp.py +0 -0
  50. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/mcp_oauth.py +0 -0
  51. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/mlx_fastpath.py +0 -0
  52. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/mlx_qsdpa.py +0 -0
  53. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/profiles.py +0 -0
  54. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/prompt.py +0 -0
  55. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/render.py +0 -0
  56. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/repomap.py +0 -0
  57. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/serve.py +0 -0
  58. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/session.py +0 -0
  59. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/skills.py +0 -0
  60. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/symbols.py +0 -0
  61. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/syntaxgate.py +0 -0
  62. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/toolcall_parse.py +0 -0
  63. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad/validate.py +0 -0
  64. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad_code.egg-info/dependency_links.txt +0 -0
  65. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad_code.egg-info/entry_points.txt +0 -0
  66. {chad_code-1.0.5 → chad_code-1.0.6}/src/chad_code.egg-info/top_level.txt +0 -0
  67. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_agent.py +0 -0
  68. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_agent_guards.py +0 -0
  69. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_atif.py +0 -0
  70. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_bench.py +0 -0
  71. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_cli.py +0 -0
  72. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_compact_notice.py +0 -0
  73. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_compaction.py +0 -0
  74. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_completion_engine.py +0 -0
  75. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_config.py +0 -0
  76. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_confirm_preview.py +0 -0
  77. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_done_audit.py +0 -0
  78. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_drift_warn.py +0 -0
  79. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_edit.py +0 -0
  80. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_edit_corruption.py +0 -0
  81. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_engine_kvquant.py +0 -0
  82. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_engine_pld_hybrid.py +0 -0
  83. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_feel_pack.py +0 -0
  84. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_ignore.py +0 -0
  85. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_intent.py +0 -0
  86. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_levers.py +0 -0
  87. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_log_redaction.py +0 -0
  88. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_lsp.py +0 -0
  89. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_mcp.py +0 -0
  90. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_mcp_oauth.py +0 -0
  91. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_mlx_fastpath.py +0 -0
  92. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_mlx_qsdpa.py +0 -0
  93. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_plan_review.py +0 -0
  94. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_prove.py +0 -0
  95. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_render.py +0 -0
  96. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_replace_lines.py +0 -0
  97. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_repomap.py +0 -0
  98. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_serve.py +0 -0
  99. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_session.py +0 -0
  100. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_skills.py +0 -0
  101. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_symbols.py +0 -0
  102. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_syntaxgate.py +0 -0
  103. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_toolcall_parse.py +0 -0
  104. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_tools.py +0 -0
  105. {chad_code-1.0.5 → chad_code-1.0.6}/tests/test_validate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chad-code
3
- Version: 1.0.5
3
+ Version: 1.0.6
4
4
  Summary: Local MLX-backed, Claude-Code-style coding agent (Apple Silicon, Ornith 35B/9B)
5
5
  License-Expression: MIT
6
6
  Project-URL: Repository, https://github.com/nathansutton/chad
@@ -21,7 +21,7 @@ Requires-Dist: mlx<0.33,>=0.32.0; sys_platform == "darwin"
21
21
  Requires-Dist: mlx-lm<0.32,>=0.31.3; sys_platform == "darwin"
22
22
  Requires-Dist: transformers<5.13,>=5.0
23
23
  Requires-Dist: jinja2>=3.1
24
- Requires-Dist: numpy<2,>=1.26
24
+ Requires-Dist: numpy<3,>=1.26
25
25
  Requires-Dist: huggingface-hub>=1.22.0
26
26
  Requires-Dist: jedi>=0.19.2
27
27
  Requires-Dist: prompt_toolkit>=3.0.43
@@ -33,6 +33,8 @@ Provides-Extra: highlight
33
33
  Requires-Dist: pygments>=2.17; extra == "highlight"
34
34
  Provides-Extra: lsp
35
35
  Requires-Dist: serena-agent>=1.5; extra == "lsp"
36
+ Provides-Extra: speech
37
+ Requires-Dist: sounddevice>=0.5; extra == "speech"
36
38
  Dynamic: license-file
37
39
 
38
40
  # chad — a local, MLX-backed coding agent
@@ -81,10 +83,12 @@ instrument:
81
83
  | **Harness** | open-ended, anything you can imagine | plan. execute. nothing else. |
82
84
  | **When wrong** | reasons a way out | already shipped |
83
85
 
84
- ![chad fixing a failing test end to end — reason, read, edit, rerun pytest, all on a local 35B](docs/demo.gif)
86
+ ![chad fixing a failing test end to end — reason, read, edit, run pytest, confirm green, all on a local 35B](docs/demo.gif)
85
87
 
86
- > Real session, unedited (cold model load cut): a local 35B reasons through the failure,
87
- > edits the file, reruns the tests, confirms green.
88
+ > Real session, unedited (the silent prefill is cut): a local 35B finds the cent that
89
+ > floor division loses, fixes it, and verifies itself — then your own `pytest`, in your
90
+ > own shell. Recorded with `--yolo` so nothing pauses for a keypress; the default mode
91
+ > stops and asks before every edit and every command.
88
92
 
89
93
  ## Frontier scores, laptop cost
90
94
 
@@ -171,7 +175,10 @@ it chad uses the tree-sitter fallback automatically.
171
175
  `uv run chad` launches a full-screen terminal UI (built on prompt_toolkit):
172
176
 
173
177
  - **shift-tab cycles permission modes** — `normal` (confirm each bash/write/edit) →
174
- `auto-accept edits` → `plan mode` (read-only: investigate + propose a numbered plan) → back.
178
+ `auto-accept edits` (edits land silently; **terminal commands still ask**) → `yolo`
179
+ (nothing asks) → `plan mode` (read-only: investigate + propose a numbered plan) → back.
180
+ - **the approval prompt shows what you're approving** — the full command on its own lines
181
+ above the input, not a clipped single line. Approving blind isn't approving.
175
182
  - **type-ahead message queue** — keep typing while the agent works; messages run in order.
176
183
  - **ctrl-c interrupts the running turn** without killing the session.
177
184
  - **live status line** — model, mode, context %, a state glyph + verb, elapsed seconds, and
@@ -181,6 +188,14 @@ it chad uses the tree-sitter fallback automatically.
181
188
  `/model`, `/mode`, `/help`, `/exit`. Same set in the `--repl` line interface.
182
189
  - **`@file` / `@dir` mentions** and **`!command` shell passthrough** — pull a file into
183
190
  context inline, or run a shell command without invoking the model.
191
+ - **voice mode, all local** — `/speech`, then ctrl-t to talk: Parakeet-on-MLX transcribes
192
+ into the input box for you to review before Enter sends it (esc discards a take);
193
+ replies are read aloud via macOS `say`. A pre-roll buffer means your first syllable
194
+ isn't clipped, and a personal word table (`~/.chad/speech_words.json`) teaches it your
195
+ identifiers — `{"pie test": "pytest"}`. Dictation cost is linear in take length, so a
196
+ long thought is fine; `/speech` off releases both the mic and the weights. Nothing
197
+ leaves the machine. Needs the `speech` extra (`uv sync --extra speech` — just a mic
198
+ library; no torch, no numba).
184
199
 
185
200
  **Usage.** `uv run chad --help` is the source of truth:
186
201
 
@@ -44,10 +44,12 @@ instrument:
44
44
  | **Harness** | open-ended, anything you can imagine | plan. execute. nothing else. |
45
45
  | **When wrong** | reasons a way out | already shipped |
46
46
 
47
- ![chad fixing a failing test end to end — reason, read, edit, rerun pytest, all on a local 35B](docs/demo.gif)
47
+ ![chad fixing a failing test end to end — reason, read, edit, run pytest, confirm green, all on a local 35B](docs/demo.gif)
48
48
 
49
- > Real session, unedited (cold model load cut): a local 35B reasons through the failure,
50
- > edits the file, reruns the tests, confirms green.
49
+ > Real session, unedited (the silent prefill is cut): a local 35B finds the cent that
50
+ > floor division loses, fixes it, and verifies itself — then your own `pytest`, in your
51
+ > own shell. Recorded with `--yolo` so nothing pauses for a keypress; the default mode
52
+ > stops and asks before every edit and every command.
51
53
 
52
54
  ## Frontier scores, laptop cost
53
55
 
@@ -134,7 +136,10 @@ it chad uses the tree-sitter fallback automatically.
134
136
  `uv run chad` launches a full-screen terminal UI (built on prompt_toolkit):
135
137
 
136
138
  - **shift-tab cycles permission modes** — `normal` (confirm each bash/write/edit) →
137
- `auto-accept edits` → `plan mode` (read-only: investigate + propose a numbered plan) → back.
139
+ `auto-accept edits` (edits land silently; **terminal commands still ask**) → `yolo`
140
+ (nothing asks) → `plan mode` (read-only: investigate + propose a numbered plan) → back.
141
+ - **the approval prompt shows what you're approving** — the full command on its own lines
142
+ above the input, not a clipped single line. Approving blind isn't approving.
138
143
  - **type-ahead message queue** — keep typing while the agent works; messages run in order.
139
144
  - **ctrl-c interrupts the running turn** without killing the session.
140
145
  - **live status line** — model, mode, context %, a state glyph + verb, elapsed seconds, and
@@ -144,6 +149,14 @@ it chad uses the tree-sitter fallback automatically.
144
149
  `/model`, `/mode`, `/help`, `/exit`. Same set in the `--repl` line interface.
145
150
  - **`@file` / `@dir` mentions** and **`!command` shell passthrough** — pull a file into
146
151
  context inline, or run a shell command without invoking the model.
152
+ - **voice mode, all local** — `/speech`, then ctrl-t to talk: Parakeet-on-MLX transcribes
153
+ into the input box for you to review before Enter sends it (esc discards a take);
154
+ replies are read aloud via macOS `say`. A pre-roll buffer means your first syllable
155
+ isn't clipped, and a personal word table (`~/.chad/speech_words.json`) teaches it your
156
+ identifiers — `{"pie test": "pytest"}`. Dictation cost is linear in take length, so a
157
+ long thought is fine; `/speech` off releases both the mic and the weights. Nothing
158
+ leaves the machine. Needs the `speech` extra (`uv sync --extra speech` — just a mic
159
+ library; no torch, no numba).
147
160
 
148
161
  **Usage.** `uv run chad --help` is the source of truth:
149
162
 
@@ -4,7 +4,7 @@
4
4
  # import name, and command name are independent. `uvx chad-code` runs the alias
5
5
  # script added under [project.scripts].
6
6
  name = "chad-code"
7
- version = "1.0.5"
7
+ version = "1.0.6"
8
8
  description = "Local MLX-backed, Claude-Code-style coding agent (Apple Silicon, Ornith 35B/9B)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -49,7 +49,10 @@ dependencies = [
49
49
  # absent in a clean install (e.g. chad inside a Linux benchmark container), where its
50
50
  # absence crashes the first turn. Declare it directly.
51
51
  "jinja2>=3.1",
52
- "numpy>=1.26,<2",
52
+ # <3: the old <2 cap was 1.0.0-era caution, not a known break. numpy 2.4.6
53
+ # verified end-to-end (full gate + live Parakeet-on-MLX transcription +
54
+ # `chad prove` 4/4); staying open keeps 2.x-floored ASR deps adoptable.
55
+ "numpy>=1.26,<3",
53
56
  "huggingface-hub>=1.22.0", # first-run model download (cli.py/engine.py); imported directly, not just transitively
54
57
  "jedi>=0.19.2", # in-process symbolic code intelligence (read/search/edit)
55
58
  "prompt_toolkit>=3.0.43",
@@ -78,6 +81,16 @@ highlight = ["pygments>=2.17"]
78
81
  # heavyweight tree; absent, every caller falls back to the tree-sitter backend.
79
82
  # Install with `uv sync --extra lsp`.
80
83
  lsp = ["serena-agent>=1.5"]
84
+ # Voice mode (/speech in the TUI): push-to-talk dictation via chad's vendored
85
+ # Parakeet-on-MLX (src/chad/parakeet — the PyPI parakeet-mlx drags librosa's
86
+ # numba/scipy tree for one mel-filterbank call) + spoken replies via macOS
87
+ # `say`. All on-device, nothing leaves the machine. mlx/numpy/huggingface-hub
88
+ # are already core deps, so the extra adds only the mic (sounddevice/PortAudio).
89
+ # Optional and import-guarded in speech.py: absent, /speech prints the install
90
+ # hint and everything else is untouched. Install with `uv sync --extra speech`.
91
+ speech = [
92
+ "sounddevice>=0.5",
93
+ ]
81
94
 
82
95
  # The one entrypoint: `uv run chad`. (`chad-bench` reproduces docs/benchmarks.md.)
83
96
  # `chad-code` is an alias so `uvx chad-code` (which runs the console script matching
@@ -107,6 +120,10 @@ build-backend = "setuptools.build_meta"
107
120
  [tool.setuptools.packages.find]
108
121
  where = ["src"]
109
122
 
123
+ # The vendored Parakeet code is Apache-2.0; its license text must travel with
124
+ # the code, wheel included.
125
+ [tool.setuptools.package-data]
126
+ "chad.parakeet" = ["LICENSE"]
110
127
  [tool.pytest.ini_options]
111
128
  testpaths = ["tests"]
112
129
  # No "-q" here: CI/docs/agents all invoke `pytest -q` explicitly, and stacking the two
@@ -127,6 +144,12 @@ src = ["src", "tests"]
127
144
  select = ["F", "E9", "I"]
128
145
  ignore = ["E741"] # ambiguous var names (l/I) appear in existing math code
129
146
 
147
+ [tool.ruff.lint.per-file-ignores]
148
+ # Vendored upstream (see chad/parakeet/__init__.py), kept byte-close for
149
+ # future re-syncs — not held to chad's own lint standard. (F401: imports left
150
+ # in place for upstream fidelity after the transcribe(path) cut.)
151
+ "src/chad/parakeet/*" = ["F401", "F821", "F841", "E741"]
152
+
130
153
  [tool.mypy]
131
154
  python_version = "3.11"
132
155
  files = ["src/chad"]
@@ -142,6 +165,12 @@ disable_error_code = ["var-annotated"]
142
165
  # Tighten (disallow_untyped_defs, re-enable var-annotated, add B/UP/SIM in ruff) in a
143
166
  # follow-up once this baseline holds.
144
167
 
168
+ [[tool.mypy.overrides]]
169
+ # Vendored upstream inference code (see chad/parakeet/__init__.py) is not held
170
+ # to chad's gate so re-syncs stay a plain re-copy.
171
+ module = "chad.parakeet.*"
172
+ ignore_errors = true
173
+
145
174
  [[tool.mypy.overrides]]
146
175
  # mlx_fastpath patches mlx_lm classes that only exist on macOS: with mlx installed
147
176
  # (dev Macs, the macos CI jobs) its `type: ignore[method-assign]` comments are load-
@@ -4,7 +4,7 @@ A flat collection of cooperating modules behind one console script (``chad``):
4
4
  the inference engine, the tool layer, the agent loop, and the terminal UI.
5
5
  """
6
6
 
7
- __version__ = "1.0.5"
7
+ __version__ = "1.0.6"
8
8
 
9
9
  # chad sets no MLX_* runtime vars. MLX_METAL_FAST_SYNCH, MLX_MAX_OPS_PER_BUFFER
10
10
  # and MLX_MAX_MB_PER_BUFFER were each measured end-to-end on the 35B and every
@@ -57,11 +57,12 @@ SUBAGENT_READ_ONLY = {
57
57
 
58
58
  def subagent_tools_for(parent_mode: str, requested: str) -> str:
59
59
  """The toolset a sub-agent may run with. A sub-agent auto-approves its own tool
60
- calls (mode='auto', no confirm callback), so it must never hold more autonomy
61
- than its parent: only an 'auto' parent (--yolo / headless) may delegate 'all';
62
- a 'normal' parent's human-approval promise and plan mode's read-only promise
63
- both clamp the sub-agent to read-only."""
64
- if parent_mode == "auto" and requested == "all":
60
+ calls (mode='yolo', no confirm callback), so it must never hold more autonomy
61
+ than its parent: only a 'yolo' parent (--yolo / headless) may delegate 'all'.
62
+ A 'normal' parent's human-approval promise, an 'auto' parent's narrower promise
63
+ (edits yes, shell no — and 'all' includes bash), and plan mode's read-only
64
+ promise all clamp the sub-agent to read-only."""
65
+ if parent_mode == "yolo" and requested == "all":
65
66
  return "all"
66
67
  return "read-only"
67
68
 
@@ -209,10 +210,30 @@ def close_unclosed_think(text: str, thinking: bool) -> str:
209
210
 
210
211
  # Permission modes (Claude Code parity, cycled with shift-tab in the TUI):
211
212
  # normal — confirm each mutating tool (bash/write/edit)
212
- # auto — auto-approve mutating tools (yolo)
213
+ # auto — auto-approve FILE EDITS only; bash and MCP tools still ask
214
+ # yolo — auto-approve every mutating tool (--yolo starts here)
213
215
  # plan — read-only: changes are blocked; the model researches and proposes a plan
214
- MODES = ("normal", "auto", "plan")
215
- MODE_LABEL = {"normal": "normal", "auto": "auto-accept edits", "plan": "plan mode"}
216
+ MODES = ("normal", "auto", "yolo", "plan")
217
+ MODE_LABEL = {"normal": "normal", "auto": "auto-accept edits", "yolo": "yolo",
218
+ "plan": "plan mode"}
219
+
220
+ # What `auto` waves through: edits to files in the workspace, whose blast radius is a
221
+ # diff you can read and revert. `bash` is excluded on purpose — a shell command can
222
+ # touch anything on the machine, is the one tool whose preview is genuinely hard to
223
+ # eyeball, and is exactly what a human is still in the loop for. MCP tools are excluded
224
+ # for the same reason (a server-side tool can send mail or hit an API). The list is
225
+ # explicit rather than `MUTATING - {"bash"}` so it fails CLOSED: a mutating tool added
226
+ # later asks for approval until someone deliberately adds it here.
227
+ AUTO_EDIT_TOOLS = {"write", "edit", "replace_lines", "insert_lines",
228
+ "replace_symbol", "insert_symbol", "rename_symbol"}
229
+
230
+
231
+ def auto_approves(mode: str, name: str) -> bool:
232
+ """Does `mode` clear tool `name` without asking a human? Read-only tools never
233
+ reach here (the caller checks is_mutating first)."""
234
+ if mode == "yolo":
235
+ return True
236
+ return mode == "auto" and name in AUTO_EDIT_TOOLS
216
237
  _PLAN_PREFIX = (
217
238
  "[PLAN MODE. Research first (read/grep/glob/repo_map/overview) — do NOT edit "
218
239
  "project files or run commands. Then write ONE self-contained plan to "
@@ -325,7 +346,7 @@ class Agent:
325
346
  skills.reset_session()
326
347
  from . import mcp
327
348
  mcp.reset_session()
328
- self.mode = mode or ("auto" if yolo else "normal")
349
+ self.mode = mode or ("yolo" if yolo else "normal")
329
350
  self.thinking = thinking # Ornith is a reasoning model; toggles <think> blocks
330
351
  # Steps per WINDOW, not a hard kill: a window that landed+verified a change
331
352
  # earns an extension (see guardrails.extend_step_cap; absolute ceiling 4x).
@@ -460,7 +481,7 @@ class Agent:
460
481
 
461
482
  @property
462
483
  def yolo(self) -> bool:
463
- return self.mode == "auto"
484
+ return self.mode == "yolo"
464
485
 
465
486
  @property
466
487
  def tok_per_s(self) -> float:
@@ -586,18 +607,18 @@ class Agent:
586
607
 
587
608
  def _confirm(self, name, args) -> bool:
588
609
  # Destructive-bash seatbelt: a catastrophic shell command (rm -rf ~, mkfs,
589
- # curl|sh, …) is screened even in --yolo/auto mode, because the model acts on
590
- # untrusted repo contents and auto mode has no human in the loop. If a confirm
610
+ # curl|sh, …) is screened even in --yolo mode, because the model acts on
611
+ # untrusted repo contents and yolo mode has no human in the loop. If a confirm
591
612
  # channel exists (TTY or callback) we force the prompt; headless with no channel
592
613
  # we BLOCK rather than execute on injection. CHAD_NO_DESTRUCTIVE_GUARD=1 opts out.
593
614
  dangerous = (name == "bash" and isinstance(args, dict)
594
615
  and not config.flag("CHAD_NO_DESTRUCTIVE_GUARD")
595
616
  and guardrails.is_destructive_bash(str(args.get("command", ""))))
596
- if self.mode == "auto" or not is_mutating(name):
617
+ if not is_mutating(name) or auto_approves(self.mode, name):
597
618
  if not dangerous:
598
619
  return True
599
620
  if self._confirm_cb is None and not sys.stdin.isatty():
600
- self._emit("info", f" [blocked destructive command in auto mode: "
621
+ self._emit("info", f" [blocked destructive command — nobody to approve it: "
601
622
  f"{args.get('command', '')!r}; set CHAD_NO_DESTRUCTIVE_GUARD=1 to allow]")
602
623
  # Tell the MODEL the truth about who blocked it and why: "[denied by
603
624
  # user]" reads as a human refusal and teaches the wrong lesson (the
@@ -642,12 +663,13 @@ class Agent:
642
663
  parent. Its grep/read churn never enters the main transcript; only this return
643
664
  does. Depth 1 only: a sub-agent can't itself call `task` (its schema omits it)."""
644
665
  # Never grant a sub-agent more autonomy than its parent: the sub-agent runs
645
- # mode='auto' with no confirm callback, so an 'all' toolset is honored only when
646
- # the parent itself is auto-approved (--yolo/headless); 'normal' (human confirms
647
- # every mutation) and 'plan' (read-only) parents clamp it to read-only.
666
+ # mode='yolo' with no confirm callback, so an 'all' toolset is honored only when
667
+ # the parent itself is fully auto-approved (--yolo/headless); 'normal' (human
668
+ # confirms every mutation), 'auto' (human still confirms shell) and 'plan'
669
+ # (read-only) parents clamp it to read-only.
648
670
  requested, tools = tools, subagent_tools_for(self.mode, tools)
649
671
  if requested == "all" and tools == "read-only":
650
- self._emit("muted", " ⌊ sub-agent clamped to read-only (parent mode is not auto)")
672
+ self._emit("muted", " ⌊ sub-agent clamped to read-only (parent mode is not yolo)")
651
673
  self._emit("muted", f" ⌊ delegating to sub-agent: {description}")
652
674
  log.info("TASK start | desc=%r | tools=%s | prompt=%r",
653
675
  description, tools, redact(prompt[:300]))
@@ -657,7 +679,7 @@ class Agent:
657
679
  try:
658
680
  sub = Agent(
659
681
  self.engine,
660
- mode="auto", # auto-approve within its restricted toolset
682
+ mode="yolo", # auto-approve within its restricted toolset
661
683
  max_steps=SUBAGENT_MAX_STEPS,
662
684
  ctx_limit=min(self.ctx_limit, SUBAGENT_CTX_LIMIT),
663
685
  thinking=self.thinking,
@@ -1509,7 +1531,8 @@ class Agent:
1509
1531
  # fire, which is how a turn once shipped a 28-token garble as its final
1510
1532
  # answer. read_only/explain asks are exempt. Banks a note so
1511
1533
  # auto-continue relaunches fresh instead of shipping nothing.
1512
- did_nothing = self.mode == "auto" and not read_only_intent and not did_work
1534
+ did_nothing = (self.mode in ("auto", "yolo")
1535
+ and not read_only_intent and not did_work)
1513
1536
  if (action_task and not read_only_intent
1514
1537
  and (not made_edit or unverified_edit)) or did_nothing:
1515
1538
  # Churn→audit handoff: this hard stop used to fire with the
@@ -712,7 +712,7 @@ def _main(argv=None):
712
712
  sys.stderr.write(f"ready in {load_s:.1f}s | context {eng.effective_ctx} tokens "
713
713
  f"(compact at {ctx_limit})\n")
714
714
 
715
- start_mode = "plan" if args.plan else ("auto" if args.yolo else "normal")
715
+ start_mode = "plan" if args.plan else ("yolo" if args.yolo else "normal")
716
716
  thinking = not args.no_think
717
717
 
718
718
  # Live per-turn ctx-limit recheck: the startup number was
@@ -758,9 +758,9 @@ def _main(argv=None):
758
758
  # auto-approve mutating tools unless the user asked for read-only --plan.
759
759
  run_mode = start_mode
760
760
  if run_mode == "normal" and not sys.stdin.isatty():
761
- run_mode = "auto"
761
+ run_mode = "yolo"
762
762
  sys.stderr.write("[headless: auto-approving tools (use --plan for read-only)]\n")
763
- agent = Agent(eng, yolo=(run_mode == "auto"), ctx_limit=ctx_limit,
763
+ agent = Agent(eng, yolo=(run_mode == "yolo"), ctx_limit=ctx_limit,
764
764
  mode=run_mode, thinking=thinking, resume=resume, persist=True,
765
765
  think_budget=args.think_budget,
766
766
  turn_budget_s=turn_budget_s, ctx_limit_fn=ctx_limit_fn)
@@ -777,7 +777,7 @@ def _main(argv=None):
777
777
  # ships as an empty diff — the measured bail signature.
778
778
  auto_continue = config.env_int("CHAD_AUTO_CONTINUE")
779
779
  continues = auto_continue if auto_continue is not None \
780
- else (2 if run_mode == "auto" else 0)
780
+ else (2 if run_mode == "yolo" else 0)
781
781
  used_continues = 0
782
782
  while agent.budget_note:
783
783
  # Base allowance first; past it, keep granting fresh attempts while most
@@ -819,7 +819,7 @@ def _main(argv=None):
819
819
  if getattr(eng, "temp", None) is not None:
820
820
  eng.temp = max(eng.temp, 0.6)
821
821
  eng.reset()
822
- agent = Agent(eng, yolo=(run_mode == "auto"), ctx_limit=ctx_limit,
822
+ agent = Agent(eng, yolo=(run_mode == "yolo"), ctx_limit=ctx_limit,
823
823
  mode=run_mode, thinking=thinking, persist=True,
824
824
  think_budget=args.think_budget,
825
825
  turn_budget_s=relaunch_s, ctx_limit_fn=ctx_limit_fn)
@@ -841,7 +841,7 @@ def _main(argv=None):
841
841
  sys.stderr.write(f"[review] task finished with {turn_budget_s - elapsed:.0f}s "
842
842
  "of budget left; running one fresh-context verification pass\n")
843
843
  eng.reset()
844
- agent = Agent(eng, yolo=(run_mode == "auto"), ctx_limit=ctx_limit,
844
+ agent = Agent(eng, yolo=(run_mode == "yolo"), ctx_limit=ctx_limit,
845
845
  mode=run_mode, thinking=thinking, persist=True,
846
846
  think_budget=args.think_budget,
847
847
  turn_budget_s=review_budget, ctx_limit_fn=ctx_limit_fn)
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,27 @@
1
+ """Vendored Parakeet-on-MLX inference — chad's STT engine, MLX-only by design.
2
+
3
+ Copied from parakeet-mlx 0.5.2 (Apache-2.0, Copyright Senstella,
4
+ https://github.com/senstella/parakeet-mlx — license text in this directory's
5
+ LICENSE file). Parakeet TDT v3 (0.6B) replaces the earlier vendored Whisper:
6
+ same speed class as the coding model's own stack, better dictation accuracy,
7
+ and its TDT decoder doesn't have Whisper's repetition-loop hallucination mode.
8
+ Crucially, its tokenizer vocabulary ships inside the model repo's config.json —
9
+ no vocab data files in this repo or the wheel.
10
+
11
+ Vendored rather than depended on because the PyPI package drags librosa
12
+ (→ numba/scipy/scikit-learn) for a single mel-filterbank call, plus typer for
13
+ a CLI chad doesn't use. What changed, relative to upstream:
14
+ - audio.py: librosa.filters.mel replaced by a local numpy implementation,
15
+ validated against librosa to <1e-8 (goldens in tests/test_speech.py);
16
+ load_audio (ffmpeg file decode) removed — chad feeds mic arrays.
17
+ - utils.py: dacite.from_dict replaced by a local recursive dataclass builder.
18
+ - parakeet.py: transcribe(path) raises (it needed the ffmpeg loader); the
19
+ array-level pipeline chad uses — get_logmel() + generate() — is untouched.
20
+ - cli.py dropped.
21
+ Every change is marked "VENDORED CHANGE"; the rest is byte-identical to
22
+ upstream, so a version bump is a re-copy plus re-applying those edits.
23
+
24
+ Runtime deps beyond chad's core tree (mlx, numpy, huggingface-hub): none.
25
+ """
26
+ from .audio import get_logmel, mel_filters # noqa: F401
27
+ from .utils import from_config, from_pretrained # noqa: F401