sweetrpg is a command-line client for the SweetRPG platform: one authenticated session usable
against every service. sweetrpg catalog covers add/edit/view/delete/link for catalog entities
(volume, publisher, studio, person, system, license, review, contribution);
sweetrpg api is a generic authenticated request passthrough for any configured service.
go install github.com/sweetrpg/sweetrpg-cli/cmd/sweetrpg@latestEach service's base URL resolves in this order:
--api-urlflag (catalog only today) - a full URLSWEETRPG_<SERVICE>_API_URLenvironment variable (e.g.SWEETRPG_CATALOG_API_URL,SWEETRPG_GAME_ROOM_API_URL) - also a full URL~/.config/sweetrpg/cli.yaml'sbaseURLplus aservices.<service>path
A service's path defaults to /api/0/<service> (/assets for assetsWeb) when
services.<service> isn't set, so the config file only needs a services entry for a service
whose path deviates from that convention:
baseURL: https://dev.sweetrpg.comis enough on its own; the longer form overrides specific paths:
baseURL: https://dev.sweetrpg.com
services:
catalog: /api/0/catalog
gameRoom: /api/0/game-room
assetsWeb: /assetsA service that lives on a different host entirely (a local port during dev, say) skips baseURL
for that one service via its env var, which always takes a full URL and overrides the config
file's path-under-baseURL resolution.
Commands that write require a login. A release build ships with its Auth0 tenant baked in, but that's a default, not a hardcode - it resolves in this order:
SWEETRPG_AUTH_DOMAIN/SWEETRPG_AUTH_CLIENT_ID/SWEETRPG_AUTH_AUDIENCEenvironment variables~/.config/sweetrpg/cli.yaml'sauthTenantsection- The values baked in via
-ldflagsat release time
For dev runs against plain go run (nothing baked in), set one of the first two:
export SWEETRPG_AUTH_DOMAIN=dev-xxxx.us.auth0.com
export SWEETRPG_AUTH_CLIENT_ID=...
export SWEETRPG_AUTH_AUDIENCE=https://catalog-apior in the config file:
authTenant:
domain: dev-xxxx.us.auth0.com
clientId: ...
audience: https://catalog-apiRun once per machine:
sweetrpg auth loginThis opens the Auth0 device-flow login (visit the printed URL and enter the code). The session is
shared across every command namespace (catalog, api, game-room, ...) - one login covers all
of them. Tokens are stored in your OS keychain under service name sweetrpg-cli; access tokens
refresh automatically. auth logout removes them. Auth failures exit with code 3.
Reads don't require a login: catalog view (and name resolution it performs) hits public
endpoints and works with no stored session. Writes (add, edit, delete, link, unlink)
require one.
Entity commands share one shape; <type> is one of the entity types above:
sweetrpg catalog add <type> <name> [property flags]
sweetrpg catalog edit <type> <name-or-id> [property flags]
sweetrpg catalog view <type> <name-or-id> [--json | --yaml]
sweetrpg catalog delete <type> <name-or-id>Name arguments match case-insensitively and partially (exact matches win when both kinds
hit); 24-hex IDs are used as-is. When a name matches several records an interactive picker
lists each candidate's ID, or (with --yes) the command fails and prints the candidates.
catalog view volume prints a viewable coverURL alongside a volume's own fields when it has a
cover and assets-web-url is configured; --json/--yaml stay the server's raw representation.
To see what a fuzzy query will hit before resolving, use search:
sweetrpg catalog search <type> <query> # prints "ID<TAB>name" per hitLinks connect two entities in either argument order:
sweetrpg catalog link volume "Dungeon World" publisher "Evil Hat Productions"
sweetrpg catalog link person "John Wick" volume 507f1f77bcf86cd799439011 --role artist
sweetrpg catalog unlink volume "Dungeon World" person "John Wick"Linkable pairs: volume-publisher, volume-studio, volume-system, volume-person. Person links to
volumes create or update contribution credits (--role, default author). Relinking an
existing pair is idempotent.
Volumes also support staged-asset upload for covers:
sweetrpg catalog edit volume "Dungeon World" --cover ./dw-cover.png--cover accepts png, jpeg, or webp files and can be combined with property
flags. Uploads require a session and an assets-web base URL (--assets-web-url flag,
SWEETRPG_ASSETS_WEB_URL env var, or services.assetsWeb in the config file); they talk to
assets-web directly, so a --curl run previews the linking PATCH but not the upload itself.
sweetrpg dtrpg login/sweetrpg dtrpg logout manage one DriveThruRPG application key, shared by
every command that imports from your DriveThruRPG library (catalog import dtrpg library,
game-room import dtrpg). It's one external account either way, so there's one login:
sweetrpg dtrpg login # paste a key from your DTRPG account settings
sweetrpg dtrpg login --credentials # or enter email + password to mint oneThe key is kept in the OS keychain under service sweetrpg-cli, account
dtrpg-app-key - separate from the platform session. It is exchanged for a short-lived session
on every run; the session token is never written to disk. Passwords are read at a masked prompt
and discarded after the exchange. sweetrpg dtrpg logout deletes the stored key.
catalog import dtrpg library bulk-loads the volumes in your DriveThruRPG library into the
catalog. It drives the same POST /volumes and POST /publishers endpoints as catalog add, so
imported records land as submitted versions for normal review.
Run the import (requires both a platform login and sweetrpg dtrpg login):
sweetrpg catalog import dtrpg library --dry-run # show the plan, write nothing
sweetrpg catalog import dtrpg library # create volumes and publishersEach product maps to a volume: title, short description, and category filters as tags. The
DriveThruRPG product ID and ISBN (when present) are stored as dtrpg_* properties - purchase
date and order ID are not, since they're personal-order facts rather than catalog data. The
product's cover image is downloaded and stored as the volume's own cover asset, not referenced
by URL. Publisher names resolve case-insensitively to existing publisher records, creating one
on a miss. Re-runs are idempotent - a product whose dtrpg_product_id already appears on a
volume is skipped.
catalog import dtrpg library is meant to be run by an admin or editor: created volumes,
publishers, and cover links land as live records, not review-queue submissions - a bulk
import can create hundreds or thousands of records, and routing all of that through review would
make the queue unusable. There's no separate "publish immediately" flag; it follows from the
caller's role the same way POST /publishers and PATCH /volumes already do. A submitter-role
token still works for the writes that support it, but expect it to behave differently than
documented here.
Flags:
--dry-run- fetch the library and print the plan (to import / already imported / skipped) without any write.--include-archived- also import products whose DriveThruRPG files are archived (skipped by default).--page-size- DriveThruRPG retrieval page size;0uses the server default.
A per-product failure is isolated: the run continues, the failure is listed in the summary, and
the command exits 1. Missing platform session exits 3; missing DriveThruRPG key exits 1
with a pointer to sweetrpg dtrpg login.
game-room import dtrpg matches your DriveThruRPG library against volumes already in the
SweetRPG catalog and adds every match to your own Game Room library. It never creates a catalog
record - a product with no matching catalog volume is skipped and reported, not imported. Use
catalog import dtrpg library (an admin/editor tool, see above) to populate the shared catalog
itself first. Uses the same DriveThruRPG login as the catalog import - run sweetrpg dtrpg login
once and both commands can use it.
Run the match-and-add:
sweetrpg game-room import dtrpg --dry-run # show what would be added, write nothing
sweetrpg game-room import dtrpg # add every matched volume to your libraryMatching is by the dtrpg_product_id property the catalog import records on each volume. The
completion summary reports counts of products added, already in your library, and skipped
because no catalog volume matches yet (with their titles), so you understand why your full
DriveThruRPG library may not fully populate your Game Room library. game-room import dtrpg logout deletes the stored key.
For endpoints the typed catalog commands don't cover, api sends an authenticated request
against any configured service, in the spirit of gh api:
sweetrpg api GET /volumes/123 --service catalog
sweetrpg api POST /publishers --service catalog --field name="Evil Hat Productions"
sweetrpg api GET /users/me --service users -H "X-Request-Id: abc123"--field key=value type-sniffs the value (true/false/numeric encode as their JSON type,
everything else as a string); --raw-field key=value always encodes a string. The method
defaults to GET with no body, POST when --field/--raw-field is present. Combine with
--curl to preview the request instead of sending it.
- Pass
--yesto skip all interactive prompts; ambiguous name resolutions then fail instead of prompting. Deletes additionally require--forcewhen stdin is not a TTY ---yesalone never deletes in a script. - Use
catalog view <type> <id> --jsonfor machine-readable output. - Pass
--curlto print the equivalent cURL command(s) instead of calling the API. Nothing is sent; the bearer token is printed as<redacted>. Flows that need server data to continue (name resolution feeding later requests) stop after their first request, so pass IDs instead of names to see write requests directly. - Exit codes:
0success,1general error,2usage error,3authentication failure.
source <(sweetrpg completion bash) # add to .bashrc
source <(sweetrpg completion zsh) # add to .zshrc
sweetrpg completion fish | source
sweetrpg completion powershellSee RELEASE.md for how versions get cut and CONTRIBUTING.md for the development workflow.