ListenHubDocs
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

Seedance 2.5

The following additions apply to doubao-seedance-2-5 only. Legacy flag limits below remain unchanged for other models. --prompt is required for ordinary/draft creation and omitted for --draft-task-id.

Use a CLI version whose video create --help includes --draft-task-id. These examples apply to both listenhub video and listenhub openapi video; both accept local image, video, and audio files or URLs. The CLI uploads local files before submitting their URLs to the API. Remote Seedance URLs still require the corresponding metadata.

CLI metadata is repeatable and follows the same order as its assets. Video: --reference-video-meta WIDTHxHEIGHT:DURATION[:FPS[:SIZE]]; audio: --reference-audio-meta DURATION[:SIZE]; image: --reference-image-meta WIDTHxHEIGHT[:SIZE]. Frame metadata uses --first-frame-meta and --last-frame-meta. Use actual file metadata. The estimator accepts the same metadata flags without asset URLs.

Use --omni-reference-task-type auto|reference|edit|extend, --draft, --draft-task-id, and --request-id. Draft requires --resolution 480p; edit requires --ratio adaptive --duration -1; extend and frame modes require --ratio adaptive. Final requests accept model, draft task ID, and a stable request ID; omit prompt, assets, and generation settings. Final estimates need neither resolution nor duration.

Draft and final are separate tasks and separate charges. A final does not subtract the draft charge or treat the draft output as another reference video. A failed final refunds its own reserved charge and leaves the successful draft available.

Video estimates use the sum of input-video durations and the selected output geometry at 24 fps, subject to minimum-token rules. Native input dimensions validate each file; they are not independently priced by pixel area. Audio-only requests use the no-video rate; audio seconds are not added to video seconds. Automatic parameters use a conservative estimate, and completion settles against actual provider token usage. The estimate is not a guaranteed maximum.

Full limits and runnable request shapes.

listenhub openapi video create --model doubao-seedance-2-5 \
  --prompt 'A paper boat in a luminous city' \
  --draft --resolution 480p --ratio 16:9 --duration 5 \
  --request-id 'draft-request-unique-id' --no-wait --json

listenhub openapi video get '<draft-task-id>' --json

listenhub openapi video estimate --model doubao-seedance-2-5 \
  --draft-task-id '<successful-draft-task-id>' --json

listenhub openapi video create --model doubao-seedance-2-5 \
  --draft-task-id '<successful-draft-task-id>' \
  --request-id 'final-request-unique-id' --no-wait --json

On this page