textflowkit 0.1.8__tar.gz → 0.1.9__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 (177) hide show
  1. {textflowkit-0.1.8 → textflowkit-0.1.9}/.github/workflows/ci.yml +5 -1
  2. {textflowkit-0.1.8 → textflowkit-0.1.9}/CHANGELOG.md +68 -0
  3. {textflowkit-0.1.8 → textflowkit-0.1.9}/Dockerfile +6 -4
  4. {textflowkit-0.1.8 → textflowkit-0.1.9}/PKG-INFO +112 -17
  5. {textflowkit-0.1.8 → textflowkit-0.1.9}/README.md +108 -15
  6. {textflowkit-0.1.8 → textflowkit-0.1.9}/compose.yaml +4 -3
  7. {textflowkit-0.1.8 → textflowkit-0.1.9}/docs/adapters.md +21 -10
  8. {textflowkit-0.1.8 → textflowkit-0.1.9}/docs/architecture.md +24 -6
  9. textflowkit-0.1.9/docs/assets/architecture-overview.svg +189 -0
  10. textflowkit-0.1.9/docs/assets/whistle-resume.svg +164 -0
  11. {textflowkit-0.1.8 → textflowkit-0.1.9}/docs/index.html +19 -5
  12. {textflowkit-0.1.8 → textflowkit-0.1.9}/docs/install.md +81 -27
  13. {textflowkit-0.1.8 → textflowkit-0.1.9}/docs/release-checklist.md +12 -8
  14. {textflowkit-0.1.8 → textflowkit-0.1.9}/docs/roadmap.md +18 -1
  15. {textflowkit-0.1.8 → textflowkit-0.1.9}/docs/user-manual.md +109 -21
  16. {textflowkit-0.1.8 → textflowkit-0.1.9}/pyproject.toml +11 -5
  17. {textflowkit-0.1.8 → textflowkit-0.1.9}/scripts/smoke_live_youtube.py +28 -9
  18. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/__init__.py +1 -1
  19. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/adapters/http_server.py +7 -5
  20. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/adapters/mcp_server.py +29 -19
  21. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/cli.py +158 -39
  22. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/checkpoint.py +226 -23
  23. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/engine.py +201 -62
  24. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/pipeline.py +149 -14
  25. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/runner.py +3 -2
  26. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/submission.py +73 -6
  27. textflowkit-0.1.9/src/textflowkit/core/whistle.py +1198 -0
  28. textflowkit-0.1.9/src/textflowkit/core/whistle_assets.py +505 -0
  29. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_adapters.py +9 -5
  30. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_admission_ownership.py +2 -2
  31. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_atomic_resume.py +9 -9
  32. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_atomic_resume_aba.py +3 -3
  33. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_batch.py +5 -2
  34. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_batch_item_boundary.py +2 -2
  35. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_batch_resume_shared.py +3 -2
  36. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_checkpoint.py +77 -5
  37. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_checkpoint_single_copy.py +9 -3
  38. textflowkit-0.1.9/tests/test_cli.py +228 -0
  39. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_cli_owning_recovery.py +5 -5
  40. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_cli_stage_feedback.py +2 -2
  41. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_diarize.py +3 -3
  42. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_docx_publication_identity.py +2 -1
  43. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_done_output_integrity.py +6 -2
  44. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_empty_formats_resume.py +3 -2
  45. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_engine_adapters.py +14 -14
  46. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_engine_faster.py +20 -10
  47. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_partial_output_resume.py +3 -0
  48. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_partial_pdf_resume.py +1 -0
  49. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_pipeline_resume_setup.py +5 -1
  50. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_release_boundaries.py +10 -7
  51. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_stage_reuse_resume.py +1 -0
  52. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_submission.py +7 -2
  53. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_submission_validation.py +13 -7
  54. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_translate.py +3 -3
  55. textflowkit-0.1.9/tests/test_whistle_assets.py +490 -0
  56. textflowkit-0.1.9/tests/test_whistle_defaults.py +661 -0
  57. textflowkit-0.1.9/tests/test_whistle_engine.py +1027 -0
  58. textflowkit-0.1.9/tests/test_whistle_job_resume.py +620 -0
  59. textflowkit-0.1.8/tests/test_cli.py +0 -105
  60. {textflowkit-0.1.8 → textflowkit-0.1.9}/.dockerignore +0 -0
  61. {textflowkit-0.1.8 → textflowkit-0.1.9}/.env.example +0 -0
  62. {textflowkit-0.1.8 → textflowkit-0.1.9}/.gitattributes +0 -0
  63. {textflowkit-0.1.8 → textflowkit-0.1.9}/.github/dependabot.yml +0 -0
  64. {textflowkit-0.1.8 → textflowkit-0.1.9}/.github/workflows/publish-pypi.yml +0 -0
  65. {textflowkit-0.1.8 → textflowkit-0.1.9}/.gitignore +0 -0
  66. {textflowkit-0.1.8 → textflowkit-0.1.9}/.pre-commit-config.yaml +0 -0
  67. {textflowkit-0.1.8 → textflowkit-0.1.9}/CONTRIBUTING.md +0 -0
  68. {textflowkit-0.1.8 → textflowkit-0.1.9}/LEGAL.md +0 -0
  69. {textflowkit-0.1.8 → textflowkit-0.1.9}/LICENSE +0 -0
  70. {textflowkit-0.1.8 → textflowkit-0.1.9}/SECURITY.md +0 -0
  71. {textflowkit-0.1.8 → textflowkit-0.1.9}/docs/.nojekyll +0 -0
  72. {textflowkit-0.1.8 → textflowkit-0.1.9}/docs/404.html +0 -0
  73. {textflowkit-0.1.8 → textflowkit-0.1.9}/docs/site-deployment.md +0 -0
  74. {textflowkit-0.1.8 → textflowkit-0.1.9}/docs/sources.md +0 -0
  75. {textflowkit-0.1.8 → textflowkit-0.1.9}/scripts/fetch_published_fonts.py +0 -0
  76. {textflowkit-0.1.8 → textflowkit-0.1.9}/scripts/smoke_installed_wheel.py +0 -0
  77. {textflowkit-0.1.8 → textflowkit-0.1.9}/scripts/verify_readme_release.py +0 -0
  78. {textflowkit-0.1.8 → textflowkit-0.1.9}/scripts/verify_release_ci.py +0 -0
  79. {textflowkit-0.1.8 → textflowkit-0.1.9}/scripts/verify_release_versions.py +0 -0
  80. {textflowkit-0.1.8 → textflowkit-0.1.9}/scripts/write_release_manifest.py +0 -0
  81. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/adapters/__init__.py +0 -0
  82. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/assets/README.md +0 -0
  83. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/assets/selftest-speech.wav +0 -0
  84. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/__init__.py +0 -0
  85. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/batch.py +0 -0
  86. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/bind.py +0 -0
  87. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/cancel.py +0 -0
  88. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/diarize.py +0 -0
  89. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/executor.py +0 -0
  90. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/jobs.py +0 -0
  91. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/model.py +0 -0
  92. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/paths.py +0 -0
  93. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/retrieval.py +0 -0
  94. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/service.py +0 -0
  95. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/sqlite_store.py +0 -0
  96. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/startup.py +0 -0
  97. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/timeutil.py +0 -0
  98. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/core/translate.py +0 -0
  99. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/render/__init__.py +0 -0
  100. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/render/_cue_layout.py +0 -0
  101. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/render/_cue_text.py +0 -0
  102. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/render/_rename_excl.py +0 -0
  103. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/render/_rename_noreplace.py +0 -0
  104. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/render/docx.py +0 -0
  105. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/render/markdown.py +0 -0
  106. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/render/pdf.py +0 -0
  107. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/render/srt.py +0 -0
  108. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/render/txt.py +0 -0
  109. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/render/vtt.py +0 -0
  110. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/sources/__init__.py +0 -0
  111. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/sources/acquire.py +0 -0
  112. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/sources/detect.py +0 -0
  113. {textflowkit-0.1.8 → textflowkit-0.1.9}/src/textflowkit/sources/scratch.py +0 -0
  114. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/__init__.py +0 -0
  115. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/ollama_stub.py +0 -0
  116. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_acquire.py +0 -0
  117. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_bind.py +0 -0
  118. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_cancellation_finalization.py +0 -0
  119. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_cli_batch_feedback.py +0 -0
  120. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_cli_resume_batch.py +0 -0
  121. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_cli_stdout_format.py +0 -0
  122. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_confined_decoder_boundary.py +0 -0
  123. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_confined_indirect_media.py +0 -0
  124. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_confined_resume_boundary.py +0 -0
  125. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_container_example.py +0 -0
  126. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_cue_layout.py +0 -0
  127. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_diarize_example_flags.py +0 -0
  128. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_diarize_token_docs.py +0 -0
  129. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_engine_cache.py +0 -0
  130. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_engine_quiet.py +0 -0
  131. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_executor.py +0 -0
  132. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_executor_store_failure.py +0 -0
  133. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_export.py +0 -0
  134. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_export_file_mode.py +0 -0
  135. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_export_preflight.py +0 -0
  136. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_faster_whisper_docs.py +0 -0
  137. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_fetch_published_fonts.py +0 -0
  138. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_font_notices.py +0 -0
  139. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_http_host_boundary.py +0 -0
  140. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_http_trusted_proxy.py +0 -0
  141. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_job_progress.py +0 -0
  142. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_jobs.py +0 -0
  143. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_landing_commands.py +0 -0
  144. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_landing_page.py +0 -0
  145. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_legal_summary.py +0 -0
  146. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_linux_no_hardlink.py +0 -0
  147. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_live_smoke_script.py +0 -0
  148. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_macos_no_hardlink.py +0 -0
  149. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_mcp_batch_cookie_docs.py +0 -0
  150. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_mcp_http_boundary.py +0 -0
  151. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_mcp_protocol_cleanliness.py +0 -0
  152. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_mcp_terminal_guidance.py +0 -0
  153. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_mcp_tool_descriptions.py +0 -0
  154. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_mcp_work_root.py +0 -0
  155. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_model.py +0 -0
  156. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_paths.py +0 -0
  157. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_pipeline_integrity.py +0 -0
  158. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_production_browser_cookies.py +0 -0
  159. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_publish_ci_gate.py +0 -0
  160. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_publish_fonts_wiring.py +0 -0
  161. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_publish_manifest_wiring.py +0 -0
  162. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_publish_workflow_gate.py +0 -0
  163. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_readme_layout.py +0 -0
  164. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_readme_release_guard.py +0 -0
  165. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_release_manifest.py +0 -0
  166. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_release_surfaces.py +0 -0
  167. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_render.py +0 -0
  168. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_retrieval.py +0 -0
  169. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_sdist_allowlist.py +0 -0
  170. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_service_profile.py +0 -0
  171. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_site_not_found.py +0 -0
  172. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_sources.py +0 -0
  173. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_startup_recovery.py +0 -0
  174. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_stdio_protocol.py +0 -0
  175. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_translate_completeness.py +0 -0
  176. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_verify_release_versions.py +0 -0
  177. {textflowkit-0.1.8 → textflowkit-0.1.9}/tests/test_windows_no_hardlink.py +0 -0
@@ -58,7 +58,11 @@ jobs:
58
58
  run: |
59
59
  python -m pip install --upgrade pip
60
60
  python -m pip install ./packages/textflowkit-fonts
61
- python -m pip install -e ".[dev,mcp,http,export]"
61
+ # `whisper` is installed here because the existing engine tests
62
+ # (openai-whisper's verbose/quiet contract, model-name validation)
63
+ # genuinely need the openai-whisper package present. It is an optional
64
+ # extra now that Whistle is the default engine, so it must be named.
65
+ python -m pip install -e ".[dev,mcp,http,export,whisper]"
62
66
 
63
67
  - name: Show ffmpeg version
64
68
  shell: bash
@@ -1,5 +1,73 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.1.9 — 2026-10-05
4
+
5
+ **Whistle is now the default engine.** This release ships the Whistle integration
6
+ prepared on the `feat/whistle-primary` branch.
7
+
8
+ ### Changed
9
+
10
+ - **Whistle is the default engine; openai-whisper becomes an explicit opt-in.**
11
+ Whistle is a CPU-only native CLI that needs no PyTorch, so a fresh
12
+ `pip install textflowkit` no longer pulls the torch stack. `openai-whisper`
13
+ moves to a new optional `whisper` extra and is selected with `--engine whisper`
14
+ (or `engine="whisper"` on the Python/MCP/HTTP surfaces); the `all` extra still
15
+ includes it. A saved request or checkpoint from before this change keeps running
16
+ on the engine it names, so legacy Whisper work is never silently migrated, and
17
+ legacy engine aliases are preserved so older decoders keep working.
18
+ - **Engine selection is never silent.** An unknown engine name, an unsupported
19
+ language, or a non-CPU device for Whistle is refused at request construction —
20
+ before any media is fetched — and the message names `--engine whisper` as the
21
+ GPU alternative. The engine is not switched to satisfy a request.
22
+
23
+ ### Added
24
+
25
+ - **Whistle engine.** Seven advertised languages (`en`, `de`, `fr`, `es`, `it`,
26
+ `nl`, `pl`); native Windows x86-64/arm64, Linux x86-64/arm64, and Apple Silicon
27
+ (an Intel Mac is refused and pointed at the explicit Whisper engine); no WSL.
28
+ One pinned native binary and one pinned model are downloaded and hash-verified
29
+ on first use, outside the package. Long audio is split into 26-second cores with
30
+ up to 2 seconds of context (each clip ≤ 30 s); a word is owned by the clip whose
31
+ core contains its midpoint, so repeated phrases are preserved. Native
32
+ `--audio-stream` is never used.
33
+ - **Durable per-block resume for Whistle.** A long run persists a partial
34
+ checkpoint after each core; a resume validates the source and decoded-WAV
35
+ hashes and the configuration, then re-runs only the unfinished blocks and adopts
36
+ the rest. A partial body is Whistle-only. Reuses the existing single SQLite
37
+ store and its one-owning-process model.
38
+ - **Prompt, bounded cancellation.** A running Whistle run polls for cancellation
39
+ between clips and terminates its exact owned child process, bounded by a
40
+ per-clip timeout and a bounded stdout cap. A single long `openai-whisper` model
41
+ call is still cancellable only at its next stage boundary.
42
+ - **Telemetry off, unconditionally.** Every Whistle child process forces
43
+ `NEEDLE_TELEMETRY=0`, `DO_NOT_TRACK=1`, and `CI=1`, overriding a parent that
44
+ opted in, and there is no opt-in setting. The product adds no analytics, usage
45
+ SDK, anonymous ids, or events. `TEXTFLOWKIT_OFFLINE` refuses downloads and
46
+ `TEXTFLOWKIT_MODELS_DIR` chooses the asset directory; the pinned first-use
47
+ asset/model download is not telemetry.
48
+
49
+ ### Documentation
50
+
51
+ - README, user manual, install/adapter/architecture/roadmap/release docs and the
52
+ website source now describe Whistle as the shipped default. ROCm (and the
53
+ torch-pinning caveat) is scoped to the explicit `whisper` engine and
54
+ diarization, never to Whistle.
55
+ - Self-contained checked-in SVG architecture drawings were added under
56
+ `docs/assets` and embedded in the README, both manuals (`user-manual.md` and the
57
+ renamed **Developer and integration manual**, `adapters.md`), the architecture
58
+ guide, and the landing page.
59
+ - The live YouTube release smoke pins `--engine whisper --model tiny` so its
60
+ measured default-clip word stays reproducible across the default-engine change.
61
+
62
+ ### Verification boundary
63
+
64
+ Local evidence only: a 4-hour (14,407-second) CPU run produced 555
65
+ clips / 34,596 words / 2,875 segments in 731 seconds, offline with telemetry
66
+ forced off, and a separate two-process run proved durable block resume. These are
67
+ first local test numbers on one machine — not a universal performance claim, and
68
+ not a claim that Whistle's accuracy equals Whisper's. No human-scored
69
+ word-error rate is claimed.
70
+
3
71
  ## v0.1.8 — 2026-10-02
4
72
 
5
73
  The 2026-10-02 audit-lite repair release. Core version: 0.1.8; unchanged optional fonts: 0.1.6. It carries the four fixes for the post-release audit-lite of 2026-10-01 (findings AL-001 – AL-004). Release artifacts and publication status are recorded on [GitHub Releases](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.8). Local source-candidate verification does not establish installed-package or individual harness compatibility.
@@ -77,11 +77,13 @@ RUN set -eux; \
77
77
  WORKDIR /app
78
78
  COPY pyproject.toml README.md LICENSE ./
79
79
  COPY src ./src
80
- # The base dependencies are yt-dlp and openai-whisper, so this image is large:
81
- # openai-whisper pulls a torch build. That is the product's own requirement, not
82
- # something this file adds.
80
+ # The base dependencies are yt-dlp alone: the default engine (Whistle) is a
81
+ # native CPU binary fetched on first use, so this image does not pull a torch
82
+ # build. Selecting the openai-whisper engine needs the `whisper` extra named
83
+ # here; the image installs it because the container is meant to serve the
84
+ # Whisper stack too, and openai-whisper is what pulls the torch build.
83
85
  RUN python -m pip install --no-cache-dir --upgrade pip \
84
- && python -m pip install --no-cache-dir ".[http]"
86
+ && python -m pip install --no-cache-dir ".[http,whisper]"
85
87
 
86
88
  # Non-secret defaults only. The roots are the directories created above; Compose
87
89
  # repeats them so the volumes it mounts land on the paths the service reads.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: textflowkit
3
- Version: 0.1.8
3
+ Version: 0.1.9
4
4
  Summary: Cross-platform media transcription toolkit: URL or file in, timestamped transcripts and subtitle files out.
5
5
  Project-URL: Homepage, https://www.textflowkit.org/
6
6
  Project-URL: Issues, https://github.com/scottconverse/textflowkit/issues
@@ -21,11 +21,11 @@ Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
22
22
  Classifier: Topic :: Text Processing
23
23
  Requires-Python: >=3.10
24
- Requires-Dist: openai-whisper>=20240930
25
24
  Requires-Dist: yt-dlp>=2025.1.1
26
25
  Provides-Extra: all
27
26
  Requires-Dist: fastapi>=0.115; extra == 'all'
28
27
  Requires-Dist: mcp>=2.0; extra == 'all'
28
+ Requires-Dist: openai-whisper>=20240930; extra == 'all'
29
29
  Requires-Dist: pyannote-audio>=4.0; extra == 'all'
30
30
  Requires-Dist: pydantic>=2.7; extra == 'all'
31
31
  Requires-Dist: python-docx>=1.1; extra == 'all'
@@ -53,6 +53,8 @@ Requires-Dist: pydantic>=2.7; extra == 'http'
53
53
  Requires-Dist: uvicorn>=0.30; extra == 'http'
54
54
  Provides-Extra: mcp
55
55
  Requires-Dist: mcp>=2.0; extra == 'mcp'
56
+ Provides-Extra: whisper
57
+ Requires-Dist: openai-whisper>=20240930; extra == 'whisper'
56
58
  Description-Content-Type: text/markdown
57
59
 
58
60
  # textflowkit
@@ -62,9 +64,10 @@ Cross-platform media transcription toolkit. **One core, one CLI, thin adapters.*
62
64
  [Project landing page](https://www.textflowkit.org/) ·
63
65
  [PyPI package](https://pypi.org/project/textflowkit/) ·
64
66
  [GitHub releases](https://github.com/scottconverse/textflowkit/releases) ·
65
- [User manual](https://github.com/scottconverse/textflowkit/blob/main/docs/user-manual.md)
67
+ [User manual](https://github.com/scottconverse/textflowkit/blob/main/docs/user-manual.md) ·
68
+ [Developer and integration manual](https://github.com/scottconverse/textflowkit/blob/main/docs/adapters.md)
66
69
 
67
- **Current release: [v0.1.8](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.8).**
70
+ **Current release: [v0.1.9](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.9).**
68
71
 
69
72
  The [static site deployment](https://github.com/scottconverse/textflowkit/blob/main/docs/site-deployment.md) is hosted on Cloudflare
70
73
  Pages. GitHub remains the source and CI host; the website does not run the
@@ -111,7 +114,7 @@ URL or file ─► detect platform ─► acquire media ─► ffmpeg
111
114
  │
112
115
  ┌───────────────────────────┘
113
116
  ▼
114
- speech-to-text (Whisper)
117
+ speech-to-text (Whistle default; Whisper on request)
115
118
  │
116
119
  ▼
117
120
  canonical transcript (JSON)
@@ -125,6 +128,8 @@ DOCX and PDF are available through the optional `export` extra.
125
128
 
126
129
  ## Architecture
127
130
 
131
+ ![TextFlowKit shared-core architecture: four thin entry points (CLI, Python, MCP, HTTP) feed one core that acquires and decodes media, transcribes with the default Whistle engine or an explicitly selected openai-whisper, keeps job state and resume checkpoints in an optional SQLite store, adds optional speaker or translation postprocessing, and publishes TXT, SRT, VTT, JSON, Markdown, and optional DOCX/PDF exports.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.9/docs/assets/architecture-overview.svg)
132
+
128
133
  The design principle is **one engine, three doors**. Everything of substance lives in
129
134
  the core; the interfaces are thin.
130
135
 
@@ -164,6 +169,22 @@ python -m pip install textflowkit
164
169
  textflowkit doctor
165
170
  ```
166
171
 
172
+ The default engine is **Whistle**: a native CPU-only transcription CLI that
173
+ needs no PyTorch. It downloads one pinned model (about 17 MB) and a small pinned
174
+ binary on first use; a fresh default install therefore stays small and pulls no
175
+ torch. To use the `openai-whisper` engine instead — the ROCm/CUDA/CPU torch
176
+ stack — install the `whisper` extra and name it explicitly:
177
+
178
+ ```bash
179
+ python -m pip install 'textflowkit[whisper]'
180
+ textflowkit transcribe meeting.mp4 --engine whisper --model small
181
+ ```
182
+
183
+ See [Whistle (default engine)](#whistle-default-engine) for platform and
184
+ language coverage, and the [install guide](https://github.com/scottconverse/textflowkit/blob/main/docs/install.md)
185
+ for the AMD ROCm path, which applies to the explicit `whisper` engine and
186
+ diarization.
187
+
167
188
  For MCP or the JSON HTTP adapter, install the matching extra:
168
189
 
169
190
  ```bash
@@ -187,7 +208,6 @@ from textflowkit import transcribe
187
208
 
188
209
  result = transcribe(
189
210
  "meeting.mp4", # also accepts supported URLs
190
- model="small",
191
211
  formats=["json", "srt", "txt"],
192
212
  output_dir="transcripts", # omit to return the transcript without writing files
193
213
  )
@@ -200,6 +220,10 @@ for segment in result.transcript.segments:
200
220
  print(" ", word.start, word.end, word.text)
201
221
  ```
202
222
 
223
+ `engine` defaults to the product default (Whistle) and `model=None` resolves to
224
+ that engine's own model. To use the Whisper-family engines, name one and its
225
+ model explicitly: `transcribe("meeting.mp4", engine="whisper", model="small")`.
226
+
203
227
  `transcribe()` returns `TranscribeResult` with a canonical `Transcript` and
204
228
  written output paths. `Transcript.to_dict()` / `.to_json()` preserve segment and
205
229
  word timing; older transcript JSON without `words` remains readable. Pass
@@ -264,6 +288,60 @@ does **not** assert that the remote bytes are still identical.
264
288
  textflowkit export ./transcript.json --format vtt
265
289
  ```
266
290
 
291
+ ## Whistle (default engine)
292
+
293
+ **Whistle is the default engine** as of v0.1.9. It is a native CPU-only CLI that
294
+ needs no PyTorch, so a fresh `pip install textflowkit` stays small and pulls no
295
+ torch; `openai-whisper` is an explicitly selectable opt-in engine.
296
+
297
+ ![Whistle bounded block run and durable resume: a decoded PCM WAV is split into 26-second cores with up to 2 seconds of context (each clip ≤ 30 s), each core runs as one owned child with forced telemetry-off flags, a partial checkpoint is written after each core, and a resume validates the source and decoded-WAV hashes then re-runs only the unfinished cores.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.9/docs/assets/whistle-resume.svg)
298
+
299
+ - **CPU only, no torch.** It runs a pinned native binary and downloads one pinned
300
+ model on first use. Naming a GPU device is refused rather than silently
301
+ ignored: a GPU request names the explicit `whisper` engine as the alternative,
302
+ and the engine is never switched for you.
303
+ - **Seven advertised languages:** `en`, `de`, `fr`, `es`, `it`, `nl`, `pl`. A
304
+ request for another language is refused at request construction, before any
305
+ media is fetched.
306
+ - **Native platforms:** Windows x86-64 and arm64, Linux x86-64 and arm64, and
307
+ Apple Silicon. **Intel Macs are not supported**; a request there is refused
308
+ with a message that names the explicit `whisper` engine instead. No WSL layer
309
+ is involved.
310
+ - **How it works:** long audio is split into 26-second cores with up to 2 seconds
311
+ of context on each side, so every standalone clip is ≤ 30 seconds (the native
312
+ CLI's limit). Overlap words are selected by core midpoint rather than text
313
+ deduplication, so repeated spoken phrases are preserved.
314
+ - **Streaming is not used.** The native `--audio-stream` mode failed its
315
+ coverage and is never passed; only the committed standalone output is parsed.
316
+ - **Durable block resume.** With `TEXTFLOWKIT_DB` set, a long run writes partial
317
+ per-block checkpoints; a resume checks the source and decoded-WAV hashes and the
318
+ config, then re-runs only the blocks that were not finished. The durable store
319
+ still assumes one owning process.
320
+ - **Cancellation.** A running Whistle run terminates its exact owned child
321
+ process bounded by a per-clip timeout. A single long `openai-whisper` model call
322
+ is still only cancellable at its next stage boundary, as documented for the
323
+ Whisper engine.
324
+ - **Telemetry is off, unconditionally.** Every Whistle child process forces
325
+ `NEEDLE_TELEMETRY=0`, `DO_NOT_TRACK=1`, and `CI=1`, overriding a parent that
326
+ opted in. The product ships no analytics, usage SDK, anonymous ids, or events,
327
+ and there is no opt-in setting. This applies the upstream documented gate; it
328
+ is **not** a claim that the upstream binary's tracking code is physically
329
+ removed.
330
+ - **Offline and downloads.** Set `TEXTFLOWKIT_OFFLINE` to refuse any download, and
331
+ `TEXTFLOWKIT_MODELS_DIR` to choose the asset directory. The Whistle asset helper
332
+ downloads only pinned runtime/model assets; media acquisition and optional
333
+ translation may independently use the network. These downloads are not
334
+ telemetry.
335
+
336
+ **Verification boundary.** A local run transcribed a 4-hour (14,407 s)
337
+ recording on CPU into 555 clips / 34,596 words / 2,875 segments in 731 s, with
338
+ telemetry forced off and offline mode on; a separate run proved durable block
339
+ resume across two processes. These are **first local test numbers**, not a
340
+ promise of universal performance or of accuracy equal to Whisper — no
341
+ human-scored word-error rate is claimed. See the
342
+ [user manual](https://github.com/scottconverse/textflowkit/blob/main/docs/user-manual.md)
343
+ and [install guide](https://github.com/scottconverse/textflowkit/blob/main/docs/install.md).
344
+
267
345
  ## Use as an MCP server
268
346
 
269
347
  ```bash
@@ -323,7 +401,7 @@ scope here, so no image is claimed to build or start. See
323
401
 
324
402
  ```bash
325
403
  TEXTFLOWKIT_DB=./jobs.db # job state survives restart (SQLite)
326
- TEXTFLOWKIT_MAX_CONCURRENCY=1 # default; Whisper saturates a GPU alone
404
+ TEXTFLOWKIT_MAX_CONCURRENCY=1 # default; bounded job worker pool
327
405
  ```
328
406
 
329
407
  `cancel_job` stops a queued job immediately, or a running job at its next stage
@@ -338,14 +416,31 @@ web frontend share the job contract without blocking a request.
338
416
 
339
417
  ## Status
340
418
 
341
- **v0.1.8 release.** Core, CLI, MCP, and HTTP have automated
342
- coverage. This release carries the four fixes for the 2026-10-01 post-release
343
- audit-lite (findings AL-001 – AL-004): CLI owning-process startup recovery so an
344
- interrupted run's saved work can be resumed instead of being refused as
345
- "already active", preservation of the completed transcript across resume setup
346
- failures, a correction to the MCP batch cookie capability, and corrected roadmap
347
- evidence receipts. It builds on the v0.1.7 audit repair set and the v0.1.6
348
- review repairs that precede it. The preceding v0.1.7 source candidate was verified with real CLI, HTTP and MCP speech, seven-format exports and completed resume. The v0.1.8 runtime fixes were independently checked through fresh-process CLI restart probes, real SQLite setup-failure tests, and the full Windows test suite; those checks are not new individual-harness receipts. Release publication uses the tag workflow, which requires successful exact-commit main CI before PyPI uploads and creates the public GitHub release only afterward. Check the linked release for artifacts and workflow status; local source verification is not a fresh PyPI-install or individual-harness receipt.
419
+ **v0.1.9 release.** Core, CLI, MCP, and HTTP have automated
420
+ coverage. This release makes **Whistle the default engine** — a native CPU-only
421
+ transcriber that needs no PyTorch — and moves `openai-whisper` to the optional
422
+ `whisper` extra, selected explicitly with `--engine whisper` or
423
+ `engine="whisper"`. Legacy engine aliases are preserved, so older saved jobs and
424
+ `transcribe()` calls still decode. Whistle refuses an unsupported platform,
425
+ language, or GPU request instead of silently falling back. Its engine's
426
+ verification boundary is in [Whistle (default engine)](#whistle-default-engine)
427
+ above. Release publication uses the tag workflow, which requires successful
428
+ exact-commit main CI before PyPI uploads and creates the public GitHub release
429
+ only afterward. Check the linked release for artifacts and workflow status; local
430
+ source verification is not a fresh PyPI-install or individual-harness receipt.
431
+
432
+ The v0.1.8 release carried four fixes for the 2026-10-01 post-release audit-lite
433
+ (findings AL-001 – AL-004): CLI owning-process startup recovery so an interrupted
434
+ run's saved work can be resumed instead of being refused as "already active",
435
+ preservation of the completed transcript across resume setup failures, a
436
+ correction to the MCP batch cookie capability, and corrected roadmap evidence
437
+ receipts. It built on the v0.1.7 audit repair set and the v0.1.6 review repairs
438
+ that precede it. The v0.1.7 source candidate was verified with real CLI, HTTP and
439
+ MCP speech, seven-format exports and completed resume; the v0.1.8 runtime fixes
440
+ were independently checked through fresh-process CLI restart probes, real SQLite
441
+ setup-failure tests, and the full Windows test suite. Those checks are not new
442
+ individual-harness receipts. That is the previous release's record, not evidence
443
+ for v0.1.9.
349
444
 
350
445
  The v0.1.6 release was the post-v0.1.5 review repair set: security hardening
351
446
  for media acquisition and the HTTP and MCP adapters, safer subtitle wrapping and
@@ -362,9 +457,9 @@ The v0.1.6 release is public: the
362
457
  [v0.1.6 GitHub release](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.6)
363
458
  and both [core](https://pypi.org/project/textflowkit/0.1.6/) and
364
459
  [fonts](https://pypi.org/project/textflowkit-fonts/0.1.6/) PyPI projects are
365
- live — that is the previous release's evidence, not a receipt for v0.1.8.
460
+ live — that is the v0.1.6 release's evidence, not a receipt for a later release.
366
461
  Everything from here to the end of this section is the v0.1.6 release's
367
- historical record as published: it is not re-verified for v0.1.8, whose separate release evidence is not supplied by these historical paragraphs.
462
+ historical record as published: it is not re-verified for v0.1.9, whose separate release evidence is not supplied by these historical paragraphs.
368
463
  Merged-main CI passed 16/16 on the tagged commit; the published wheel and
369
464
  sdist digests match the GitHub release assets and their SHA-256 list; and a fresh
370
465
  Windows Python 3.12 install of `textflowkit[export,mcp,http]==0.1.6` from PyPI
@@ -5,9 +5,10 @@ Cross-platform media transcription toolkit. **One core, one CLI, thin adapters.*
5
5
  [Project landing page](https://www.textflowkit.org/) ·
6
6
  [PyPI package](https://pypi.org/project/textflowkit/) ·
7
7
  [GitHub releases](https://github.com/scottconverse/textflowkit/releases) ·
8
- [User manual](https://github.com/scottconverse/textflowkit/blob/main/docs/user-manual.md)
8
+ [User manual](https://github.com/scottconverse/textflowkit/blob/main/docs/user-manual.md) ·
9
+ [Developer and integration manual](https://github.com/scottconverse/textflowkit/blob/main/docs/adapters.md)
9
10
 
10
- **Current release: [v0.1.8](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.8).**
11
+ **Current release: [v0.1.9](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.9).**
11
12
 
12
13
  The [static site deployment](https://github.com/scottconverse/textflowkit/blob/main/docs/site-deployment.md) is hosted on Cloudflare
13
14
  Pages. GitHub remains the source and CI host; the website does not run the
@@ -54,7 +55,7 @@ URL or file ─► detect platform ─► acquire media ─► ffmpeg
54
55
  │
55
56
  ┌───────────────────────────┘
56
57
  ▼
57
- speech-to-text (Whisper)
58
+ speech-to-text (Whistle default; Whisper on request)
58
59
  │
59
60
  ▼
60
61
  canonical transcript (JSON)
@@ -68,6 +69,8 @@ DOCX and PDF are available through the optional `export` extra.
68
69
 
69
70
  ## Architecture
70
71
 
72
+ ![TextFlowKit shared-core architecture: four thin entry points (CLI, Python, MCP, HTTP) feed one core that acquires and decodes media, transcribes with the default Whistle engine or an explicitly selected openai-whisper, keeps job state and resume checkpoints in an optional SQLite store, adds optional speaker or translation postprocessing, and publishes TXT, SRT, VTT, JSON, Markdown, and optional DOCX/PDF exports.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.9/docs/assets/architecture-overview.svg)
73
+
71
74
  The design principle is **one engine, three doors**. Everything of substance lives in
72
75
  the core; the interfaces are thin.
73
76
 
@@ -107,6 +110,22 @@ python -m pip install textflowkit
107
110
  textflowkit doctor
108
111
  ```
109
112
 
113
+ The default engine is **Whistle**: a native CPU-only transcription CLI that
114
+ needs no PyTorch. It downloads one pinned model (about 17 MB) and a small pinned
115
+ binary on first use; a fresh default install therefore stays small and pulls no
116
+ torch. To use the `openai-whisper` engine instead — the ROCm/CUDA/CPU torch
117
+ stack — install the `whisper` extra and name it explicitly:
118
+
119
+ ```bash
120
+ python -m pip install 'textflowkit[whisper]'
121
+ textflowkit transcribe meeting.mp4 --engine whisper --model small
122
+ ```
123
+
124
+ See [Whistle (default engine)](#whistle-default-engine) for platform and
125
+ language coverage, and the [install guide](https://github.com/scottconverse/textflowkit/blob/main/docs/install.md)
126
+ for the AMD ROCm path, which applies to the explicit `whisper` engine and
127
+ diarization.
128
+
110
129
  For MCP or the JSON HTTP adapter, install the matching extra:
111
130
 
112
131
  ```bash
@@ -130,7 +149,6 @@ from textflowkit import transcribe
130
149
 
131
150
  result = transcribe(
132
151
  "meeting.mp4", # also accepts supported URLs
133
- model="small",
134
152
  formats=["json", "srt", "txt"],
135
153
  output_dir="transcripts", # omit to return the transcript without writing files
136
154
  )
@@ -143,6 +161,10 @@ for segment in result.transcript.segments:
143
161
  print(" ", word.start, word.end, word.text)
144
162
  ```
145
163
 
164
+ `engine` defaults to the product default (Whistle) and `model=None` resolves to
165
+ that engine's own model. To use the Whisper-family engines, name one and its
166
+ model explicitly: `transcribe("meeting.mp4", engine="whisper", model="small")`.
167
+
146
168
  `transcribe()` returns `TranscribeResult` with a canonical `Transcript` and
147
169
  written output paths. `Transcript.to_dict()` / `.to_json()` preserve segment and
148
170
  word timing; older transcript JSON without `words` remains readable. Pass
@@ -207,6 +229,60 @@ does **not** assert that the remote bytes are still identical.
207
229
  textflowkit export ./transcript.json --format vtt
208
230
  ```
209
231
 
232
+ ## Whistle (default engine)
233
+
234
+ **Whistle is the default engine** as of v0.1.9. It is a native CPU-only CLI that
235
+ needs no PyTorch, so a fresh `pip install textflowkit` stays small and pulls no
236
+ torch; `openai-whisper` is an explicitly selectable opt-in engine.
237
+
238
+ ![Whistle bounded block run and durable resume: a decoded PCM WAV is split into 26-second cores with up to 2 seconds of context (each clip ≤ 30 s), each core runs as one owned child with forced telemetry-off flags, a partial checkpoint is written after each core, and a resume validates the source and decoded-WAV hashes then re-runs only the unfinished cores.](https://raw.githubusercontent.com/scottconverse/textflowkit/v0.1.9/docs/assets/whistle-resume.svg)
239
+
240
+ - **CPU only, no torch.** It runs a pinned native binary and downloads one pinned
241
+ model on first use. Naming a GPU device is refused rather than silently
242
+ ignored: a GPU request names the explicit `whisper` engine as the alternative,
243
+ and the engine is never switched for you.
244
+ - **Seven advertised languages:** `en`, `de`, `fr`, `es`, `it`, `nl`, `pl`. A
245
+ request for another language is refused at request construction, before any
246
+ media is fetched.
247
+ - **Native platforms:** Windows x86-64 and arm64, Linux x86-64 and arm64, and
248
+ Apple Silicon. **Intel Macs are not supported**; a request there is refused
249
+ with a message that names the explicit `whisper` engine instead. No WSL layer
250
+ is involved.
251
+ - **How it works:** long audio is split into 26-second cores with up to 2 seconds
252
+ of context on each side, so every standalone clip is ≤ 30 seconds (the native
253
+ CLI's limit). Overlap words are selected by core midpoint rather than text
254
+ deduplication, so repeated spoken phrases are preserved.
255
+ - **Streaming is not used.** The native `--audio-stream` mode failed its
256
+ coverage and is never passed; only the committed standalone output is parsed.
257
+ - **Durable block resume.** With `TEXTFLOWKIT_DB` set, a long run writes partial
258
+ per-block checkpoints; a resume checks the source and decoded-WAV hashes and the
259
+ config, then re-runs only the blocks that were not finished. The durable store
260
+ still assumes one owning process.
261
+ - **Cancellation.** A running Whistle run terminates its exact owned child
262
+ process bounded by a per-clip timeout. A single long `openai-whisper` model call
263
+ is still only cancellable at its next stage boundary, as documented for the
264
+ Whisper engine.
265
+ - **Telemetry is off, unconditionally.** Every Whistle child process forces
266
+ `NEEDLE_TELEMETRY=0`, `DO_NOT_TRACK=1`, and `CI=1`, overriding a parent that
267
+ opted in. The product ships no analytics, usage SDK, anonymous ids, or events,
268
+ and there is no opt-in setting. This applies the upstream documented gate; it
269
+ is **not** a claim that the upstream binary's tracking code is physically
270
+ removed.
271
+ - **Offline and downloads.** Set `TEXTFLOWKIT_OFFLINE` to refuse any download, and
272
+ `TEXTFLOWKIT_MODELS_DIR` to choose the asset directory. The Whistle asset helper
273
+ downloads only pinned runtime/model assets; media acquisition and optional
274
+ translation may independently use the network. These downloads are not
275
+ telemetry.
276
+
277
+ **Verification boundary.** A local run transcribed a 4-hour (14,407 s)
278
+ recording on CPU into 555 clips / 34,596 words / 2,875 segments in 731 s, with
279
+ telemetry forced off and offline mode on; a separate run proved durable block
280
+ resume across two processes. These are **first local test numbers**, not a
281
+ promise of universal performance or of accuracy equal to Whisper — no
282
+ human-scored word-error rate is claimed. See the
283
+ [user manual](https://github.com/scottconverse/textflowkit/blob/main/docs/user-manual.md)
284
+ and [install guide](https://github.com/scottconverse/textflowkit/blob/main/docs/install.md).
285
+
210
286
  ## Use as an MCP server
211
287
 
212
288
  ```bash
@@ -266,7 +342,7 @@ scope here, so no image is claimed to build or start. See
266
342
 
267
343
  ```bash
268
344
  TEXTFLOWKIT_DB=./jobs.db # job state survives restart (SQLite)
269
- TEXTFLOWKIT_MAX_CONCURRENCY=1 # default; Whisper saturates a GPU alone
345
+ TEXTFLOWKIT_MAX_CONCURRENCY=1 # default; bounded job worker pool
270
346
  ```
271
347
 
272
348
  `cancel_job` stops a queued job immediately, or a running job at its next stage
@@ -281,14 +357,31 @@ web frontend share the job contract without blocking a request.
281
357
 
282
358
  ## Status
283
359
 
284
- **v0.1.8 release.** Core, CLI, MCP, and HTTP have automated
285
- coverage. This release carries the four fixes for the 2026-10-01 post-release
286
- audit-lite (findings AL-001 – AL-004): CLI owning-process startup recovery so an
287
- interrupted run's saved work can be resumed instead of being refused as
288
- "already active", preservation of the completed transcript across resume setup
289
- failures, a correction to the MCP batch cookie capability, and corrected roadmap
290
- evidence receipts. It builds on the v0.1.7 audit repair set and the v0.1.6
291
- review repairs that precede it. The preceding v0.1.7 source candidate was verified with real CLI, HTTP and MCP speech, seven-format exports and completed resume. The v0.1.8 runtime fixes were independently checked through fresh-process CLI restart probes, real SQLite setup-failure tests, and the full Windows test suite; those checks are not new individual-harness receipts. Release publication uses the tag workflow, which requires successful exact-commit main CI before PyPI uploads and creates the public GitHub release only afterward. Check the linked release for artifacts and workflow status; local source verification is not a fresh PyPI-install or individual-harness receipt.
360
+ **v0.1.9 release.** Core, CLI, MCP, and HTTP have automated
361
+ coverage. This release makes **Whistle the default engine** — a native CPU-only
362
+ transcriber that needs no PyTorch — and moves `openai-whisper` to the optional
363
+ `whisper` extra, selected explicitly with `--engine whisper` or
364
+ `engine="whisper"`. Legacy engine aliases are preserved, so older saved jobs and
365
+ `transcribe()` calls still decode. Whistle refuses an unsupported platform,
366
+ language, or GPU request instead of silently falling back. Its engine's
367
+ verification boundary is in [Whistle (default engine)](#whistle-default-engine)
368
+ above. Release publication uses the tag workflow, which requires successful
369
+ exact-commit main CI before PyPI uploads and creates the public GitHub release
370
+ only afterward. Check the linked release for artifacts and workflow status; local
371
+ source verification is not a fresh PyPI-install or individual-harness receipt.
372
+
373
+ The v0.1.8 release carried four fixes for the 2026-10-01 post-release audit-lite
374
+ (findings AL-001 – AL-004): CLI owning-process startup recovery so an interrupted
375
+ run's saved work can be resumed instead of being refused as "already active",
376
+ preservation of the completed transcript across resume setup failures, a
377
+ correction to the MCP batch cookie capability, and corrected roadmap evidence
378
+ receipts. It built on the v0.1.7 audit repair set and the v0.1.6 review repairs
379
+ that precede it. The v0.1.7 source candidate was verified with real CLI, HTTP and
380
+ MCP speech, seven-format exports and completed resume; the v0.1.8 runtime fixes
381
+ were independently checked through fresh-process CLI restart probes, real SQLite
382
+ setup-failure tests, and the full Windows test suite. Those checks are not new
383
+ individual-harness receipts. That is the previous release's record, not evidence
384
+ for v0.1.9.
292
385
 
293
386
  The v0.1.6 release was the post-v0.1.5 review repair set: security hardening
294
387
  for media acquisition and the HTTP and MCP adapters, safer subtitle wrapping and
@@ -305,9 +398,9 @@ The v0.1.6 release is public: the
305
398
  [v0.1.6 GitHub release](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.6)
306
399
  and both [core](https://pypi.org/project/textflowkit/0.1.6/) and
307
400
  [fonts](https://pypi.org/project/textflowkit-fonts/0.1.6/) PyPI projects are
308
- live — that is the previous release's evidence, not a receipt for v0.1.8.
401
+ live — that is the v0.1.6 release's evidence, not a receipt for a later release.
309
402
  Everything from here to the end of this section is the v0.1.6 release's
310
- historical record as published: it is not re-verified for v0.1.8, whose separate release evidence is not supplied by these historical paragraphs.
403
+ historical record as published: it is not re-verified for v0.1.9, whose separate release evidence is not supplied by these historical paragraphs.
311
404
  Merged-main CI passed 16/16 on the tagged commit; the published wheel and
312
405
  sdist digests match the GitHub release assets and their SHA-256 list; and a fresh
313
406
  Windows Python 3.12 install of `textflowkit[export,mcp,http]==0.1.6` from PyPI
@@ -72,9 +72,10 @@ services:
72
72
  - "textflowkit-output:/srv/textflowkit/output"
73
73
  - "textflowkit-work:/srv/textflowkit/work"
74
74
  - "textflowkit-state:/srv/textflowkit/state"
75
- # openai-whisper downloads each model into `~/.cache/whisper` (its own
76
- # default download root), so without this volume a recreated container
77
- # downloads the model again.
75
+ # Each engine caches its own model under `~/.cache`: openai-whisper in
76
+ # `~/.cache/whisper`, and Whistle's pinned binary and model in its own
77
+ # directory there. Without this volume a recreated container downloads
78
+ # them again.
78
79
  - "textflowkit-cache:/home/textflowkit/.cache"
79
80
  healthcheck:
80
81
  test:
@@ -1,6 +1,6 @@
1
- # Adapters and integration
1
+ # Developer and integration manual
2
2
 
3
- Current published release: [v0.1.8](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.8).
3
+ Current release: [v0.1.9](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.9).
4
4
  Start with the [user manual](user-manual.md) for everyday use. Harness versions
5
5
  in the compatibility table below identify **historical test sessions**, not
6
6
  the version of TextFlowKit currently published.
@@ -9,6 +9,8 @@ textflowkit has one core and several thin doors. Nothing is duplicated between
9
9
  them: the CLI, the MCP server, and the HTTP API all call
10
10
  `textflowkit.core.submission` and share one job model.
11
11
 
12
+ ![TextFlowKit shared-core architecture: four thin entry points (CLI, Python, MCP, HTTP) feed one core that acquires and decodes media, transcribes with the default Whistle engine or an explicitly selected openai-whisper, keeps job state and resume checkpoints in an optional SQLite store, adds optional speaker or translation postprocessing, and publishes TXT, SRT, VTT, JSON, Markdown, and optional DOCX/PDF exports.](assets/architecture-overview.svg)
13
+
12
14
  ```
13
15
  ┌──────────────────────┐
14
16
  CLI ────────────►│ │
@@ -200,11 +202,15 @@ the same options: `source`, `language`, `formats`, `output_dir`, `model`,
200
202
  `/jobs/batch` these are **per item**: each entry is its own full request, so an
201
203
  item's model/language/device apply only to that item (unlike the MCP batch tool,
202
204
  which takes one shared set for every source).
203
- `engine` defaults to `whisper` — openai-whisper on the torch stack: ROCm on AMD,
204
- CUDA on NVIDIA, CPU otherwise — and may be set to `faster-whisper`, the opt-in
205
- CTranslate2 engine for CPU and Apple Silicon that needs
206
- `pip install "textflowkit[faster-whisper]"`. The default is unchanged, and no
207
- speed or accuracy comparison between the engines is claimed. On a **single**
205
+ `engine` defaults to `whistle` (a CPU-only native CLI needing no torch).
206
+ `engine: "whisper"`
207
+ selects openai-whisper on the torch stack — ROCm on AMD, CUDA on NVIDIA, CPU
208
+ otherwise — and needs `pip install "textflowkit[whisper]"`; `engine:
209
+ "faster-whisper"` is the opt-in CTranslate2 engine for CPU and Apple Silicon that
210
+ needs `pip install "textflowkit[faster-whisper]"`. An unknown engine name, an
211
+ unsupported language or non-CPU device for Whistle, or a missing extra is refused
212
+ rather than silently re-routed, and no speed or accuracy comparison between the
213
+ engines is claimed. On a **single**
208
214
  submission (`POST /jobs`) an unknown engine name or a missing extra is answered
209
215
  with **422 before a job record is written**. On `/jobs/batch` each item is
210
216
  admitted independently: an unusable engine (or any other per-item refusal - an
@@ -530,8 +536,9 @@ pool:
530
536
  TEXTFLOWKIT_MAX_CONCURRENCY=1 # default
531
537
  ```
532
538
 
533
- The default is **1** deliberately. Whisper saturates a GPU on its own, so parallel
534
- jobs thrash VRAM rather than finishing sooner.
539
+ The default is **1** deliberately. A single model call saturates a GPU (Whisper)
540
+ or holds a CPU-bound native run (Whistle) on its own, so parallel jobs contend for
541
+ that resource rather than finishing sooner.
535
542
 
536
543
  **Raising the bound does not by itself parallelise transcription.** Model inference
537
544
  is serialized per engine *instance*: `WhisperEngine.transcribe` holds a lock across
@@ -563,7 +570,11 @@ because these are genuinely different situations:
563
570
  mid-call. Download hooks and the ffmpeg process respond during acquisition and
564
571
  decode, but cancellation during a 20-minute Whisper call takes effect when that
565
572
  call returns. The API reports `cancelling` rather than claiming an instant stop
566
- it cannot deliver.
573
+ it cannot deliver. The default Whistle engine is the exception: a
574
+ running Whistle run polls for cancellation between clips and terminates its exact
575
+ owned child process, bounded by a per-clip timeout — so a long Whistle job stops
576
+ promptly, while a single long `openai-whisper` model call is still only
577
+ cancellable at its next stage boundary.
567
578
 
568
579
  ## Export formats
569
580