Skip to content

feat(cli): stdin support, download progress, bash completion, thread-safe defaults#11

Merged
StuBehan merged 1 commit into
mainfrom
feat/cli-quality-of-life
Apr 29, 2026
Merged

feat(cli): stdin support, download progress, bash completion, thread-safe defaults#11
StuBehan merged 1 commit into
mainfrom
feat/cli-quality-of-life

Conversation

@StuBehan
Copy link
Copy Markdown
Collaborator

@StuBehan StuBehan commented Apr 28, 2026

Summary

Four small quality-of-life CLI/engine improvements from the post-v0.2.0 audit. None change existing behavior; all are purely additive.

Changes

`feat`: read text from piped stdin

`echo hi | stackvox` now works (previously printed help). `_read_text` picks `--file` > positional > piped stdin, in that order. Existing patterns (positional text, `--file`) are unchanged.

`feat`: progress on first-run model download

The ~340 MB Kokoro model used to download silently — looked like a hang on slow networks. Now prints a percentage line to stderr via `urlretrieve`'s `reporthook`. No new dependencies; `tqdm` would have been heavier than the value added.

`feat`: bash completion via `stackvox completion bash`

```bash
eval "$(stackvox completion bash)" # current shell

or persist:

stackvox completion bash > ~/.stackvox-completion.bash
echo 'source ~/.stackvox-completion.bash' >> ~/.bashrc
```

Static script — completes subcommands, common flags, and file paths for `--file` / `--out`. Voice list isn't auto-completed because that would require loading the model on every TAB. zsh/fish can be added later if asked for.

`fix`: thread-safe `_default` Stackvox singleton

`_get_default()` previously had a TOCTOU race: two threads concurrently calling the module-level `speak()` / `synthesize()` at process start could both see `_default is None` and each instantiate a `Stackvox` (each loading the 340 MB model). Now wrapped in double-checked locking — fast path stays lock-free after init.

Tests

  • `test_piped_stdin_with_no_args_routes_to_speak` — covers the new shortcut.
  • `test_read_text_prefers_file_then_positional_then_stdin` — covers the precedence ladder including stdin fallback.
  • `test_completion_bash_emits_complete_script` — sanity-check the emitted script.
  • New `autouse` fixture in `test_cli.py` defaults `sys.stdin.isatty` to `True` so existing tests aren't perturbed by the piped-stdin shortcut.

`pytest`: 17/17 passing locally. `ruff check`, `ruff format --check`, `mypy` all clean.

Test plan

  • CI passes (lint, format, mypy, tests 3.10–3.13 on Ubuntu, test-macos py3.12, PR-title, commit lint).
  • Manually: `echo "hello" | stackvox` plays.
  • Manually: `stackvox completion bash` outputs a script that successfully completes `stackvox ` after sourcing.

Summary by cubic

Adds stdin piping, visible first-run model download progress, and Bash completion to the stackvox CLI, plus a thread-safe default engine to prevent duplicate model loads.

  • New Features

    • Piped stdin: echo "hi" | stackvox now works; text precedence is --file > positional > stdin.
    • Download progress: show percentage to stderr while the Kokoro model downloads on first run; no new deps.
    • Bash completion: stackvox completion bash emits a static script that completes subcommands, common flags, and file paths.
  • Bug Fixes

    • Make _get_default() thread-safe with double-checked locking to avoid creating multiple Stackvox instances under concurrent first calls.

Written for commit 1a56141. Summary will update on new commits. Review in cubic

…safe defaults

Four small quality-of-life improvements bundled into one PR:

 - cli: read text from piped stdin when --file and the positional are
   absent. `echo hi | stackvox` defaults to speak. _read_text picks
   --file > positional > stdin in that order.

 - engine: print percentage updates to stderr while downloading the
   ~340 MB Kokoro model on first run, so it doesn't look like a hang.
   Uses urlretrieve's reporthook callback — no new deps.

 - cli: new `stackvox completion bash` subcommand emits a static bash
   completion script. Source via eval or persist to a file under
   ~/.bashrc. Voice list isn't auto-completed (would require loading
   the model on every TAB); subcommands and most flags are.

 - engine: wrap _get_default in double-checked locking. Without it,
   concurrent first calls to the module-level speak()/synthesize()
   could race and instantiate two Stackvox engines, each loading the
   model.

Tests: stdin precedence, piped-stdin shortcut, completion script
emission. New autouse fixture defaults stdin to a TTY so existing
tests aren't perturbed by the new shortcut. README CLI section
updated with stdin example and completion install snippet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@StuBehan StuBehan merged commit 2ae8f85 into main Apr 29, 2026
10 checks passed
This was referenced Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant