saynow {{VERSION}} — speak text aloud from the terminal

Plays synthesised speech through the speakers. Built to be called by LLM
agents that need to tell a human something out loud. Runs with no setup by
falling back to the operating system's built-in voice.

USAGE
  saynow [options] <text...>    Speak the given text
  saynow [options]              Speak text read from stdin
  saynow <command> [args]       Run a subcommand

COMMANDS
  init                   Interactively pick a provider and store an API key
  config list            Print every setting and its value, secrets redacted
  config get <key>       Print one setting
  config set <key> <v>   Change one setting and save it
  config path            Print the absolute path of the config file
  voices                 List voice names valid for the active provider
  models                 List OpenRouter speech models with their prices
  history                List archived clips, newest first
  history open [n]       Play an archived clip, default the newest
  history path           Print the archive directory
  history clear          Delete every archived clip
  help                   Show this text

OPTIONS
  -v, --voice <name>     Voice name. Valid values differ per provider; run
                         saynow voices. Default: the provider's own default.
  -p, --provider <id>    Where speech is synthesised. One of:
                         {{PROVIDERS}}.
                         Default: system, or the configured provider.
  -m, --model <id>       Model id, for providers that have models (openai,
                         elevenlabs, openrouter). Run saynow models.
  -r, --rate <n>         Speaking rate in words per minute. system provider
                         only. Typical range 150-350. Default: OS default.
  -s, --speed <n>        Playback speed multiplier, 0.25 to 4.0. Cloud
                         providers only; openrouter ignores it. Default: 1.
      --ask              Add a reply box to the bubble and wait for an answer,
                         which is printed to stdout. See ASKING below. Cannot
                         be combined with --save.
      --no-ui            Speak without showing the bubble at all.
      --save <file>      Write audio to <file> instead of playing it, and
                         print the absolute path. Not supported by the system
                         provider. Format is mp3, or wav for openrouter.
      --no-queue         Do not wait for other saynow processes. Speech from
                         concurrent calls may overlap.
      --strict           Exit non-zero when the chosen provider is unusable,
                         instead of falling back to the system voice.
  -q, --quiet            Suppress warnings on stderr. Exit codes unaffected.
  -h, --help             Show this text
      --version          Print the version and exit

PROVIDERS
  id           credential            quality  notes
  system       none                  fair     offline, built into the OS
  openai       OPENAI_API_KEY        good     dedicated speech endpoint
  elevenlabs   ELEVENLABS_API_KEY    best     most natural, highest cost
  openrouter   OPENROUTER_API_KEY    best     15+ speech models from Google,
                                              xAI, Deepgram, MiniMax and
                                              others behind one key

CONFIGURATION
  File: {{CONFIG_PATH}}, created with mode 0600.
  Precedence, lowest to highest:
    built-in defaults < config file < environment variables < flags

  Settings, for use with config set:
    provider           system | openai | elevenlabs | openrouter
    voice              voice name, see saynow voices
    model              model id, see saynow models
    speed              number between 0.25 and 4.0
    historyLimit       clips to keep in the archive, 0 disables it
    openaiApiKey       secret, redacted by config list
    elevenlabsApiKey   secret, redacted by config list
    openrouterApiKey   secret, redacted by config list

  Environment variables:
    SAYNOW_PROVIDER    same values as --provider
    SAYNOW_VOICE       same values as --voice
    SAYNOW_MODEL       same values as --model
    SAYNOW_SPEED       same values as --speed
    SAYNOW_CONFIG_DIR  override the config directory
    SAYNOW_LOCK_PATH   override the playback lock file, giving this process
                       its own queue instead of the machine-wide one
    SAYNOW_HISTORY_DIR override where archived clips are written
    OPENAI_API_KEY, ELEVENLABS_API_KEY, OPENROUTER_API_KEY

  API keys are deliberately not accepted as flags: argv is visible to ps and
  is saved in shell history. Use saynow init, config set, or the env var.

BEHAVIOUR
  Fallback   If a cloud provider is selected but no credential is found,
             saynow warns on stderr, speaks with the system voice, and exits
             0. An agent reporting success to someone who heard nothing is
             worse than a robotic voice. Use --strict to make it an error.
  Queueing   Concurrent saynow processes serialise through a lock file, so
             three calls produce three sentences rather than one muddle. The
             queue is machine-wide and shared by the npm and pip builds, so
             agents in different projects still take turns. A lock whose
             owner has died, or which is older than five minutes, is
             reclaimed automatically.
  Stdin      With no text arguments and stdin not a terminal, saynow reads
             the text from stdin.
  Literals   To speak a word that is also a command name, pipe it instead:
             echo "config" | saynow

THE BUBBLE
  Whenever saynow speaks it also shows a small bubble in the bottom-right
  corner carrying the transcript, lit word by word, with a stop button and a
  dismiss button. It fades out shortly after the audio ends, so a sentence you
  half-heard is still readable. Pass --no-ui to suppress it, or --quiet to
  keep it while silencing stderr.

  On macOS it is a floating panel: no dock icon, no entry in the app switcher,
  and it does not steal focus from what you are doing. Elsewhere it falls back
  to a Chromium app window, which looks the same but has a title bar. With
  neither available, saynow simply speaks.

ASKING
  saynow --ask "question" adds a reply box to that bubble and waits.

  The reply is written to stdout, so a caller can read it directly:

    answer=$(saynow --ask "Should I drop the old table?")

  It waits indefinitely once you interact with it. If you do not, it fades out
  five seconds after the audio ends and exits 2, which lets a caller tell
  "they said no" apart from "nobody was there".

EXIT CODES
  0   spoke successfully, wrote the file when --save was given, or got a reply
  1   usage error, unusable provider under --strict, network or API failure,
      or no audio player found on the system
  2   --ask was dismissed or timed out without a reply

HISTORY
  Every cloud synthesis is archived, so converting a long article once means
  you can replay or share it without paying for it again. The newest 50 clips
  are kept; change that with: saynow config set historyLimit <n>, or set it to
  0 to archive nothing.

    saynow history            list clips, newest first, with what each cost
    saynow history open 3     play the third
    saynow history path       print the directory, to share a file from it

  Prices come from OpenRouter and are looked up when you run the listing, not
  during synthesis, so speaking is never delayed to learn what it cost.

  The system voice is not archived. It speaks directly and never produces a
  file, so there is nothing to keep. Clip size varies a lot by model: some
  return mp3, others return raw audio that saynow wraps as WAV, which is
  roughly ten times larger.

LIMITATIONS
  Voice names are vendor-specific and OpenRouter rejects a request without
  one. saynow ships verified voice lists for 8 of the 15 speech models; for
  the rest, pass --voice explicitly. Run: saynow voices -p openrouter -m <id>
  Only OpenRouter reports a price, so history costs are blank for the other
  providers.
  The system provider cannot write files, so it rejects --save.
  Windows support is best-effort and less tested than macOS and Linux.
  --ask needs the npm build. The pip build speaks the text and then exits 2,
  because the bubble is not yet ported to it.
  The transcript is lit at an estimated pace rather than in true sync with
  the audio, so the highlight can drift on unusually long or short text.

EXAMPLES
  saynow "the build finished"
  saynow -p openai -v nova "tests passed, 42 of 42"
  saynow -p openrouter -v Kore "a different Gemini voice"
  saynow -p openrouter -m deepgram/aura-2 -v aura-2-zeus-en "another vendor"
  saynow -r 320 "read this quickly"
  saynow --save note.mp3 "write to a file instead of playing"
  saynow --strict -p elevenlabs "fail loudly if the key is missing"
  npm test 2>&1 | tail -1 | saynow
  saynow config set provider openai

FOR AGENTS
  Call saynow when a human needs interrupting: a long task finished, a
  question blocks progress, or an error needs attention. Keep it to one short
  sentence, since it is heard rather than read. Do not narrate routine
  progress. Prefer the default provider unless the user asked for a specific
  voice or quality level.
