@jackmorgan/phaser-cli
v0.1.0
Published
CLI for the Phaser API — browse catalog, manage keys, upload tracks
Readme
@jackmorgan/phaser-cli
Command-line interface for the Phaser API. Written in Go, distributed on npm.
Install
npm install -g @jackmorgan/phaser-cliPostinstall pulls the prebuilt binary from the matching GitHub release (cli-v<version>). Supported: darwin/arm64, darwin/amd64, linux/arm64, linux/amd64, windows/arm64, windows/amd64. Set PHASER_CLI_SKIP_INSTALL=1 to skip.
Quick start
# Read-only (API key)
phaser auth login # paste an existing key
phaser catalog tracks --limit 10
# Artist flow (SIWE JWT → mint key → upload)
phaser auth siwe # signs an EIP-4361 challenge, saves JWT
phaser artists init --name "My Band" --slug my-band
phaser keys create --name cli --save # mints and saves API key
phaser tracks upload song.mp3 --title "Track 1" --price 0.05 --cover cover.jpg --publishConfig lives at ~/.phaser/config.json (override with PHASER_CONFIG_HOME). Env overrides: PHASER_API_KEY, PHASER_API_URL, PHASER_PRIVATE_KEY.
Authentication
Two credential types, used for different endpoints:
| Credential | Where from | Used by |
| ----------- | -------------------------------------- | ----------------------------------------------- |
| API key | auth login / keys create | Catalog reads (catalog *, albums get) |
| JWT | auth siwe (wallet signature, 1-hour) | Mutations: keys, tracks, albums, artists init |
The CLI picks the right credential per command automatically.
SIWE login
phaser auth siwe does the full EIP-4361 handshake:
- derives your address from your private key locally,
- fetches
/v1/auth/challenge, - EIP-191 signs the SIWE message with
secp256k1(pure-Go, no cgo), - posts the signature to
/v1/auth/verify, - stores the returned JWT in config.
Private key is resolved in this order: --private-key flag → --private-key-file <path> → $PHASER_PRIVATE_KEY → interactive prompt (masked). The key never leaves your machine.
Signatures are byte-identical to
viem.signMessage— seeinternal/siwe/siwe_test.go.
Commands
Browse (API key)
| Command | Summary |
| -------------------------------- | ---------------------------------------------- |
| phaser health | Check API health |
| phaser catalog tracks | List tracks (--q, --genre, --sort, --cursor, --limit, --artist) |
| phaser catalog track <id> | Get a single track |
| phaser catalog search <q> | Cross-search tracks/artists/albums |
| phaser catalog genres | List genres |
| phaser catalog artists | List artists |
| phaser albums get <id> | Get an album with its track list |
Auth + config
| Command | Summary |
| ------------------------ | --------------------------------------- |
| phaser auth login | Save an existing API key |
| phaser auth siwe | Sign in with Ethereum → JWT |
| phaser auth logout | Clear saved credentials |
| phaser auth status | Show which creds are active |
| phaser config show | Print config (secrets redacted) |
| phaser config path | Print config file path |
API keys (JWT)
| Command | Summary |
| -------------------------- | ---------------------------------------------------- |
| phaser keys list | List your keys (prefix + status) |
| phaser keys create | Mint a key (--name, --save writes to config) |
| phaser keys revoke <id> | Revoke by ID |
Artist profile + albums (JWT)
| Command | Summary |
| ------------------------------------------- | --------------------------------------------- |
| phaser artists init --name X --slug x | Create your artist profile (one per wallet) |
| phaser albums create --title X | Create an album |
| phaser albums update <id> --title Y | Update album metadata |
Storage + analytics (JWT)
| Command | Summary |
| ----------------------------- | ------------------------------------------------------------- |
| phaser storage balance | Credit, monthly burn, months of runway |
| phaser storage invoices | Ledger of topups/charges/refunds (--cursor, --limit) |
| phaser analytics | Streams, revenue, per-track table, last-30-days daily totals |
Tracks (JWT)
Simple path — single-shot multipart, ≤100 MB:
phaser tracks upload song.mp3 \
--title "Track 1" \
--genre synthwave \
--price 0.05 \
--cover art.jpg \
--album-id <uuid> \
--track-number 1 \
--release-date 2026-04-20 \
--publishAdvanced — presigned direct-to-S3 (for large files):
# Option A: let the CLI PUT the files for you
phaser tracks presign --title "Track 2" --audio big.flac --cover cover.jpg --upload
phaser tracks finalize --track-id <returned-id> --publish
# Option B: just get the URLs and PUT them yourself
phaser tracks presign --title "Track 2" --audio-content-type audio/flac -o json
# …curl -X PUT --data-binary @big.flac -H "Content-Type: audio/flac" "<upload_url>"
phaser tracks finalize --track-id <returned-id> --source-audio-key <key>Other mutations:
| Command | Summary |
| ---------------------------------- | ----------------------------------------------- |
| phaser tracks update <id> | Update title/genre/price/album/etc. |
| phaser tracks publish <id> | Publish a finalized draft |
| phaser tracks delete <id> --yes | Archive (soft-delete) |
All commands support -o json. Use --help on any subcommand for flag details.
Shell completion
Cobra generates the scripts — run phaser completion <shell> --help for the per-shell install steps. Quick hits:
# zsh (one-shot this session)
source <(phaser completion zsh)
# bash (persistent)
phaser completion bash | sudo tee /etc/bash_completion.d/phaser
# fish (persistent)
phaser completion fish > ~/.config/fish/completions/phaser.fishError messages
Validation failures from the server include a details array (Zod issues). The CLI pretty-prints them:
Error: http 400: Validation error
- title: String must contain at least 1 character(s)
- stream_price_usdc: Expected string, received number (expected string, got number)Use -o json on any command to see the full raw error payload.
Upload progress
tracks upload and tracks presign --upload print a live progress line to stderr when stdout/stderr is a TTY:
uploading 47.32% 47.32 MB / 100.00 MB 8.12 MB/sNon-TTY environments (CI, piped output) stay silent to keep logs clean.
Development
go build -o bin/phaser ./cmd/phaser # local build
go test ./... # unit tests (SIWE signing vs viem)
bash scripts/build.sh # cross-compile all 6 targetsAdding a new endpoint
- Add the typed method + response struct in
internal/client/<resource>.go(one file per resource). - Add a command file under
internal/commands/<resource>.go. - Register the top-level command in
internal/commands/root.go.
The client.do method (JSON) and client.doMultipart (file uploads) are the single paths all requests flow through — retries, logging, tracing go there.
Release
Bump the version, tag, push:
# From packages/cli
npm version patch # e.g. 0.1.1
git commit -am "cli: v0.1.1"
git tag cli-v$(node -p "require('./package.json').version")
git push && git push --tags.github/workflows/cli-release.yml cross-compiles, uploads gzipped binaries to the release, and publishes the npm package. Requires the NPM_TOKEN secret.
Security notes
- Private keys are used locally to sign one SIWE challenge and are never transmitted. Still, prefer a dedicated wallet — don't use high-value keys.
- Config file is written with
0600perms; keep~/.phaser/out of backups that may expose it. - API keys are shown once at mint time —
keys listonly ever returns prefixes.
