Publish from Claude Code or Codex

Medal Play ships as one plugin for Claude Code and Codex. Its medal-play skill prepares a web game, adds Medal clips and scores when you ask for them, and publishes the game with the Medal Play CLI. You can also run the CLI yourself.

Before you start

  • Node.js 22 or newer. The skill runs the CLI through npx.
  • Claude Code or the Codex CLI.
  • Access to the get-wrecked organization on GitHub. During early access the plugin installs from the private get-wrecked/medal-play repository, and the CLI is a private package on GitHub Packages.
  • A web game that runs from static files, with an index.html.

1. Give npm access to the CLI

The CLI is @get-wrecked/medal-play on GitHub Packages. npm needs to know where the @get-wrecked scope lives, and it needs a GitHub token with the read:packages scope. With the GitHub CLI signed in:

gh auth refresh -s read:packages
npm config set @get-wrecked:registry https://npm.pkg.github.com
npm config set //npm.pkg.github.com/:_authToken "$(gh auth token)"

Without the GitHub CLI, create a classic personal access token with read:packages and add these two lines to ~/.npmrc:

@get-wrecked:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=<your token>

npm view @get-wrecked/medal-play version prints the latest version once this works. If you skip this step, the skill notices the failed install on your first publish and walks you through it.

2. Install the plugin

The plugin and its skill are the same in both agents. Install it in the one you use.

Claude Code

From a terminal:

claude plugin marketplace add get-wrecked/medal-play
claude plugin install medal-play@medal-play

Or inside Claude Code:

/plugin marketplace add get-wrecked/medal-play
/plugin install medal-play@medal-play

Claude Code clones the marketplace with your git credentials, so your machine has to be able to clone https://github.com/get-wrecked/medal-play. Plugins load when a session starts, so start a new one. claude plugin list shows medal-play@medal-play as enabled.

Codex

codex plugin marketplace add https://github.com/get-wrecked/medal-play
codex plugin add medal-play@medal-play

codex plugin list shows medal-play@medal-play as installed and enabled. Start a new Codex session to load the skill.

Publishing uploads files, so it needs the network. When Codex runs commands in a sandbox without network access, it asks to run the publish command outside the sandbox. Approve it.

3. Publish a game

Open your agent in the game's folder and ask in plain words:

publish this on Medal

Ask for more when you want it:

publish this on Medal, clip when the player wins, and post the high score

The agent then:

  1. Runs the project's build, if it has one, and finds the folder with index.html: the first of dist/, build/, out/, the project root and public/.
  2. Adds window.medal calls only where you asked for them. Asked only to publish, it leaves your code alone. The clip button works on every game anyway.
  3. Writes medal.json with the game's name, description, clip events and leaderboard order.
  4. Runs npx --yes @get-wrecked/medal-play@latest publish --json.
  5. Tells you the live URL, the claim link and which calls it added.

On success the CLI prints:

{
  "url": "https://star-dodge.medal.games",
  "claimUrl": "https://medal.games/claim/…",
  "slug": "star-dodge",
  "version": "…"
}

Publishing needs no account. The first publish creates a publisher key for this machine and saves it in ~/.config/medal-play/credentials.json.

4. Claim your game

A new game is live but unlisted, and it expires after 7 days unless you claim it. Open the claim link, sign in with Medal and press Claim. The game then stays up under your Medal account.

To skip claim links, link this machine to your Medal account once:

npx --yes @get-wrecked/medal-play@latest login

It prints a code and a link to the approval page. Sign in there and approve the code, and the CLI finishes on its own. The games this machine already published become yours, and later ones start claimed, with a claimUrl of null. Only approve a code you started yourself.

5. Update a game

Publish again from the same folder on the same machine, through your agent or the CLI. The CLI wrote the game's slug into medal.json on the first publish, and the publisher key shows the game is yours, so the new version replaces the old one at the same address. Keep medal.json in the project and keep the credentials file.

To list a claimed game on the games page, set "visibility": "public" in medal.json and publish again.

Publish from the terminal

The skill runs the same CLI you can run yourself, from the project root:

npx --yes @get-wrecked/medal-play@latest publish
npx --yes @get-wrecked/medal-play@latest login
--dir <folder>
The folder with the built index.html. Default: the first of dist/, build/, out/, . and public/ that has one.
--json
Prints the result as JSON. On failure it prints {"error": {"code", "message", "hint"}} and exits non-zero.
--env prod|staging|test
The Medal Play environment to publish to. Default: prod, which is medal.games.

Add clips and scores

Medal Play injects its SDK into every page of a hosted game as window.medal. Call it through window.medal?. so the game still works anywhere else, and don't bundle the SDK or add a script tag for it. The overlay draws the clip button, sign-in and the leaderboard, so the game doesn't need its own.

// Clip the moment the player wins, with a marker on the clip's timeline.
window.medal?.event('win', { title: 'Level 3 cleared' })

// Save the last 25 seconds after a big play.
window.medal?.clip({ title: 'Five-hit combo', seconds: 25 })

// At the end of a run, post the score and show the leaderboard.
window.medal?.scores.submit(score)
window.medal?.scores.show()
event(name, { title, seconds, clip })
Marks a moment. It clips when medal.json lists the name in clipEvents, when clip is true, or when there is no clipEvents list.
clip({ title, seconds })
Saves the last seconds of play, 15 by default, and shows the clip toast.
scores.submit(score)
Posts a score under the signed-in player and keeps each player's best. A signed-out player is asked to sign in first.
scores.top({ limit }), scores.show()
Reads the top scores, or opens the leaderboard.
init({ canvas, bufferSeconds, overlay, hotkey })
Optional. Picks the canvas to record when the page has several, keeps 5 to 30 seconds of video (30 by default), and turns off the overlay or the F8 clip hotkey.
user(), signIn()
The signed-in Medal player, or a sign-in popup. Call signIn() from a click or key press.

The recorder captures the game's canvas and its Web Audio at up to 1280×720 and 30 fps. Nothing leaves the player's browser unless they choose Post to Medal. TypeScript projects get the types from the CLI package with import type {} from '@get-wrecked/medal-play/sdk'.

medal.json

Optional, in the project root. The CLI fills in anything missing and writes the slug back after the first publish.

{
  "name": "Star Dodge",
  "description": "Dodge the falling stars for as long as you can.",
  "clipEvents": ["win"],
  "leaderboard": { "order": "desc" },
  "input": ["keyboard", "mouse", "touch"],
  "orientation": "portrait",
  "visibility": "public"
}
name
At most 60 characters. Default: the page's <title>, else the folder name.
slug
The game's address: 3 to 40 characters of a-z, 0-9 and -. Default: made from the name.
description
At most 280 characters. Default: the page's meta description.
clipEvents
The medal.event() names that clip. Without the list, every event clips.
leaderboard.order
"desc" when a higher score is better, the default. "asc" for times and other scores where lower wins.
input
Any of keyboard, mouse, touch and gamepad. Default: read from the game's event listeners.
orientation
landscape, portrait or any, the default.
visibility
unlisted, the default, or public. Claimed public games are listed on medal.games.

Limits

  • 100 MB and 2,000 files per build.
  • 25 MB per file, and 5 MB per HTML file.
  • Dotfiles, node_modules/ and .git/ are never uploaded.
  • Static files only: no server code, databases or secrets.

Troubleshooting

npx fails with 401, 403 or 404 for @get-wrecked/medal-play
npm can't read the private package. Redo step 1, and check that the token has read:packages and that your GitHub account can see the get-wrecked organization.
Adding the marketplace fails
Your git can't clone the private repository. Try git clone https://github.com/get-wrecked/medal-play. gh auth login followed by gh auth setup-git sets up access over HTTPS.
Publishing fails with slug_taken
The slug in medal.json belongs to another game, or to one published from another machine. Use the slug the error's hint suggests, or remove slug to get a free one.
Clips fail with no_canvas
Only games that draw to a <canvas> can be clipped for now. When a page has several canvases, call window.medal?.init({ canvas }) with the main one.

Keep the plugin current

Every publish runs the latest CLI, so CLI fixes need no update. To pick up a newer skill in Claude Code, then restart it:

claude plugin marketplace update medal-play
claude plugin update medal-play@medal-play

In Codex:

codex plugin marketplace upgrade
codex plugin add medal-play@medal-play