vocalize-cli 0.10.2__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.
Files changed (153) hide show
  1. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/CHANGELOG.md +96 -0
  2. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/PKG-INFO +39 -1
  3. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/README.md +38 -0
  4. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/installation.md +13 -0
  5. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/decisions.md +471 -1
  6. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/design.md +127 -1
  7. vocalize_cli-0.11.0/docs/plans/2026-09-next-features/review-0.11.0.md +134 -0
  8. vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-10-release-0-11-0/report.md +153 -0
  9. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-10-release-0-11-0/validate-exit.sh +31 -2
  10. vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-7-portal-read/report.md +297 -0
  11. vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-7-portal-read/review-findings.md +130 -0
  12. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-7-portal-read/validate-exit.sh +8 -1
  13. vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-8-portal-write/report.md +346 -0
  14. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-8-portal-write/validate-exit.sh +29 -2
  15. vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-9-portal-page/report.md +231 -0
  16. vocalize_cli-0.11.0/docs/plans/2026-09-next-features/run-9-portal-page/review-findings.md +0 -0
  17. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-9-portal-page/validate-exit.sh +25 -1
  18. vocalize_cli-0.11.0/docs/research/2026-09-04-app-roadmap-analysis.md +290 -0
  19. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/roadmap.md +3 -0
  20. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/conftest.py +33 -0
  21. vocalize_cli-0.11.0/tests/portal_page_harness.js +713 -0
  22. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_auth.py +10 -0
  23. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_cli.py +222 -0
  24. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_config.py +21 -0
  25. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_dictate.py +17 -0
  26. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_elevenlabs_provider.py +1 -1
  27. vocalize_cli-0.11.0/tests/test_portal.py +3835 -0
  28. vocalize_cli-0.11.0/tests/test_portal_assets.py +393 -0
  29. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_readiness.py +44 -15
  30. vocalize_cli-0.11.0/tests/test_tts.py +411 -0
  31. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_wizard.py +375 -1
  32. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/__init__.py +1 -1
  33. vocalize_cli-0.11.0/vocalize/assets/portal.html +295 -0
  34. vocalize_cli-0.11.0/vocalize/assets/portal.js +1486 -0
  35. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/auth.py +8 -1
  36. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/cli.py +58 -9
  37. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/config.py +22 -8
  38. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/exceptions.py +15 -0
  39. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/local/install.py +10 -0
  40. vocalize_cli-0.11.0/vocalize/portal.py +1693 -0
  41. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/readiness.py +31 -6
  42. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/tts.py +44 -5
  43. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/wizard.py +183 -17
  44. vocalize_cli-0.10.2/tests/test_tts.py +0 -201
  45. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/.env.example +0 -0
  46. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/.github/workflows/ci.yml +0 -0
  47. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/.gitignore +0 -0
  48. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/LICENSE +0 -0
  49. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/dictation.md +0 -0
  50. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/next-features-analysis.md +0 -0
  51. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/choreography.md +0 -0
  52. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/plan.md +0 -0
  53. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/review-0.10.0.md +0 -0
  54. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-1-status/project-plan.md +0 -0
  55. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-1-status/report.md +0 -0
  56. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-1-status/validate-exit.sh +0 -0
  57. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-10-release-0-11-0/project-plan.md +0 -0
  58. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-2-stt-runtime/project-plan.md +0 -0
  59. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-2-stt-runtime/report.md +0 -0
  60. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-2-stt-runtime/validate-exit.sh +0 -0
  61. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-3-recorder/project-plan.md +0 -0
  62. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-3-recorder/report.md +0 -0
  63. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-3-recorder/validate-exit.sh +0 -0
  64. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-4-dictation/project-plan.md +0 -0
  65. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-4-dictation/report.md +0 -0
  66. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-4-dictation/validate-exit.sh +0 -0
  67. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-5-resume/project-plan.md +0 -0
  68. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-5-resume/report.md +0 -0
  69. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-5-resume/validate-exit.sh +0 -0
  70. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-6-release-0-10-0/project-plan.md +0 -0
  71. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-6-release-0-10-0/report.md +0 -0
  72. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-6-release-0-10-0/validate-exit.sh +0 -0
  73. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-7-portal-read/project-plan.md +0 -0
  74. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-8-portal-write/project-plan.md +0 -0
  75. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/run-9-portal-page/project-plan.md +0 -0
  76. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/spike-2026-09-01.md +0 -0
  77. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/split-assessment.md +0 -0
  78. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/plans/2026-09-next-features/verification.md +0 -0
  79. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/provider-credentials.md +0 -0
  80. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/research/2026-09-01-config-portal-design.md +0 -0
  81. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/research/2026-09-01-dictation-design.md +0 -0
  82. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/docs/research/2026-09-01-voicebox-findings.md +0 -0
  83. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/claude_stop_hook.py +0 -0
  84. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/install_hook.py +0 -0
  85. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/install_quick_action.py +0 -0
  86. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/quick_actions/Dictate with Vocalize.workflow/Contents/Info.plist +0 -0
  87. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/quick_actions/Dictate with Vocalize.workflow/Contents/Resources/document.wflow +0 -0
  88. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/quick_actions/Speak Latest Plan.workflow/Contents/Info.plist +0 -0
  89. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/quick_actions/Speak Latest Plan.workflow/Contents/Resources/document.wflow +0 -0
  90. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/quick_actions/Speak with Vocalize.workflow/Contents/Info.plist +0 -0
  91. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/quick_actions/Speak with Vocalize.workflow/Contents/Resources/document.wflow +0 -0
  92. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/quick_actions/Stop Vocalize.workflow/Contents/Info.plist +0 -0
  93. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/quick_actions/Stop Vocalize.workflow/Contents/Resources/document.wflow +0 -0
  94. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/speak_options.py +0 -0
  95. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/hooks/speak_url_gate.py +0 -0
  96. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/pyproject.toml +0 -0
  97. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_audio.py +0 -0
  98. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_cache.py +0 -0
  99. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_chain.py +0 -0
  100. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_claude_stop_hook.py +0 -0
  101. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_clipboard.py +0 -0
  102. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_cue_assets.py +0 -0
  103. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_exceptions.py +0 -0
  104. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_google_provider.py +0 -0
  105. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_http.py +0 -0
  106. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_install_hook.py +0 -0
  107. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_install_quick_action.py +0 -0
  108. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_kokoro_manifest.py +0 -0
  109. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_kokoro_provider.py +0 -0
  110. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_kokoro_worker.py +0 -0
  111. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_ledger.py +0 -0
  112. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_listen_check.py +0 -0
  113. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_local_install.py +0 -0
  114. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_openai_provider.py +0 -0
  115. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_polly_provider.py +0 -0
  116. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_preprocess.py +0 -0
  117. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_providers_registry.py +0 -0
  118. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_recorder_build.py +0 -0
  119. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_say_provider.py +0 -0
  120. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_speak_options.py +0 -0
  121. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_speak_url_gate.py +0 -0
  122. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_uv_path.py +0 -0
  123. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_whisper_manifest.py +0 -0
  124. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/tests/test_whisper_worker.py +0 -0
  125. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/__main__.py +0 -0
  126. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/assets/cues/README.md +0 -0
  127. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/assets/cues/ready.wav +0 -0
  128. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/assets/cues/start.wav +0 -0
  129. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/assets/cues/stopped.wav +0 -0
  130. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/audio.py +0 -0
  131. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/cache.py +0 -0
  132. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/chain.py +0 -0
  133. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/clipboard.py +0 -0
  134. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/dictate.py +0 -0
  135. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/interrupted.py +0 -0
  136. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/ledger.py +0 -0
  137. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/local/__init__.py +0 -0
  138. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/local/kokoro_manifest.py +0 -0
  139. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/local/kokoro_worker.py +0 -0
  140. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/local/whisper_manifest.py +0 -0
  141. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/local/whisper_worker.py +0 -0
  142. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/preprocess.py +0 -0
  143. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/providers/__init__.py +0 -0
  144. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/providers/_http.py +0 -0
  145. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/providers/elevenlabs.py +0 -0
  146. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/providers/google.py +0 -0
  147. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/providers/kokoro.py +0 -0
  148. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/providers/openai.py +0 -0
  149. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/providers/polly.py +0 -0
  150. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/providers/say.py +0 -0
  151. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/recorder/Info.plist.in +0 -0
  152. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/recorder/Recorder.entitlements +0 -0
  153. {vocalize_cli-0.10.2 → vocalize_cli-0.11.0}/vocalize/recorder/VocalizeRecorder.swift +0 -0
@@ -3,6 +3,102 @@
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
+
6
102
  ## 0.10.2 - 2026-09-02
7
103
 
8
104
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vocalize-cli
3
- Version: 0.10.2
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
@@ -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
@@ -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