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
  app install            Build and install the macOS settings app
  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.
      --from <name>      Who is speaking. Shown in the bubble header, so the
                         sentence itself does not have to introduce the agent.
      --file <path>      Show a Markdown file in the bubble. Speaks the whole
                         document unless a text argument is also given, in
                         which case that is what is read aloud. See DOCUMENTS.
  -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 DOCUMENTS
  saynow --file report.md shows a rendered Markdown document in the bubble:
  headings, lists, tables, quotes, code, links and images.

  Speech and display are deliberately not the same thing. An image cannot be
  spoken and a URL read aloud is noise, so the spoken version keeps the prose
  and drops the rest. Give a text argument as well and that becomes the only
  thing said, which is usually what you want for a long report:

    saynow --file news.md "Morning summary is ready"

  Hover, scroll or type to hold the bubble open while you read it.

  Markdown in the file is escaped before rendering and only tags saynow
  builds itself are emitted, so a document cannot script the bubble. Links
  are limited to http, https and mailto; images to http, https and inline
  data. Relative image paths are resolved against the file's own directory
  and refused if they climb out of it.

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 SETTINGS APP
  macOS only. A small window for the things a flag is a clumsy way to set:
  picking a model from the live catalogue with prices, storing keys, and
  browsing, playing and pricing what you have already synthesised.

  Installing saynow on macOS installs the app too, so there is nothing else
  to run. It compiles in the background after the install returns, so the
  install itself never waits for it, and appears a few seconds later. To opt
  out, install with SAYNOW_NO_APP=1 set; to add it later, or after an install
  that skipped scripts:

    saynow app install     build it and put it in /Applications

  It compiles from source and needs the Xcode command line tools
  (xcode-select --install); without them the install skips it and says so. It
  ships as source rather than a binary because a prebuilt app needs a
  Developer ID and notarisation to run on anyone else's machine. It is a
  separate process from this tool and they meet only at the config file, so
  either works without the other.

THE BUBBLE
  Whenever saynow speaks it also shows a small bubble in the bottom-right
  corner carrying the transcript, lit word by word in time with the audio,
  with a stop button and a dismiss button. --from names the sender in its
  header, which is worth using when several agents share a machine: the
  identity is then something you read rather than something you sit through.

  Long text is rendered a sentence at a time so speech starts while the rest
  is still being made — a few seconds rather than the twenty a whole article
  takes to synthesise. It stays one bubble showing the full transcript; the
  split is a synthesis detail, not something you see. 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.

  It stays while you are with it — hovering, scrolling or typing holds it
  open, and the countdown resumes a moment after you leave. Anything typed
  keeps it until you send or dismiss it.

  The bubble talks to saynow over a loopback server that is authenticated per
  message, so nothing else on the machine can answer a question on your behalf.

  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.

DOCUMENTS
  saynow --file report.md shows a rendered Markdown document in the bubble:
  headings, lists, tables, quotes, code, links and images.

  Speech and display are deliberately not the same thing. An image cannot be
  spoken and a URL read aloud is noise, so the spoken version keeps the prose
  and drops the rest. Give a text argument as well and that becomes the only
  thing said, which is usually what you want for a long report:

    saynow --file news.md "Morning summary is ready"

  Hover, scroll or type to hold the bubble open while you read it.

  Markdown in the file is escaped before rendering and only tags saynow
  builds itself are emitted, so a document cannot script the bubble. Links
  are limited to http, https and mailto; images to http, https and inline
  data. Relative image paths are resolved against the file's own directory
  and refused if they climb out of it.

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, so saynow reads each model's published voices from the API and caches
  them for a day. Two models publish none; for those, pass --voice and see
  what the vendor accepts. 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.
  The bubble needs a window shell: Xcode command line tools on macOS, or a
  Chromium-based browser anywhere. Without one saynow still speaks, but --ask
  exits 2 immediately and --file is read aloud rather than shown.
  Long text is split so speech starts sooner in the npm build only; the pip
  build synthesises the whole passage before the first word.

EXAMPLES
  saynow "the build finished"
  saynow -p openai -v nova "tests passed, 42 of 42"
  saynow --from "billing agent" --ask "drop the old table?"
  saynow --file report.md "the weekly report is up"
  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.
