tmc-cli - The Modding Community

Open Source

tmc-cli is a command-line client for our public content API. It creates, edits and deletes your assets, mods, servers, articles, communities, collections and groups, and, because that is what the API is actually used for, it uploads files and cuts releases in one command.

Python 3.11+Zero dependenciesMIT licensed

Install it from a clone. tmc-cli is not on PyPI yet, so pip installs it from the public repository. The three steps are further down.

$ tmc auth whoamiprofile    defaultsite       https://moddingcommunity.comauthMode   tokentoken      tmc_ab…9fcanRead    truecanWrite   truecanDelete  false

A single standard-library package, so there is nothing to build and nothing to resolve. It is not on PyPI, so it installs from a clone. Every command, flag and exit code is written up in the docs.

Install

Three Steps, No Package Index

tmc-cli is not on PyPI. It installs from a clone of the public repository, needs Python 3.11 or newer and pulls in no other packages.

  1. 1

    Clone it

    Get the source. Nothing is built or compiled.

    git clone https://github.com/modcommunity/tmc-cli.git && cd tmc-cli
  2. 2

    Install it

    pip puts the tmc command on your PATH. The extra is optional: with cryptography installed, signing is faster. The package is tmc-cli, but the command you type is tmc.

    pip install .
  3. 3

    Sign in

    Make a key under Account, API Keys, then log in. Or skip the key and read public items with --anon.

    tmc auth login --token tmc_xxxxxxxxxxxx
Commands

One Tool, Six Corners Of The API

Command names are exactly what is typed. Run tmc --help for the full list.

Content

List, get, create, update and delete on every content type.

assetmodservercommunityarticlecollectiongroup

Around an item

Tags, media, links, relations, comments and reviews, each managed without re-sending the item.

tagtagsmedialinksrelcommentreview

Files and releases

Upload files, cut a release and attach them in one command.

filerelease

Your account

Favourites, friends, parties, installs and subscriptions.

authaccountfavoritefriendspartyinstallssubscriptions

The site

The network monitor's status, stats, filters, the catalogue, reports and play.

defconstatsfiltercatalogreportplay

The tool itself

The schema it checks against, completions for your shell, and raw for any request at all.

schemacontractcompletionopenraw

Releases

Releases From One Command

Cutting a release through the raw API is four requests in a particular order: upload the files, collect their ids, read the release set that already exists, then write the new one back without disturbing the others. release publish is that sequence.

publish.sh
# Upload the files, attach them, write the release: one command
tmc release publish --mod 5 \
  --version 1.2.0 \
  --title "Bug fixes" \
  --content-file CHANGELOG.md \
  --file 'dist/*.zip' --file dist/checksums.txt

# Everything on the item, without re-sending the item
tmc release list  --mod 5
tmc release files --mod 5 --version 1.2.0

Re-Running It Updates

The same --version twice updates that release and merges the new files into its set. Your other releases are never touched, and a hidden release stays hidden.

Globs, Batched

Pass --file 'dist/*.zip' and as many files as you have; the twenty-per-request upload cap is handled for you.

Big Files Stream

A 1 GB upload is read from disk in chunks, not into memory. Anything over your key's size limit is flagged before it is sent.
What It Does

What The CLI Can Do

One command for each thing you would do in the browser, plus a few you would not want to do by hand: bulk edits, pipelines and scripted releases.

No Dependencies At All

Python 3.11+ and the standard library. Drop it on a build machine or a game server with no package index and it runs. cryptography is used for signing if it happens to be installed and a bundled RFC 8032 implementation if it is not, same signatures either way.

Both Auth Modes

Bearer tmc_ keys, or Ed25519 signed assertions where you hold the private key and we only ever store the public half.

One Grammar For Every Type

The same five verbs, list, get, create, update, delete, on every content type. tmc content <type> is the uniform form, and the only way to the free-standing release and media types.

Relations Without Re-Sending

Tags, media, releases, links, sources and collection items are managed on their own, so adding one screenshot does not mean rewriting the whole item.

Bulk, Within Every Cap

Create or edit from a JSON file of any length. Every server cap, 25 per write, 100 per delete, 200 relation members, 500 keys on a relation delete, 20 upload parts, is batched for you, and a partial failure names the element that failed so you can resume rather than start over.

Typos Caught Locally

Field names are checked against a local mirror of the API's schemas, with the near-miss: 'mod' has no field 'tgs'. Did you mean 'tags'? tmc schema mod prints the field list it is checking against.

Made For Pipes

Seven output formats, table, json, jsonl, csv, tsv, yaml, ids, with --field to keep only the columns you want. Data on stdout, progress on stderr, and tmc completion bash|zsh|fish for the shell you are in.

Rate Limits Handled

A 429 is waited out for exactly as long as the API asks, up to --retry-wait-max, then tells you how long is left rather than hanging a build. Backoff applies only to idempotent methods, a POST is never silently repeated.
Credentials

Keys, Profiles And The Public Half

Log in once and the credential is stored in ~/.config/tmc/config.json at mode 0600, or hand it over in the environment and nothing touches disk at all. One profile per site or per key, and --anon for the half of the API that needs no key in the first place.

auth.sh
# A bearer key, or an Ed25519 key you hold the private half of
tmc auth login --token tmc_xxxxxxxxxxxx
tmc auth login --jwt --key-id tmcak_xxxxxxxx --private-key ~/keys/tmc.pem

# A second key on the same site, a scoped one for scripts
tmc auth login --profile ci \
  --base-url https://moddingcommunity.com --token tmc_…
tmc auth use ci

# Which key is active, and what the server lets it do
tmc auth whoami
tmc auth doctor

# …or no key at all
tmc mod list --anon --app 4 -o json

A Profile Per Site Or Key

--profile names one, tmc auth use switches the default, and every stored value has an environment twin, TMC_TOKEN, TMC_KEY_ID, TMC_PRIVATE_KEY_FILE, TMC_BASE_URL, TMC_PROFILE.

Ask What A Key Can Actually Do

auth whoami establishes the key's real permissions with three deliberately harmless probes, a read, a create that cannot validate, and a delete of an empty id list. Nothing is created or deleted; --read-only sends only the read.

A Doctor For The Setup

auth doctor reports which signing backend is in use, whether your config file's permissions are safe and whether the site answers at all, the three things worth knowing before debugging a pipeline.

Reading With No Key At All

--anon reads the public summary of seven types with no Authorization header, a genuinely different endpoint, so it refuses writes before the request leaves, drops the filters that surface does not have rather than letting them look like they matched, and never quietly upgrades itself to a stored key. --set apiPublic=false is how an item leaves it.
Relations

Four Verbs That Say What They Do

The dangerous one is set: on this API a PUT to a relation means "this is now the complete set", so a one-member PUT deletes everything else on the item. The CLI keeps the four apart and warns before it sends the destructive one.

Command Method Means
tmc rel add POST Merge these in, leave everything else
tmc rel set PUT This is now the complete set
tmc rel rm DELETE Drop the ones you named
tmc rel clear DELETE Drop all of them
relations.sh
# Add two tags. The rest of the item is untouched
tmc tags add mod 5 pvp vanilla

# Upload a screenshot and attach it in one step
tmc media add mod 5 --file shot.png --title Screenshot

# Read one relation back
tmc rel get mod 5 releases

# …and the one that REPLACES the whole gallery
tmc rel set mod 5 media --from-file gallery.json
Automation

Use It In CI

Build your project, cut a release and upload its files on every tag. Credentials come from the environment, so nothing is written to disk on a runner.

release.yml
- name: Publish to TMC
  env:
    TMC_TOKEN: ${{ secrets.TMC_TOKEN }}
  run: |
    tmc release publish --mod 42 \
      --version "${GITHUB_REF_NAME#v}" \
      --content-file CHANGELOG.md \
      --file 'dist/*.zip'

# Clean pipes: data on stdout, progress on stderr
tmc mod list --mine --all -o ids | xargs -n1 tmc mod get -o json

Nothing Written To A Runner

The credential comes out of the environment, so no profile is created and nothing outlives the job. A JWT key can stay a secret file the runner mounts, we only ever hold its public half.

Exit Codes That Mean Something

2 usage, 3 auth, 4 not found, 5 validation, 6 rate limited, 7 server, 8 network, so a pipeline can tell "retry this" from "fix your key".

Never Blocked By A Stale Build

A field the tool has not learned about yet still goes through with --allow-unknown-fields, and tmc raw sends any request at all.

Tested Against The Real Thing

203 tests drive the actual CLI against an in-process mock of the API over a real socket, both auth modes, the batching, the relation semantics and the retry path.

Keys Live Under Account → API Keys

A key is free, takes a moment to make, and carries read / write / delete scopes, expiry, IP allow-lists and its own rate limit, so a key you hand to a build server can do exactly one thing. Every command, flag and exit code is written up in the docs.

A key is useful today: the API is live and the docs are written. tmc-cli is open source and installs from a clone of its repository. It is not on PyPI yet.