vocalize-cli 0.10.1__tar.gz → 0.11.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.10.1 → vocalize_cli-0.11.0}/.gitignore +1 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/CHANGELOG.md +122 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/PKG-INFO +43 -1
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/README.md +42 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/dictation.md +14 -1
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/installation.md +13 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/decisions.md +471 -1
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/design.md +127 -1
- vocalize_cli-0.11.0/docs/plans/2026-09-next-features/review-0.11.0.md +134 -0
- vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-10-release-0-11-0/report.md +153 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-10-release-0-11-0/validate-exit.sh +31 -2
- vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-7-portal-read/report.md +297 -0
- vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-7-portal-read/review-findings.md +130 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-7-portal-read/validate-exit.sh +8 -1
- vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-8-portal-write/report.md +346 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-8-portal-write/validate-exit.sh +29 -2
- vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-9-portal-page/report.md +231 -0
- vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-9-portal-page/review-findings.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-9-portal-page/validate-exit.sh +25 -1
- vocalize_cli-0.11.0/docs/research/2026-09-04-app-roadmap-analysis.md +290 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/roadmap.md +5 -1
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/conftest.py +33 -0
- vocalize_cli-0.11.0/tests/portal_page_harness.js +713 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_auth.py +10 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_cli.py +227 -2
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_config.py +45 -0
- vocalize_cli-0.11.0/tests/test_cue_assets.py +28 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_dictate.py +102 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_elevenlabs_provider.py +1 -1
- vocalize_cli-0.11.0/tests/test_portal.py +3835 -0
- vocalize_cli-0.11.0/tests/test_portal_assets.py +393 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_readiness.py +44 -15
- vocalize_cli-0.11.0/tests/test_tts.py +411 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_wizard.py +375 -1
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/__init__.py +1 -1
- vocalize_cli-0.11.0/vocalize/assets/cues/README.md +11 -0
- vocalize_cli-0.11.0/vocalize/assets/cues/ready.wav +0 -0
- vocalize_cli-0.11.0/vocalize/assets/cues/start.wav +0 -0
- vocalize_cli-0.11.0/vocalize/assets/cues/stopped.wav +0 -0
- vocalize_cli-0.11.0/vocalize/assets/portal.html +295 -0
- vocalize_cli-0.11.0/vocalize/assets/portal.js +1486 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/auth.py +8 -1
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/cli.py +59 -9
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/config.py +34 -8
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/dictate.py +39 -4
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/exceptions.py +15 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/local/install.py +10 -0
- vocalize_cli-0.11.0/vocalize/portal.py +1693 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/readiness.py +31 -6
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/tts.py +44 -5
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/wizard.py +183 -17
- vocalize_cli-0.10.1/tests/test_tts.py +0 -201
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/.env.example +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/.github/workflows/ci.yml +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/LICENSE +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/next-features-analysis.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/choreography.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/plan.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/review-0.10.0.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-1-status/project-plan.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-1-status/report.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-1-status/validate-exit.sh +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-10-release-0-11-0/project-plan.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-2-stt-runtime/project-plan.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-2-stt-runtime/report.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-2-stt-runtime/validate-exit.sh +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-3-recorder/project-plan.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-3-recorder/report.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-3-recorder/validate-exit.sh +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-4-dictation/project-plan.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-4-dictation/report.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-4-dictation/validate-exit.sh +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-5-resume/project-plan.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-5-resume/report.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-5-resume/validate-exit.sh +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-6-release-0-10-0/project-plan.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-6-release-0-10-0/report.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-6-release-0-10-0/validate-exit.sh +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-7-portal-read/project-plan.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-8-portal-write/project-plan.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-9-portal-page/project-plan.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/spike-2026-09-01.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/split-assessment.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/verification.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/provider-credentials.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/research/2026-09-01-config-portal-design.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/research/2026-09-01-dictation-design.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/docs/research/2026-09-01-voicebox-findings.md +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/claude_stop_hook.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/install_hook.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/install_quick_action.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/quick_actions/Dictate with Vocalize.workflow/Contents/Info.plist +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/quick_actions/Dictate with Vocalize.workflow/Contents/Resources/document.wflow +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/quick_actions/Speak Latest Plan.workflow/Contents/Info.plist +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/quick_actions/Speak Latest Plan.workflow/Contents/Resources/document.wflow +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/quick_actions/Speak with Vocalize.workflow/Contents/Info.plist +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/quick_actions/Speak with Vocalize.workflow/Contents/Resources/document.wflow +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/quick_actions/Stop Vocalize.workflow/Contents/Info.plist +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/quick_actions/Stop Vocalize.workflow/Contents/Resources/document.wflow +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/speak_options.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/hooks/speak_url_gate.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/pyproject.toml +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_audio.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_cache.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_chain.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_claude_stop_hook.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_clipboard.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_exceptions.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_google_provider.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_http.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_install_hook.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_install_quick_action.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_kokoro_manifest.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_kokoro_provider.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_kokoro_worker.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_ledger.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_listen_check.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_local_install.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_openai_provider.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_polly_provider.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_preprocess.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_providers_registry.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_recorder_build.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_say_provider.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_speak_options.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_speak_url_gate.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_uv_path.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_whisper_manifest.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/tests/test_whisper_worker.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/__main__.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/audio.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/cache.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/chain.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/clipboard.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/interrupted.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/ledger.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/local/__init__.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/local/kokoro_manifest.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/local/kokoro_worker.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/local/whisper_manifest.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/local/whisper_worker.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/preprocess.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/providers/__init__.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/providers/_http.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/providers/elevenlabs.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/providers/google.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/providers/kokoro.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/providers/openai.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/providers/polly.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/providers/say.py +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/recorder/Info.plist.in +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/recorder/Recorder.entitlements +0 -0
- {vocalize_cli-0.10.1 → vocalize_cli-0.11.0}/vocalize/recorder/VocalizeRecorder.swift +0 -0
|
@@ -3,6 +3,128 @@
|
|
|
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.11.0 - 2026-09-05
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **A web portal page** (`vocalize portal`) for the whole config surface: reorder
|
|
11
|
+
the provider chain; set each provider's voice, speed and monthly budget with a
|
|
12
|
+
live preview; store and check API keys (masked, `autocomplete="off"`); watch
|
|
13
|
+
usage; and drive the local Kokoro and whisper installs with progress — no more
|
|
14
|
+
hand-editing `config.toml`. Five tabs (Chain, Providers, Keys, Usage, Local)
|
|
15
|
+
plus a persistent readiness sidebar reading `/api/state`, with buttons on the
|
|
16
|
+
rows that map to a vocalize command the portal already runs. Every save goes
|
|
17
|
+
through the same compare-and-swap the CLI uses, so a change made elsewhere
|
|
18
|
+
while the page was open is refused rather than silently overwritten. Ships as
|
|
19
|
+
static HTML and JS with no framework, no inline script and no external
|
|
20
|
+
resource — the whole surface has to work with the network cable pulled — over
|
|
21
|
+
a loopback-only, one-time-code session (DEC-004).
|
|
22
|
+
- **A voice picker on the Providers tab.** `GET /api/voices/<name>` returns the
|
|
23
|
+
provider's own `list_voices()`, paginated and cached for the life of the
|
|
24
|
+
portal process, so the field is a real dropdown of the account's actual
|
|
25
|
+
voices with the current value pre-selected, and `"Type a voice id…"` for an
|
|
26
|
+
id the list doesn't carry — rather than a hand-typed guess.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **`vocalize voices` and `vocalize usage` printed the ElevenLabs API key to
|
|
31
|
+
stderr when the API quoted it back.** The SDK's `ApiError` renders the whole
|
|
32
|
+
response body and header dict, and both commands printed that text verbatim —
|
|
33
|
+
so a key the API named in an error reached terminal scrollback, any session
|
|
34
|
+
log, and whatever the user pasted into a bug report. `auth.scrub` already
|
|
35
|
+
existed for exactly this, but three callers never reached it. The scrub now
|
|
36
|
+
happens inside `tts` itself, below every caller, against the key the client
|
|
37
|
+
was built with. Found by the 0.11.0 release review (DEC-021).
|
|
38
|
+
- **The portal's `say` voice list failed on the first ask.** The route gave every
|
|
39
|
+
provider the two-second budget meant for network probes, and enumerating the
|
|
40
|
+
system voices takes longer than that on a current Mac — so the one provider
|
|
41
|
+
needing no key, no account and no network was the one that said "couldn't
|
|
42
|
+
fetch the list". Offline lists get their own budget now.
|
|
43
|
+
- **A key of four characters or fewer was shown in full** by `vocalize auth
|
|
44
|
+
status` and on the portal's Keys tab — a truncated paste, or another tool's
|
|
45
|
+
short secret in the same environment variable, reproduced whole in output the
|
|
46
|
+
design treats as safe to screenshot. Anything under eight characters now masks
|
|
47
|
+
to `…` alone.
|
|
48
|
+
- **The portal's Keys tab pointed at Keychain Access** to remove a stored key.
|
|
49
|
+
It now names `vocalize auth logout --provider <name>`, which reads the entry
|
|
50
|
+
back and refuses to claim a removal it cannot verify.
|
|
51
|
+
- **The ElevenLabs SDK client followed a redirect and re-sent the API key
|
|
52
|
+
wherever it pointed, cross-origin, over plain http.** The SDK's httpx client
|
|
53
|
+
follows a 3xx by default; every ElevenLabs endpoint is a fixed URL, so a
|
|
54
|
+
redirect was never legitimate and the key had no business leaving the
|
|
55
|
+
original origin. Present since 0.9.0, when the ElevenLabs provider first
|
|
56
|
+
shipped. Closed by building the client with `follow_redirects=False` — the
|
|
57
|
+
same rule the urllib-based providers already held via `_http._NoRedirects` —
|
|
58
|
+
and proven on two real loopback HTTP servers: the second origin never sees
|
|
59
|
+
the request.
|
|
60
|
+
- **Nothing could rewrite a config file containing an `[stt]` table** —
|
|
61
|
+
which is every config a dictation user has had since 0.10.0 added the
|
|
62
|
+
table. The serialiser wrote flat keys and `[providers.*]` and had no
|
|
63
|
+
case for any other table, so `stt` reached the scalar renderer as a
|
|
64
|
+
`dict` and raised: "The config file has a table under 'stt'. The wizard
|
|
65
|
+
only manages flat keys, so it will not rewrite this file — edit that
|
|
66
|
+
file by hand." That refusal came out of `vocalize wizard` before it
|
|
67
|
+
asked its first question, and out of `vocalize chain google say` instead
|
|
68
|
+
of a write. The only way out was to delete the `[stt]` table, run the
|
|
69
|
+
command, and put it back. `[stt]` is now rendered as its own table, so
|
|
70
|
+
both commands rewrite the file and leave the dictation settings alone.
|
|
71
|
+
The rewritten file puts `[stt]` ahead of `[providers.*]` whatever order
|
|
72
|
+
they were in before — identical TOML, a one-off cosmetic diff.
|
|
73
|
+
- **Two writers saving at the same moment could lose one of the two
|
|
74
|
+
changes.** The config write compared the file against the fingerprint it
|
|
75
|
+
had read and then renamed a temp file over it, with the two steps not
|
|
76
|
+
held together, so two saves that overlapped could both see an unchanged
|
|
77
|
+
file, both write, and both report success while only the second survived.
|
|
78
|
+
The compare and the rename now happen under one lock. Two `vocalize`
|
|
79
|
+
*processes* saving in the same instant can still race — the fingerprint
|
|
80
|
+
check narrows that to a window of microseconds, and closing it entirely
|
|
81
|
+
needs a lock file, which is deliberately not what this is.
|
|
82
|
+
- **A config save rendered through a fixed `config.toml.tmp`**, so two
|
|
83
|
+
writers at once could render through the same temp file and leave one of
|
|
84
|
+
them writing a file the other had already renamed away — reported as
|
|
85
|
+
"Could not write config file", or worse, reported as success for bytes
|
|
86
|
+
that were not in the file. Each writer now gets its own temp file, made
|
|
87
|
+
with `mkstemp` in the same directory, still `0600`, and still renamed
|
|
88
|
+
into place atomically. The predictable name a local process could
|
|
89
|
+
pre-plant as a symlink is gone with it.
|
|
90
|
+
|
|
91
|
+
### Known limitation
|
|
92
|
+
|
|
93
|
+
- **Any other process on the Mac can close the portal with five wrong or
|
|
94
|
+
missing-origin requests to `/api/session`**, no code and no token needed —
|
|
95
|
+
the same guard that stops a cross-origin web page from doing it has no way
|
|
96
|
+
to tell that traffic apart from a local script's. This is availability
|
|
97
|
+
only: nothing is read or changed, the portal just exits. Rerun
|
|
98
|
+
`vocalize portal` and carry on. Accepted rather than fixed, since anything
|
|
99
|
+
able to send that traffic locally could kill the process outright anyway
|
|
100
|
+
(DEC-018).
|
|
101
|
+
|
|
102
|
+
## 0.10.2 - 2026-09-02
|
|
103
|
+
|
|
104
|
+
### Added
|
|
105
|
+
|
|
106
|
+
- **`[stt] cues`** picks what a dictation's feedback sounds — say instead
|
|
107
|
+
of, or alongside, the Tink/Pop/Glass system sounds. `"sounds"` (default)
|
|
108
|
+
is unchanged; `"words"` speaks "Start.", "Stopped.", "Ready." in their
|
|
109
|
+
place; `"both"` speaks the word and then plays the sound. The word files
|
|
110
|
+
ship in `vocalize/assets/cues/`, generated with the local Kokoro voice.
|
|
111
|
+
A spoken "Start." plays *before* the recorder launches — played once the
|
|
112
|
+
microphone was open it would be recorded and transcribed along with the
|
|
113
|
+
dictation. In `"both"` mode the Tink still plays *after* the microphone
|
|
114
|
+
opens, so the two cues keep distinct meanings: the word is "get ready",
|
|
115
|
+
the sound is "talk now". The plain Tink is unaffected.
|
|
116
|
+
|
|
117
|
+
### Known issue
|
|
118
|
+
|
|
119
|
+
- In `"words"` mode there is no cue for the moment the microphone actually
|
|
120
|
+
opens, which is a second or so after "Start." finishes (LaunchServices
|
|
121
|
+
start-up plus the input device switching on). People start talking too
|
|
122
|
+
soon and lose their first word. The fix — open and warm the microphone
|
|
123
|
+
first, play the cue, and only then capture — is tracked in
|
|
124
|
+
[#2](https://github.com/matthager12-collab/vocalize/issues/2). Until
|
|
125
|
+
then: in `"words"` mode, wait a beat after "Start."; in `"both"` mode,
|
|
126
|
+
talk after the Tink.
|
|
127
|
+
|
|
6
128
|
## 0.10.1 - 2026-09-02
|
|
7
129
|
|
|
8
130
|
Three fixes found in the first owner-present run of 0.10.0's dictation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vocalize-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.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
|
|
@@ -216,6 +216,9 @@ Hotkeys: `↑`/`↓` or `k`/`j` move, `Enter` selects, `p` previews the
|
|
|
216
216
|
highlighted voice, `m` types a value by hand, `q` or `Esc` cancels without
|
|
217
217
|
writing anything.
|
|
218
218
|
|
|
219
|
+
There's also a settings page in the browser — see
|
|
220
|
+
[Settings portal](#settings-portal) below.
|
|
221
|
+
|
|
219
222
|
| Setting | Flag | Env var | Config file key | Default |
|
|
220
223
|
|---|---|---|---|---|
|
|
221
224
|
| API key | `--api-key` | `ELEVENLABS_API_KEY` | not read from the config file | stored via `vocalize auth` |
|
|
@@ -295,6 +298,41 @@ configures a TTS provider — see
|
|
|
295
298
|
[Dictation](#dictation-speech-to-text) below for every key and its
|
|
296
299
|
allowlist.
|
|
297
300
|
|
|
301
|
+
## Settings portal
|
|
302
|
+
|
|
303
|
+
A page in your own browser for the whole config surface above, so you don't
|
|
304
|
+
have to hand-edit `config.toml` or click through `vocalize config` one
|
|
305
|
+
setting at a time.
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
vocalize portal # opens your browser at a one-time link
|
|
309
|
+
vocalize portal --no-browser # prints the link instead (headless, SSH)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Five tabs:
|
|
313
|
+
|
|
314
|
+
- **Chain** — reorder providers, or add and remove one.
|
|
315
|
+
- **Providers** — each provider's voice, model, speed, and monthly budget,
|
|
316
|
+
with a live preview of the voice you're looking at.
|
|
317
|
+
- **Keys** — store or check an API key; the field is masked and never
|
|
318
|
+
offers to autocomplete.
|
|
319
|
+
- **Usage** — this month's spend and quota per provider.
|
|
320
|
+
- **Local** — install or update the on-device Kokoro and whisper models,
|
|
321
|
+
with progress.
|
|
322
|
+
|
|
323
|
+
The opening link works once and for 60 seconds; after that, run the command
|
|
324
|
+
again for a new one. The server only ever listens on `127.0.0.1`, and it
|
|
325
|
+
closes itself on Ctrl-C or after fifteen minutes with nothing to do. Nothing
|
|
326
|
+
you change on the page reaches disk until you press Save on that tab — and
|
|
327
|
+
a save made against a config file that's changed on disk since the page
|
|
328
|
+
loaded is refused rather than silently overwritten, the same protection
|
|
329
|
+
`vocalize chain` and `vocalize config` already have.
|
|
330
|
+
|
|
331
|
+
It assumes a single-user machine: everything that reads or changes your
|
|
332
|
+
settings is behind that one-time token, but any other process on the Mac
|
|
333
|
+
can still reach the port and close the portal under you. Nothing leaks if
|
|
334
|
+
that happens — it's a shutdown, not a read — just run the command again.
|
|
335
|
+
|
|
298
336
|
## Providers and fallback
|
|
299
337
|
|
|
300
338
|
vocalize tries providers in order — a **chain** — until one speaks. The
|
|
@@ -485,6 +523,8 @@ terminal, if you'd rather trigger it that way.
|
|
|
485
523
|
- **A third press while transcribing is refused**: a Pop, and "Still
|
|
486
524
|
transcribing the last dictation." Wait for the clipboard notification, or
|
|
487
525
|
`--cancel`, before dictating again.
|
|
526
|
+
- Can't tell Tink from Pop from Glass yet? Set `[stt] cues = "words"` and
|
|
527
|
+
vocalize says "Start.", "Stopped.", "Ready." instead.
|
|
488
528
|
|
|
489
529
|
### `vocalize listen`
|
|
490
530
|
|
|
@@ -519,6 +559,7 @@ input_device = "" # "" = system default; else an exact name from --list-dev
|
|
|
519
559
|
cleanup = false # send the transcript (never audio) to Claude first
|
|
520
560
|
max_seconds = 120 # 1-600; the recorder self-stops here, dictate backstops it
|
|
521
561
|
sounds = true # the Tink/Pop/Glass feedback sounds
|
|
562
|
+
cues = "sounds" # "sounds" | "words" | "both" — speak "Start."/"Stopped."/"Ready." instead
|
|
522
563
|
```
|
|
523
564
|
|
|
524
565
|
| Key | Allowed values | Default |
|
|
@@ -530,6 +571,7 @@ sounds = true # the Tink/Pop/Glass feedback sounds
|
|
|
530
571
|
| `paste` | reserved — not implemented in 0.10.0 | `false` |
|
|
531
572
|
| `max_seconds` | integer, 1–600 | `120` |
|
|
532
573
|
| `sounds` | `true` / `false` | `true` |
|
|
574
|
+
| `cues` | `sounds`, `words`, `both` | `sounds` |
|
|
533
575
|
|
|
534
576
|
An unknown key warns on stderr; a bad value is a `ConfigError` naming it —
|
|
535
577
|
every one of these becomes a subprocess argument eventually, so nothing
|
|
@@ -180,6 +180,9 @@ Hotkeys: `↑`/`↓` or `k`/`j` move, `Enter` selects, `p` previews the
|
|
|
180
180
|
highlighted voice, `m` types a value by hand, `q` or `Esc` cancels without
|
|
181
181
|
writing anything.
|
|
182
182
|
|
|
183
|
+
There's also a settings page in the browser — see
|
|
184
|
+
[Settings portal](#settings-portal) below.
|
|
185
|
+
|
|
183
186
|
| Setting | Flag | Env var | Config file key | Default |
|
|
184
187
|
|---|---|---|---|---|
|
|
185
188
|
| API key | `--api-key` | `ELEVENLABS_API_KEY` | not read from the config file | stored via `vocalize auth` |
|
|
@@ -259,6 +262,41 @@ configures a TTS provider — see
|
|
|
259
262
|
[Dictation](#dictation-speech-to-text) below for every key and its
|
|
260
263
|
allowlist.
|
|
261
264
|
|
|
265
|
+
## Settings portal
|
|
266
|
+
|
|
267
|
+
A page in your own browser for the whole config surface above, so you don't
|
|
268
|
+
have to hand-edit `config.toml` or click through `vocalize config` one
|
|
269
|
+
setting at a time.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
vocalize portal # opens your browser at a one-time link
|
|
273
|
+
vocalize portal --no-browser # prints the link instead (headless, SSH)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Five tabs:
|
|
277
|
+
|
|
278
|
+
- **Chain** — reorder providers, or add and remove one.
|
|
279
|
+
- **Providers** — each provider's voice, model, speed, and monthly budget,
|
|
280
|
+
with a live preview of the voice you're looking at.
|
|
281
|
+
- **Keys** — store or check an API key; the field is masked and never
|
|
282
|
+
offers to autocomplete.
|
|
283
|
+
- **Usage** — this month's spend and quota per provider.
|
|
284
|
+
- **Local** — install or update the on-device Kokoro and whisper models,
|
|
285
|
+
with progress.
|
|
286
|
+
|
|
287
|
+
The opening link works once and for 60 seconds; after that, run the command
|
|
288
|
+
again for a new one. The server only ever listens on `127.0.0.1`, and it
|
|
289
|
+
closes itself on Ctrl-C or after fifteen minutes with nothing to do. Nothing
|
|
290
|
+
you change on the page reaches disk until you press Save on that tab — and
|
|
291
|
+
a save made against a config file that's changed on disk since the page
|
|
292
|
+
loaded is refused rather than silently overwritten, the same protection
|
|
293
|
+
`vocalize chain` and `vocalize config` already have.
|
|
294
|
+
|
|
295
|
+
It assumes a single-user machine: everything that reads or changes your
|
|
296
|
+
settings is behind that one-time token, but any other process on the Mac
|
|
297
|
+
can still reach the port and close the portal under you. Nothing leaks if
|
|
298
|
+
that happens — it's a shutdown, not a read — just run the command again.
|
|
299
|
+
|
|
262
300
|
## Providers and fallback
|
|
263
301
|
|
|
264
302
|
vocalize tries providers in order — a **chain** — until one speaks. The
|
|
@@ -449,6 +487,8 @@ terminal, if you'd rather trigger it that way.
|
|
|
449
487
|
- **A third press while transcribing is refused**: a Pop, and "Still
|
|
450
488
|
transcribing the last dictation." Wait for the clipboard notification, or
|
|
451
489
|
`--cancel`, before dictating again.
|
|
490
|
+
- Can't tell Tink from Pop from Glass yet? Set `[stt] cues = "words"` and
|
|
491
|
+
vocalize says "Start.", "Stopped.", "Ready." instead.
|
|
452
492
|
|
|
453
493
|
### `vocalize listen`
|
|
454
494
|
|
|
@@ -483,6 +523,7 @@ input_device = "" # "" = system default; else an exact name from --list-dev
|
|
|
483
523
|
cleanup = false # send the transcript (never audio) to Claude first
|
|
484
524
|
max_seconds = 120 # 1-600; the recorder self-stops here, dictate backstops it
|
|
485
525
|
sounds = true # the Tink/Pop/Glass feedback sounds
|
|
526
|
+
cues = "sounds" # "sounds" | "words" | "both" — speak "Start."/"Stopped."/"Ready." instead
|
|
486
527
|
```
|
|
487
528
|
|
|
488
529
|
| Key | Allowed values | Default |
|
|
@@ -494,6 +535,7 @@ sounds = true # the Tink/Pop/Glass feedback sounds
|
|
|
494
535
|
| `paste` | reserved — not implemented in 0.10.0 | `false` |
|
|
495
536
|
| `max_seconds` | integer, 1–600 | `120` |
|
|
496
537
|
| `sounds` | `true` / `false` | `true` |
|
|
538
|
+
| `cues` | `sounds`, `words`, `both` | `sounds` |
|
|
497
539
|
|
|
498
540
|
An unknown key warns on stderr; a bad value is a `ConfigError` naming it —
|
|
499
541
|
every one of these becomes a subprocess argument eventually, so nothing
|
|
@@ -147,6 +147,16 @@ never runs two transcriptions over the same recording, and it never
|
|
|
147
147
|
silently drops one either. Wait for the clipboard notification, or use
|
|
148
148
|
`--cancel`.
|
|
149
149
|
|
|
150
|
+
New to the sounds and can't tell Tink from Pop from Glass? Set
|
|
151
|
+
`[stt] cues = "words"` and vocalize says "Start.", "Stopped." and "Ready."
|
|
152
|
+
instead, or `"both"` to hear the word and then its sound — see the `[stt]`
|
|
153
|
+
table below. Timing matters: "Start." is spoken *before* the microphone
|
|
154
|
+
opens (so it is never in your recording), and the microphone is open
|
|
155
|
+
about a second later — the Tink marks that moment. So in `"both"` mode
|
|
156
|
+
talk after the Tink; in `"words"` mode give it a beat after "Start.".
|
|
157
|
+
Closing that gap is
|
|
158
|
+
[#2](https://github.com/matthager12-collab/vocalize/issues/2).
|
|
159
|
+
|
|
150
160
|
## `vocalize listen`
|
|
151
161
|
|
|
152
162
|
The terminal-facing primitive behind the hotkey:
|
|
@@ -216,6 +226,7 @@ cleanup = false
|
|
|
216
226
|
paste = false
|
|
217
227
|
max_seconds = 120
|
|
218
228
|
sounds = true
|
|
229
|
+
cues = "sounds" # "sounds" | "words" | "both"
|
|
219
230
|
```
|
|
220
231
|
|
|
221
232
|
| Key | Type / allowlist | Default | Notes |
|
|
@@ -226,7 +237,8 @@ sounds = true
|
|
|
226
237
|
| `cleanup` | `true` / `false` | `false` | see `--cleanup` above |
|
|
227
238
|
| `paste` | reserved | `false` | not implemented in 0.10.0 — setting it does nothing |
|
|
228
239
|
| `max_seconds` | integer, 1–600 | `120` | the recorder self-stops here; `dictate` backstops it a few seconds later in case the recorder doesn't |
|
|
229
|
-
| `sounds` | `true` / `false` | `true` | the Tink/Pop/Glass feedback; `false` silences all three |
|
|
240
|
+
| `sounds` | `true` / `false` | `true` | the Tink/Pop/Glass feedback; `false` silences all three (words included) |
|
|
241
|
+
| `cues` | `sounds`, `words`, `both` | `sounds` | `"words"` speaks "Start.", "Stopped.", "Ready." instead of the system sounds; `"both"` speaks the word and then plays the sound — for the start cue, the word before the microphone opens and the Tink once it has. Has no effect while `sounds = false`. |
|
|
230
242
|
|
|
231
243
|
Every value here eventually becomes a subprocess argument — the recorder's
|
|
232
244
|
`--device`, or the whisper worker's `--model`/`--language` — so each one is
|
|
@@ -243,6 +255,7 @@ stt.model=small.en
|
|
|
243
255
|
stt.language=en
|
|
244
256
|
stt.cleanup=false
|
|
245
257
|
stt.max_seconds=120
|
|
258
|
+
stt.cues=sounds
|
|
246
259
|
```
|
|
247
260
|
|
|
248
261
|
### The input-device gotcha
|
|
@@ -75,6 +75,19 @@ Smoke test (also proves stdin + preprocessing):
|
|
|
75
75
|
echo "vocalize is alive" | vocalize speak-file -
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
### Or: finish setup in a browser
|
|
79
|
+
|
|
80
|
+
Everything above — keys, the chain, the local models — can be done from one
|
|
81
|
+
page instead:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
vocalize portal
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
It opens a one-time link at `127.0.0.1` (valid for 60 seconds), and writes
|
|
88
|
+
nothing until you save. See [README § Settings portal](../README.md#settings-portal)
|
|
89
|
+
for what each tab does.
|
|
90
|
+
|
|
78
91
|
## Layer 2 — the `/speak` command in Claude Code
|
|
79
92
|
|
|
80
93
|
Until the repo ships this (tracked as an open gap), create it by hand at
|