Agents & Automation
spogo is built to be driven by something other than a human at a terminal — shell scripts, cron jobs, CI pipelines, AI coding agents. The same properties that make it pleasant interactively (stable JSON, plain mode, predictable exit codes, stderr-only logs) also make it safe to script.
#The contract
A script-friendly CLI must guarantee:
- Stable output. spogo's
--jsonand--plainmodes don't change keys/columns between releases without a major bump. - Stable exit codes.
0success,1generic,2usage,3auth,4network. Branch on these. - stdout is data. stderr carries everything else. Pipes always work.
- No interactive surprises. When stdin is not a TTY, spogo refuses to prompt. Pass
--no-inputto be explicit. - Fewer public-API requests. Connect uses internal endpoints for supported reads and playback; library mutations, playlist creation, and other Web-API-only paths can still return
429with a retry hint.
See Output for the full contract.
#Wiring spogo into a shell script
#!/usr/bin/env bash
set -euo pipefail
# Make sure the configured cookie store exists
if ! spogo auth status >/dev/null 2>&1; then
echo "spogo: cookie inventory unavailable; re-run 'spogo auth import'" >&2
exit 3
fi
# For an OAuth-only Web API profile, inspect local OAuth state instead:
# spogo --engine web --auth oauth auth oauth status --json
# Capture the currently playing track ID
track_id=$(spogo status --json | jq -r '.item.id // empty')
if [[ -z "$track_id" ]]; then
echo "Nothing playing" >&2
exit 0
fi
# Save it
spogo library tracks add "$track_id"
Pin to a specific spogo version in CI to avoid silent JSON drift across releases.
#Common patterns
#Save the currently playing track
id=$(spogo status --json | jq -r '.item.id')
spogo library tracks add "$id"
#Build a playlist from a search
playlist_id=$(spogo playlist create "Weekly Lo-Fi" --json | jq -r .id)
spogo search track "lo-fi 2026" --limit 30 --json |
jq -r '.items[].uri' |
while IFS= read -r uri; do spogo playlist add "$playlist_id" "$uri"; done
#Snapshot a library page to JSON
spogo library tracks list --limit 50 --json > snapshots/tracks.$(date +%F).json
#Move playback to a specific room when leaving home
spogo device set "Phone"
#"Sleep timer" — pause after N minutes
sleep "${1:-1800}" && spogo pause
#Cron / launchd / systemd
spogo writes nothing to stdout that isn't useful and nothing to stderr unless something happened — perfect for cron tail logs.
# Snapshot the first page of liked tracks daily at 04:00
0 4 * * * /usr/local/bin/spogo library tracks list --limit 50 --json > "$HOME/snapshots/tracks-$(date +\%F).json"
For headless servers / CI runners, either copy a working cookie jar (from a machine where you ran auth import) into the runner's spogo config directory, or provision an OAuth token cache created by auth oauth login for --engine web --auth oauth. Both files are credentials. Do not print them or commit them.
#CI
For a macOS runner with Homebrew available, use an explicit config path and owner-only files:
- name: Install spogo
run: brew install steipete/tap/spogo
- name: Restore cookies
shell: bash
run: |
umask 077
mkdir -p "$RUNNER_TEMP/spogo/cookies"
printf '%s' "$SPOGO_COOKIES" > "$RUNNER_TEMP/spogo/cookies/default.json"
env:
SPOGO_COOKIES: ${{ secrets.SPOGO_COOKIES }}
- name: Snapshot first library page
run: spogo --config "$RUNNER_TEMP/spogo/config.toml" library tracks list --json --limit 50 > tracks.json
Listings are capped at 50 items. Fetch further pages with --offset; a larger limit does not download the whole library.
Treat the cookie jar like a credential — it's tied to your Spotify session.
#Coding agents
spogo is a good fit for AI coding agents (Claude Code, Codex, Cursor) because:
- Self-documenting.
spogo --helpandspogo <subcommand> --helpdescribe the entire surface. The Spec is short and stable. - Deterministic. Stable JSON keys mean the agent's parsing doesn't drift across releases.
- Safe-ish. The destructive surface is small (
library tracks remove,playlist remove,auth clear,auth oauth clear). Wrap those behind explicit confirmation in your agent prompt.
Recommended agent rules:
- Always pass
--jsonor--plainfor output the agent will parse. - Always pass
--no-inputso spogo cannot block on a prompt. - Branch on exit code, not stderr text.
- Pin spogo version in the agent's environment.
A starter system prompt fragment for an agent:
You can use the
spogoCLI to control Spotify. Always pass--jsonand--no-input. Readspogo --helpandspogo <cmd> --helpbefore invoking unfamiliar commands. Treat exit code3as "needs auth". Surface that to the user; do not launch an interactive cookie import or OAuth login automatically.
#Safety
spogo has no built-in command allowlist or read-only mode. If you're handing it to an unattended process or untrusted agent and want to restrict it:
- Run inside a separate spogo profile (
SPOGO_PROFILE=automation) with cookies for an account that has limited permissions. - Wrap spogo in a thin shell script that whitelists subcommands.
- Do not use engine selection as an access-control boundary: explicit
applescriptcan delegate library and playlist operations to remote APIs.
#Debugging an automation
-v and -d currently do not add tracing. Capture the command error on stderr and include the version and selected engine when reporting a failure:
spogo --version
spogo status --json 2>spogo.log | jq .
See Troubleshooting.