Before you start
- Node.js 22 or newer. The skill runs the CLI through
npx. - Claude Code or the Codex CLI.
- A web game that runs from static files, with an
index.html.
1. Install the plugin
The plugin and its skill are the same in both agents. Install it in the one you use. Both install it from medal.games, with no GitHub or npm account.
Claude Code
From a terminal:
claude plugin marketplace add https://medal.games/marketplace.json claude plugin install medal-play@medal-play
Or inside Claude Code:
/plugin marketplace add https://medal.games/marketplace.json /plugin install medal-play@medal-play
Plugins load when a session starts, so start a new one. claude plugin list shows medal-play@medal-play as enabled.
If you added the marketplace from GitHub during early access, run claude plugin marketplace remove medal-play first. Claude Code won't add a marketplace of the same name from another source.
Codex
codex plugin marketplace add https://medal.games/marketplace.git 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. If you added the marketplace from GitHub during early access, this replaces it.
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.
2. 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:
- Runs the project's build, if it has one, and finds the folder with
index.html: the first ofdist/,build/,out/, the project root andpublic/. - Adds
window.medalcalls only where you asked for them. Asked only to publish, it leaves your code alone. The clip button works on every game anyway. - Writes
medal.jsonwith the game's name, description, clip events and leaderboard order. - Runs
npx --yes --@get-wrecked:registry=https://medal.games/npm @get-wrecked/medal-play@latest publish --json. - 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": "…"
}The first publish creates a publisher key for this machine and saves it in ~/.config/medal-play/credentials.json. For now only Medal employees can publish, so a new key first gets medal_login_required and then publisher_not_approved.
3. Claim your game
While only Medal employees can publish, every key is linked to a Medal account before its first game, so games start claimed and claimUrl is null. The rest of this step is for when publishing opens up.
A new game is live but not yet listed, 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 and appears on the games page.
To skip claim links, link this machine to your Medal account once:
npx --yes --@get-wrecked:registry=https://medal.games/npm @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.
4. 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.
A claimed game is listed on the games page once someone at Medal approves the listing, and the approval lasts when you publish again. To keep one off it, set "visibility": "unlisted" in medal.json and publish again; setting "public" brings it back without a new approval.
Publish from the terminal
The skill runs the same CLI you can run yourself, from the project root:
npx --yes --@get-wrecked:registry=https://medal.games/npm @get-wrecked/medal-play@latest publish npx --yes --@get-wrecked:registry=https://medal.games/npm @get-wrecked/medal-play@latest login
Medal Play serves the CLI from its own npm registry, which the --@get-wrecked:registry flag points npm at, so it needs no npm account or ~/.npmrc entry.
--dir <folder>- The folder with the built
index.html. Default: the first ofdist/,build/,out/,.andpublic/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.jsonlists the name inclipEvents, whenclipis true, or when there is noclipEventslist. clip({ title, seconds })- Saves the last
secondsof 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, capture })- 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
F8clip hotkey.capture: 'page'lets players clip the whole page, HTML included. 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. With capture: 'page', the overlay offers a button that shares the tab, and from then on it captures the whole page without the overlay, in desktop Chromium. 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 25 characters of
a-z,0-9and-. Default: made from the name. description- At most 280 characters. Default: the page's meta description.
thumbnail- A PNG, JPEG or WebP in the build, the game's art on medal.games.
icon- A square PNG, JPEG or WebP in the build, the box art on medal.games' featured cards. Default: the thumbnail.
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.leaderboard.season- Default 1. Raise it to start an empty board when the build goes live; earlier seasons stay readable with
?season=N.leaderboard.seasonNamenames it. input- Any of
keyboard,mouse,touchandgamepad. Default: read from the game's event listeners. orientationlandscape,portraitorany, the default.visibilitypublic, the default, orunlisted. A claimed public game is listed on medal.games once someone at Medal approves it.unlistedkeeps it off, andpublicshows it again.
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
npxfails with 401, 403 or 404 for@get-wrecked/medal-play- npm looked for the package somewhere other than medal.games. Keep the
--@get-wrecked:registry=https://medal.games/npmflag in the command. - Claude Code won't add the marketplace
- A marketplace named
medal-playfrom another source, such as the GitHub repository from early access, is already there. Remove it withclaude plugin marketplace remove medal-playand add it again. - Publishing fails with
medal_login_required - Only Medal employees can publish for now, and each machine's key is linked to a Medal account first. Run the
logincommand from step 3, then publish again. - Publishing fails with
publisher_not_approved - The key is linked, and a Medal employee approves it next. Open the link in the error's hint, sign in with your Medal work account, check the Medal username and approve the key, then publish again. Each machine has its own key, so each one is approved once.
- Publishing fails with
slug_taken - The slug in
medal.jsonbelongs to another game, or to one published from another machine. Use the slug the error's hint suggests, or removeslugto get a free one. - Clips fail with
no_canvas - The game has no
<canvas>yet. When a page has several canvases, callwindow.medal?.init({ canvas })with the main one. A game without one can be clipped only withcapture: 'page', once the player shares the tab.
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