ListenHubSDKs & CLI
CLI

CLI Examples

Copy-pasteable shell recipes for one-shot generation, batch loops, audio export, and CI pipelines with the listenhub CLI.

A cookbook of real shell recipes. Each one runs as-is once you have the CLI installed and authenticated. Most examples use the API-key (listenhub openapi) namespace because it's the right fit for scripts and CI — but recipes that depend on local-file upload (cover audio, image references) use the OAuth namespace where that behavior lives, and each one says which auth mode it needs.

Before you start:

  • Install: npm install -g @marswave/listenhub-cli (Node.js >= 20).
  • For the openapi recipes, set LISTENHUB_API_KEY (the CLI reads it first) or run listenhub openapi config set-key. See Authentication.
  • For the OAuth recipes, run listenhub auth login once.

--json / -j prints machine-readable output to stdout; errors and the progress spinner go to stderr. That separation is what makes piping into jq safe. Exit codes: 0 ok, 1 error, 2 auth, 3 timeout — branch on $? in scripts.

1. One-shot podcast

Submit a topic and block until the episode is ready. By default the command polls every 10 seconds up to the --timeout (default 300), then prints the finished episode.

export LISTENHUB_API_KEY="lh_sk_..."

# Pick a voice once, copy its ID
listenhub openapi speakers list --language en

listenhub openapi podcast create \
  --query "AI agent trends in 2026" \
  --speaker-id <speaker-id> \
  --mode quick

--speaker-id is required and repeatable — pass it twice for a two-host episode. Ground the content with --source-url or --source-text (both repeatable) instead of, or in addition to, --query.

2. Batch generation in a bash loop

Read topics from a file, fire each one with --no-wait (return the ID immediately), capture the episodeId with jq, then poll the IDs in a second pass. This keeps submission fast and decouples it from the slow generation step.

#!/usr/bin/env bash
set -euo pipefail

SPEAKER_ID="<speaker-id>"
ids=()

# Submit every topic, collect episode IDs
while IFS= read -r topic; do
  [ -z "$topic" ] && continue
  id=$(listenhub openapi podcast create \
        --query "$topic" \
        --speaker-id "$SPEAKER_ID" \
        --mode quick \
        --no-wait --json | jq -r '.episodeId')
  echo "submitted: $topic -> $id"
  ids+=("$id")
done < topics.txt

# Poll each episode until it leaves the running state
for id in "${ids[@]}"; do
  while :; do
    status=$(listenhub openapi podcast get "$id" --json | jq -r '.processStatus')
    case "$status" in
      success) echo "$id done"; break ;;
      failed)  echo "$id failed" >&2; break ;;
      *)       sleep 10 ;;
    esac
  done
done

processStatus is success or failed at a terminal state; anything else means still running. topics.txt is one topic per line.

3. Text-to-speech to an audio file

openapi tts streams binary audio straight to disk — no polling, no --json. Use openapi speakers list to find a voice ID, then write an MP3:

VOICE=$(listenhub openapi speakers list --language en --json | jq -r '.[0].speakerId')

listenhub openapi tts \
  --text "Hello from ListenHub" \
  --voice "$VOICE" \
  --output hello.mp3
# ✓ Audio saved: /abs/path/hello.mp3 (42.3KB)

--format accepts mp3 (default), opus, aac, flac, wav, or pcm. For an audio URL instead of a local file (returns JSON with audioUrl), use openapi speech --script "..." --speaker-id "$VOICE" -j.

4. Music from a reference track

Generating a cover from an existing track needs local-file upload, which lives in the OAuth namespace. listenhub music cover auto-detects a local path, validates it, uploads it to cloud storage, then submits the job and polls.

# OAuth: run `listenhub auth login` first
listenhub music cover \
  --audio ./original.mp3 \
  --style "lo-fi" \
  --title "My Cover" \
  --json

--audio also accepts a URL, which is passed through without uploading. For a fully API-key-driven flow that takes a reference audio, use listenhub openapi music instrumental --reference-audio ./clip.mp3 (model default applies) or listenhub openapi music soundtrack --image ./cover.png --prompt "..." to score an image.

5. AI image with a local reference

openapi image create reads a local reference file, base64-encodes it, and sends it inline. --reference is repeatable and also accepts URLs (passed through by URI). --provider is required.

listenhub openapi image create \
  --prompt "a dragon in watercolor style, inspired by this sketch" \
  --provider google \
  --reference ./sketch.png \
  --ratio 16:9 \
  --json | jq -r '.imageUrl // .'

Supported reference formats: .png, .jpg/.jpeg, .gif, .webp, .bmp. Optional --size is 1K, 2K, or 4K; --ratio is one of 16:9, 4:3, 1:1, 3:4, 9:16, 21:9.

6. PixVerse text-to-video

PixVerse runs through openapi video pixverse generate. --capability is required; text_to_video is the simplest case. --language en (default) hits the international service, --language zh the China service.

# Estimate credits first (never assume a fixed cost)
listenhub openapi video pixverse estimate \
  --capability text_to_video --quality 720p --duration 5

# Submit and return the task ID without waiting
listenhub openapi video pixverse generate \
  --capability text_to_video \
  --prompt "A cat playing piano on a neon stage" \
  --quality 720p \
  --aspect-ratio 16:9 \
  --duration 5 \
  --no-wait --json

--quality is 360p, 540p, 720p (default), or 1080p; --duration is an integer from 1 to 60 (default 5). Image, video, and audio assets accept an optional trailing :<seconds> duration suffix (for example --image https://example.com/p.jpg:3).

7. CI pipeline: submit, then poll separately

In CI you usually don't want a single command holding a connection open for minutes. Submit with --no-wait, persist the ID as a job output, and poll in a later step (or a later run). This recipe also shows branching on the exit code.

#!/usr/bin/env bash
set -euo pipefail
# LISTENHUB_API_KEY is injected from CI secrets

# --- step: submit ---
EPISODE_ID=$(listenhub openapi podcast create \
  --source-url "https://example.com/release-notes" \
  --speaker-id "$LH_SPEAKER_ID" \
  --no-wait --json | jq -r '.episodeId')
echo "episode_id=$EPISODE_ID" >> "$GITHUB_OUTPUT"

# --- step: poll with a hard cap ---
deadline=$(( $(date +%s) + 900 ))   # 15 minutes
while :; do
  status=$(listenhub openapi podcast get "$EPISODE_ID" --json | jq -r '.processStatus')
  [ "$status" = "success" ] && break
  if [ "$status" = "failed" ]; then
    echo "generation failed" >&2
    exit 1
  fi
  if [ "$(date +%s)" -ge "$deadline" ]; then
    echo "timed out waiting for $EPISODE_ID" >&2
    exit 3
  fi
  sleep 10
done

# --- step: fetch the audio URL ---
listenhub openapi podcast get "$EPISODE_ID" --json | jq -r '.audioUrl'

If you let a single creation command poll in CI (no --no-wait), it exits 3 when --timeout is reached — but the job keeps running server-side. Always capture the ID so a later step can recover it. Treat exit 2 as a credentials problem: rotate or re-set LISTENHUB_API_KEY.

8. Check credits before a batch

Generation spends credits. Read your balance, and estimate the cost of the work, before kicking off a large run.

# Remaining credits and plan
listenhub openapi subscription --json | jq '{credits: .totalAvailableCredits, plan: .subscriptionPlan.name}'

# Estimate one video task before multiplying it across a batch
listenhub openapi video estimate \
  --model doubao-seedance-2-pro \
  --resolution 1080p \
  --duration 10 \
  --json | jq '{credits, tokens}'

Every product with a metered cost exposes an estimate command (openapi video estimate, openapi video pixverse estimate). Query it rather than hard-coding a number — costs change with model, resolution, and duration.

Next steps

On this page