vocalize-cli 0.5.0__tar.gz → 0.6.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.
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/CHANGELOG.md +11 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/PKG-INFO +5 -1
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/README.md +4 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/hooks/claude_stop_hook.py +13 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/tests/test_claude_stop_hook.py +20 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/tests/test_cli.py +28 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/vocalize/__init__.py +1 -1
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/vocalize/cli.py +19 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/.env.example +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/.github/workflows/ci.yml +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/.gitignore +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/LICENSE +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/hooks/install_hook.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/hooks/speak_url_gate.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/pyproject.toml +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/tests/conftest.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/tests/test_audio.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/tests/test_auth.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/tests/test_config.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/tests/test_install_hook.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/tests/test_preprocess.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/tests/test_speak_url_gate.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/tests/test_tts.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/tests/test_wizard.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/vocalize/__main__.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/vocalize/audio.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/vocalize/auth.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/vocalize/config.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/vocalize/exceptions.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/vocalize/preprocess.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/vocalize/tts.py +0 -0
- {vocalize_cli-0.5.0 → vocalize_cli-0.6.0}/vocalize/wizard.py +0 -0
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project are documented here. Format follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
+
## 0.6.0 - 2026-08-31
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `vocalize settings` — prints the resolved settings, one key=value per
|
|
11
|
+
line, so wrapper scripts (like the /speak slash command) can read the
|
|
12
|
+
effective `overflow` and `max_chars` instead of hardcoding them.
|
|
13
|
+
- `--print-length` on the Stop hook: prints the response's character count
|
|
14
|
+
instead of speaking, so a wrapper can decide to ask about truncation
|
|
15
|
+
interactively before any audio is spent.
|
|
16
|
+
|
|
6
17
|
## 0.5.0 - 2026-08-31
|
|
7
18
|
|
|
8
19
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vocalize-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: A CLI that turns text, markdown, or piped stdin into speech via the ElevenLabs API, with markdown-table-aware preprocessing.
|
|
5
5
|
Project-URL: Homepage, https://github.com/matthager12-collab/vocalize
|
|
6
6
|
Project-URL: Repository, https://github.com/matthager12-collab/vocalize
|
|
@@ -225,6 +225,10 @@ regardless. With no cap set anywhere there is no overflow, so the mode
|
|
|
225
225
|
never fires. Hook-triggered speech still lives under the hook's 15-minute
|
|
226
226
|
watchdog described below, whatever the mode.
|
|
227
227
|
|
|
228
|
+
`vocalize settings` prints the resolved values (one `key=value` per
|
|
229
|
+
line, env and config applied) — handy for wrapper scripts and for checking
|
|
230
|
+
which source won.
|
|
231
|
+
|
|
228
232
|
Not having a config file is normal and silent. A file that isn't valid TOML
|
|
229
233
|
is an error naming the file; a key that isn't recognised is a warning on
|
|
230
234
|
stderr, so a typo doesn't pass unnoticed but doesn't stop the run either.
|
|
@@ -191,6 +191,10 @@ regardless. With no cap set anywhere there is no overflow, so the mode
|
|
|
191
191
|
never fires. Hook-triggered speech still lives under the hook's 15-minute
|
|
192
192
|
watchdog described below, whatever the mode.
|
|
193
193
|
|
|
194
|
+
`vocalize settings` prints the resolved values (one `key=value` per
|
|
195
|
+
line, env and config applied) — handy for wrapper scripts and for checking
|
|
196
|
+
which source won.
|
|
197
|
+
|
|
194
198
|
Not having a config file is normal and silent. A file that isn't valid TOML
|
|
195
199
|
is an error naming the file; a key that isn't recognised is a warning on
|
|
196
200
|
stderr, so a typo doesn't pass unnoticed but doesn't stop the run either.
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
that response. Run it when you want speech instead of installing the
|
|
10
10
|
hook and getting it after every turn.
|
|
11
11
|
|
|
12
|
+
Add `--print-length` to either mode to print the response's character
|
|
13
|
+
count instead of speaking — for wrappers deciding whether to ask about
|
|
14
|
+
truncation before committing to audio.
|
|
15
|
+
|
|
12
16
|
Either way it pulls the most recent assistant text message out of the
|
|
13
17
|
transcript and pipes it through the `vocalize` CLI (the same one used
|
|
14
18
|
directly from the command line), so there's exactly one code path for
|
|
@@ -125,6 +129,15 @@ def main() -> int:
|
|
|
125
129
|
|
|
126
130
|
text = _extract_last_assistant_text(transcript_path)
|
|
127
131
|
if not text.strip():
|
|
132
|
+
if "--print-length" in sys.argv[1:]:
|
|
133
|
+
print(0)
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
if "--print-length" in sys.argv[1:]:
|
|
137
|
+
# Report the response's size without speaking — the /speak command
|
|
138
|
+
# uses this to decide whether to ask about truncation in-app. Raw
|
|
139
|
+
# (pre-flattening) length: close enough for an ask threshold.
|
|
140
|
+
print(len(text))
|
|
128
141
|
return 0
|
|
129
142
|
|
|
130
143
|
# VOCALIZE_BIN wins over PATH so a venv install still resolves when the
|
|
@@ -172,6 +172,26 @@ def test_env_max_chars_is_left_for_the_cli_to_resolve(monkeypatch, tmp_path):
|
|
|
172
172
|
assert calls[0][2:4] == ["--default-max-chars", "500"]
|
|
173
173
|
|
|
174
174
|
|
|
175
|
+
def test_print_length_reports_count_without_speaking(monkeypatch, tmp_path, capsys):
|
|
176
|
+
path = _write_transcript(tmp_path, [_assistant([_text("hello there")])])
|
|
177
|
+
calls = _patch_main(monkeypatch, {"transcript_path": path})
|
|
178
|
+
monkeypatch.setattr(hook.sys, "argv", ["claude_stop_hook.py", "--print-length"])
|
|
179
|
+
|
|
180
|
+
assert hook.main() == 0
|
|
181
|
+
assert capsys.readouterr().out.strip() == "11"
|
|
182
|
+
assert calls == [] # nothing was spoken
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_print_length_reports_zero_for_an_empty_response(monkeypatch, tmp_path, capsys):
|
|
186
|
+
path = _write_transcript(tmp_path, ['{"type":"user"}'])
|
|
187
|
+
calls = _patch_main(monkeypatch, {"transcript_path": path})
|
|
188
|
+
monkeypatch.setattr(hook.sys, "argv", ["claude_stop_hook.py", "--print-length"])
|
|
189
|
+
|
|
190
|
+
assert hook.main() == 0
|
|
191
|
+
assert capsys.readouterr().out.strip() == "0"
|
|
192
|
+
assert calls == []
|
|
193
|
+
|
|
194
|
+
|
|
175
195
|
def test_speech_timeout_scales_with_text_and_is_capped():
|
|
176
196
|
assert hook._speech_timeout("") == 60
|
|
177
197
|
assert hook._speech_timeout("x" * 1200) == 60 + 100
|
|
@@ -583,6 +583,34 @@ def test_ask_to_truncate_returns_none_on_tty_eof(monkeypatch):
|
|
|
583
583
|
assert cli_module._ask_to_truncate(5000, 100) is None
|
|
584
584
|
|
|
585
585
|
|
|
586
|
+
def test_settings_prints_resolved_config_values(monkeypatch, tmp_path):
|
|
587
|
+
_isolate_overflow_env(monkeypatch, tmp_path)
|
|
588
|
+
for var in ("VOCALIZE_VOICE", "VOCALIZE_MODEL", "VOCALIZE_SPEED"):
|
|
589
|
+
monkeypatch.delenv(var, raising=False)
|
|
590
|
+
cfg = tmp_path / "vocalize" / "config.toml"
|
|
591
|
+
cfg.parent.mkdir(parents=True, exist_ok=True)
|
|
592
|
+
cfg.write_text('max_chars = 1000\noverflow = "ask"\n', encoding="utf-8")
|
|
593
|
+
|
|
594
|
+
result = CliRunner().invoke(main, ["settings"])
|
|
595
|
+
|
|
596
|
+
assert result.exit_code == 0, result.output
|
|
597
|
+
assert "max_chars=1000" in result.output
|
|
598
|
+
assert "overflow=ask" in result.output
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def test_settings_prints_defaults_when_nothing_is_configured(monkeypatch, tmp_path):
|
|
602
|
+
_isolate_overflow_env(monkeypatch, tmp_path)
|
|
603
|
+
for var in ("VOCALIZE_VOICE", "VOCALIZE_MODEL", "VOCALIZE_SPEED"):
|
|
604
|
+
monkeypatch.delenv(var, raising=False)
|
|
605
|
+
|
|
606
|
+
result = CliRunner().invoke(main, ["settings"])
|
|
607
|
+
|
|
608
|
+
assert result.exit_code == 0, result.output
|
|
609
|
+
assert "max_chars=unset" in result.output
|
|
610
|
+
assert "overflow=truncate" in result.output
|
|
611
|
+
assert "speed=unset" in result.output
|
|
612
|
+
|
|
613
|
+
|
|
586
614
|
def test_stop_command_reports_a_stopped_player(monkeypatch):
|
|
587
615
|
monkeypatch.setattr(cli_module, "stop_playback", lambda: True)
|
|
588
616
|
result = CliRunner().invoke(main, ["stop"])
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
vocalize speak-file report.md --play
|
|
5
5
|
cat notes.md | vocalize speak-file - --play
|
|
6
6
|
vocalize stop
|
|
7
|
+
vocalize settings
|
|
7
8
|
vocalize voices
|
|
8
9
|
vocalize usage
|
|
9
10
|
vocalize config
|
|
@@ -181,6 +182,24 @@ def speak_file(path, api_key, voice_id, model_id, speed, output_path, play, raw,
|
|
|
181
182
|
chunk_chars=chunk_chars, overflow=overflow, default_max_chars=default_max_chars)
|
|
182
183
|
|
|
183
184
|
|
|
185
|
+
@main.command()
|
|
186
|
+
def settings() -> None:
|
|
187
|
+
"""Print the resolved settings, env and config precedence applied.
|
|
188
|
+
|
|
189
|
+
One key=value per line, made for wrapper scripts (the /speak slash
|
|
190
|
+
command reads overflow and max_chars from here) — no API call, no key
|
|
191
|
+
needed.
|
|
192
|
+
"""
|
|
193
|
+
file_config = load_config_file()
|
|
194
|
+
resolved = resolve_settings(file_config=file_config)
|
|
195
|
+
mode, cap = resolve_overflow(file_config=file_config)
|
|
196
|
+
click.echo(f"voice={resolved.voice_id}")
|
|
197
|
+
click.echo(f"model={resolved.model_id}")
|
|
198
|
+
click.echo(f"speed={resolved.speed if resolved.speed is not None else 'unset'}")
|
|
199
|
+
click.echo(f"max_chars={cap if cap is not None else 'unset'}")
|
|
200
|
+
click.echo(f"overflow={mode}")
|
|
201
|
+
|
|
202
|
+
|
|
184
203
|
@main.command()
|
|
185
204
|
def stop() -> None:
|
|
186
205
|
"""Stop any audio vocalize is currently playing."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|