Skill: chuckstock/remix-agent-publish (version 0.1.0)

--- name: remix-agent-publish description: Build Remix games for remix.gg with the server-api v1 agents REST API and Farcade game SDK requirements. metadata: tags: remix, games, api, agent, publishing --- ## When to use Use this skill when users want to automate game publishing on Remix (`remix.gg`) from AI agents or external services. ## How to use - Read [API Authentication](api/authentication.md) first. - Fetch OpenAPI spec at `https://api.remix.gg/docs/json` before generating API calls. - Use [API Reference](api/reference.md) for workflow guardrails (not as the method/path source of truth). - For Phaser builds, use [Phaser 2D Arcade Companion Skill](frameworks/phaser-2d-arcade/SKILL.md). - For lightweight 3D builds, use [Three.js Lite Companion Skill](frameworks/threejs-lite/SKILL.md). - Use [Game SDK Reference](references/game-sdk.md) when generating or fixing game code. - Apply [Submission Rules](rules/submission-requirements.md) for validation requirements. - Follow [Game Creation Best Practices](rules/game-creation-best-practices.md) for mobile-first and SDK-safe implementation. - Use [REST Snippets](snippets/rest-client.md) for client integration examples. - Use [MCP Quickstart](mcp/quickstart.md) for assistant workflows. ## Source of truth When docs and runtime behavior disagree, defer to the server API source and OpenAPI docs: - `https://api.remix.gg/docs` - `https://api.remix.gg/docs/json` - `apps/server-api/routes/agents.ts` - `apps/server-api/lib/api-auth.ts` - `apps/server-api/lib/agent-api.ts` - `apps/server-api/app/[...path]/route.ts` - `packages/game-sdk/src/index.ts`

path: api/authentication.md
content: ---
name: api-authentication
description: Authentication for Remix agent publishing REST APIs using bearer API keys
metadata:
  tags: remix, authentication, api-key
---

## Current Auth Model

Server API base URL: `https://api.remix.gg`

Remix server API uses bearer API keys generated from Remix account settings.

Supported auth header:
- `Authorization: Bearer <api_key>` (required)

The backend resolves the key to a user and enforces ownership checks on game/version mutations.

## How to Get an API Key

1. Log in to your Remix account.
2. Go to `https://remix.gg/api-keys`.
3. Generate a new API key.
4. Copy it once and store it in your server-side secret manager.

## Required Headers

```http
Authorization: Bearer <api_key>
Content-Type: application/json
```

## Security Notes

- Never expose API keys in client-side browser code.
- Store in server env vars or secret manager.
- Revoke and rotate keys from `https://remix.gg/api-keys`.
- Usage updates `lastUsed` automatically.

## Troubleshooting Invalid API Key

- Confirm you are sending the header exactly as `Authorization: Bearer <api_key>`.
- Make sure there are no extra quotes/spaces/newlines around the key in your environment variable.
- Verify the key is active in `https://remix.gg/api-keys` (not revoked or expired).
- Generate a new key and retry if you suspect the original key was copied incorrectly.
- Ensure requests are sent from your server/backend, not client-side browser code.
- For definitive request/response contracts, check `https://api.remix.gg/docs`.


path: api/reference.md
content: ---
name: api-reference
description: OpenAPI-first reference for Remix agent publishing REST routes
metadata:
  tags: remix, api, rest, reference
---

## OpenAPI-First Rule

Before generating or executing API calls, fetch:

- `https://api.remix.gg/docs/json`

Use OpenAPI as the contract source of truth for:
- methods (`GET`/`POST`/...)
- paths
- params/query/body schemas
- response shapes

Base headers:

```http
Authorization: Bearer <api_key>
Content-Type: application/json
```

Base URL: `https://api.remix.gg`

OpenAPI UI: `https://api.remix.gg/docs`

## Response Envelope

Success:

```json
{ "success": true, "data": { "...": "..." } }
```

Error:

```json
{ "success": false, "error": { "code": "SOME_CODE", "message": "...", "details": null } }
```

## Endpoint Families

Use OpenAPI for exact paths; these are the stable groups:
- Metadata: categories
- Games: list/detail/create
- Versions: list/detail/code/thread/status/validate/code-update
- Assets: list game assets (read-only discovery for app-uploaded files)
- Readiness: launch readiness checks

## Not Exposed in Agent REST

- No delete route.
- No create-version route.
- No submit route.
- No write endpoints for game metadata or assets in this surface.

## Asset Upload State (As of February 9, 2026)

- Binary asset uploads (including icon/image/audio files) are handled in the Remix app/Studio upload flow.
- Agent REST currently exposes asset discovery via `GET /v1/agents/games/{gameId}/assets` only.
- Recommended pattern: upload assets in the app first, then reference hosted asset URLs from the assets list in your HTML game code.

Canonical flow:
1. create game
2. upload/update version code
3. validate and/or launch-readiness
4. check status

## Common Error Codes

- `UNAUTHORIZED` - Missing/invalid API key.
- `ACCOUNT_INELIGIBLE` - User is banned/deleted.
- `FORBIDDEN` - User does not own the game/version.
- `GAME_NOT_FOUND` - Game missing.
- `VERSION_NOT_FOUND` - Game/version mismatch or missing.
- `MAX_IN_DEV_GAMES_EXCEEDED` - Creator reached cap.
- `VALIDATION_ERROR` - Body/query failed validation.
- `INVALID_JSON` - Body could not be parsed as JSON.
- `LIVE_VERSION_LOCKED` - Attempted to edit a live version.
- `VERSION_NOT_EDITABLE` - Version is submitted/approved/launched.
- `THREAD_CREATE_FAILED` - Failed to provision upload thread.
- `RATE_LIMITED` - API key rate limit exceeded.
- `INTERNAL_SERVER_ERROR` - Unexpected server failure.


path: mcp/quickstart.md
content: ---
name: mcp-quickstart
description: Quickstart workflow for AI assistants using Remix publishing APIs
metadata:
  tags: remix, mcp, quickstart
---

## Recommended Flow

Base URL: `https://api.remix.gg`

1. Fetch OpenAPI JSON from `https://api.remix.gg/docs/json`.
2. Resolve exact methods/paths/schemas from the spec.
3. `GET /v1/agents/metadata/categories` to fetch valid category enums.
4. `POST /v1/agents/games`
5. Build game code against `window.FarcadeSDK` requirements, including SDK `<script>` in HTML `<head>` (`https://cdn.jsdelivr.net/npm/@farcade/game-sdk@0.3.0/dist/index.min.js`) (see `references/game-sdk.md`)
6. Set required metadata:
   - Name: game metadata APIs
   - Category: game metadata APIs (1-3)
   - Icon: upload in Remix Studio/app flow
7. Upload required binary assets (icon/sprites/audio) in Remix Studio/app flow.
8. `GET /v1/agents/games/{gameId}/assets` to confirm hosted asset URLs.
9. `POST /v1/agents/games/{gameId}/versions/{versionId}/code`
10. `GET /v1/agents/games/{gameId}/versions/{versionId}/validate`
11. Optional: `GET /v1/agents/games/{gameId}/launch-readiness?versionId={versionId}`
12. If blockers exist, patch code/metadata and repeat validation
13. `GET /v1/agents/games/{gameId}/versions/{versionId}/status`

## Discovery / Inspection Endpoints

- `GET /v1/agents/games`
- `GET /v1/agents/games/{gameId}`
- `GET /v1/agents/games/{gameId}/versions`
- `GET /v1/agents/games/{gameId}/versions/{versionId}`
- `GET /v1/agents/games/{gameId}/versions/{versionId}/code`
- `GET /v1/agents/games/{gameId}/versions/{versionId}/thread`
- `GET /v1/agents/games/{gameId}/assets`

## Guardrails

- Never skip validation checks.
- Treat `blockers[]` as source of truth.
- Treat agent REST assets as read-only discovery; do not assume an asset upload REST route exists.
- Do not trust cached path/method memory when OpenAPI is available.
- Do not create extra versions from agent REST.
- Do not submit from agent REST.
- If docs are stale, check `https://api.remix.gg/docs`.


path: references/game-sdk.md
content: # Farcade Game SDK Reference (`@farcade/game-sdk`)

Use this file when generating or repairing game code for Remix.

## Runtime model

- In Remix-hosted uploads, the SDK is available as `window.FarcadeSDK`.
- Include SDK script in the HTML `<head>`:
  - `<script src="https://cdn.jsdelivr.net/npm/@farcade/game-sdk@0.3.0/dist/index.min.js"></script>`
- Do not rely on package imports in uploaded single-file game code.
- Always call `await window.FarcadeSDK.ready()` before reading player/game data.

## Required hooks for v1 agent validation

These checks are required by `GET /v1/agents/games/{gameId}/versions/{versionId}/validate`:

- `window.FarcadeSDK.singlePlayer.actions.gameOver({ score })`
- `window.FarcadeSDK.onPlayAgain(() => { ... })`
- `window.FarcadeSDK.onToggleMute(({ isMuted }) => { ... })`

## Commonly used SDK surface

Properties/getters:

- `window.FarcadeSDK.player`
- `window.FarcadeSDK.players`
- `window.FarcadeSDK.gameState`
- `window.FarcadeSDK.gameInfo`
- `window.FarcadeSDK.purchasedItems`
- `window.FarcadeSDK.isReady`
- `window.FarcadeSDK.hasItem(itemId)`

Single-player actions:

- `window.FarcadeSDK.singlePlayer.actions.gameOver({ score })`
- `window.FarcadeSDK.singlePlayer.actions.saveGameState({ gameState })`
- `window.FarcadeSDK.singlePlayer.actions.purchase({ item })`
- `window.FarcadeSDK.singlePlayer.actions.reportError({ message, ... })`
- `window.FarcadeSDK.hapticFeedback()`

Multiplayer actions/events also exist (`multiplayer.actions.*`, `onGameStateUpdated`), but are optional for basic single-player games.

## Minimal integration template

```javascript
async function initGame() {
  const sdk = window.FarcadeSDK
  await sdk.ready()

  let muted = true
  sdk.onToggleMute(({ isMuted }) => {
    muted = isMuted
  })

  sdk.onPlayAgain(() => {
    restart()
  })

  function finish(score) {
    sdk.singlePlayer.actions.gameOver({ score })
  }

  function save(state) {
    sdk.singlePlayer.actions.saveGameState({ gameState: state })
  }

  function restart() {
    // reset local game state and render
  }

  // start gameplay loop...
}

initGame()
```

## Mistakes to avoid

- Calling SDK getters before `ready()` resolves.
- Omitting one of the required validation hooks (`gameOver`, `onPlayAgain`, `onToggleMute`).
- Using non-existent SDK methods such as `vibrate`, `checkpoint`, or `save`.
- Using `localStorage`/`sessionStorage` as primary persistence instead of `saveGameState`.


path: rules/game-creation-best-practices.md
content: # Game Creation Best Practices

## Mobile-First Requirements

- Build for portrait orientation only.
- Canvas must be `720x1080` (2:3 aspect ratio).
- Do not use landscape layouts or controls.
- Design for thumb-friendly interactions and readable UI.

## Mandatory Farcade SDK Usage

Include the SDK script in HTML `<head>`:

- `<script src="https://cdn.jsdelivr.net/npm/@farcade/game-sdk@0.3.0/dist/index.min.js"></script>`

Use only the supported SDK APIs:

- `window.FarcadeSDK.singlePlayer.actions.saveGameState({ gameState: {...} })`
- `window.FarcadeSDK.singlePlayer.actions.gameOver({ score: number })`
- `window.FarcadeSDK.onPlayAgain(() => { ... })`
- `window.FarcadeSDK.onToggleMute((data) => { ... })`
- `window.FarcadeSDK.ready()`
- `window.FarcadeSDK.gameState`
- `window.FarcadeSDK.hapticFeedback()`

## Forbidden APIs and Patterns

- Do not use `localStorage`.
- Do not use `sessionStorage`.
- Do not use `navigator.vibrate(...)`.
- Do not use `window.FarcadeSDK.vibrate(...)`.
- Do not use non-existent SDK methods (`save`, `checkpoint`, etc.).

## Game Over and Restart Flow

- End runs only with:

```javascript
window.FarcadeSDK.singlePlayer.actions.gameOver({ score: finalScore })
```

- Do not implement a custom game-over screen.
- Handle replay via `window.FarcadeSDK.onPlayAgain(...)`.

## State Persistence

- Persist progress only with `saveGameState`.
- Read persisted values from `window.FarcadeSDK.gameState` after `await window.FarcadeSDK.ready()`.
- Keep saved state compact and serializable.

## Haptics and Audio

- Use `window.FarcadeSDK.hapticFeedback()` for collisions, scoring, UI interactions, and game over.
- Respect `onToggleMute` state for all game audio.
- Use lightweight Web Audio API SFX for better mobile performance.

## Framework Rules

### Phaser (2D)

- Use global `Phaser` (no imports).
- Use scene lifecycle (`preload`, `create`, `update`).
- Use Arcade physics for collisions.
- Do not set `parent` in `Phaser.Game` config.

### Three.js + Cannon-ES (3D)

- Use globals `THREE` and `CANNON` (no imports).
- Use `requestAnimationFrame` loop.
- Include ambient + directional lighting.
- Sync mesh transforms from physics bodies every frame.

## Code and Runtime Constraints

- Write ES6 JavaScript only (no TypeScript).
- Do not use `import` statements.
- Assume browser-only runtime (no Node.js APIs).
- Define `initGame()` and call it at the end of the file.

## Validation Checklist

Before finalizing a game, verify:

- Portrait 2:3 canvas (`720x1080`).
- SDK hooks present (`gameOver`, `onPlayAgain`, `onToggleMute`).
- No forbidden storage or vibration APIs.
- Haptics integrated for meaningful gameplay events.
- Restart flow works cleanly from `onPlayAgain`.


path: rules/submission-requirements.md
content: # Submission Requirements

A version should be considered launch-ready only when all are true:

- Game code exists
- Game has name, icon, and >=1 category
- SDK hooks exist in code:
  - HTML `<head>` includes:
    - `<script src="https://cdn.jsdelivr.net/npm/@farcade/game-sdk@0.3.0/dist/index.min.js"></script>`
  - `window.FarcadeSDK.singlePlayer.actions.gameOver(...)`
  - `window.FarcadeSDK.onPlayAgain(...)`
  - `window.FarcadeSDK.onToggleMute(...)`

## Agent REST flow constraints

- Create a game draft via `POST /v1/agents/games`.
- Update current version via `POST /v1/agents/games/{gameId}/versions/{versionId}/code`.
- Do not create extra versions from agent REST (no create-version route).
- Do not submit from agent REST (no submit route).
- Never update a live version.

## Metadata requirements (current)

- Name: set via existing game metadata APIs.
- Category: add 1-3 categories via existing game metadata APIs.
- Icon: upload in Remix Studio/app flow (no dedicated agent REST icon route yet).
- Binary assets: upload in Remix Studio/app flow, then use `GET /v1/agents/games/{gameId}/assets` to map hosted URLs into code.

Use `GET /v1/agents/games/{gameId}/versions/{versionId}/validate` to confirm blockers.
Use `GET /v1/agents/games/{gameId}/launch-readiness?versionId={versionId}` when you want an aggregate readiness signal (`valid` + `readyForSubmission`).


path: snippets/rest-client.md
content: ---
name: agent-rest-snippets
description: REST client snippets for Remix agent publishing
metadata:
  tags: remix, rest, snippets
---

## TypeScript (REST)

```ts
const baseUrl = 'https://api.remix.gg'
const headers = {
  Authorization: `Bearer ${process.env.REMIX_API_KEY!}`,
  'Content-Type': 'application/json',
}

const openApiSpec = await fetch(`${baseUrl}/docs/json`, {
  method: 'GET',
  headers,
}).then((r) => r.json())

const createRes = await fetch(`${baseUrl}/v1/agents/games`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ name: 'Neon Dash' }),
})
const createJson = await createRes.json()
if (!createJson.success) throw new Error(createJson.error.message)

const gameId = createJson.data.game.id as string
const versionId = createJson.data.game.version.id as string

const updateRes = await fetch(
  `${baseUrl}/v1/agents/games/${gameId}/versions/${versionId}/code`,
  {
    method: 'POST',
    headers,
    body: JSON.stringify({ code: html }),
  },
)
const updateJson = await updateRes.json()
if (!updateJson.success) throw new Error(updateJson.error.message)

const validateRes = await fetch(
  `${baseUrl}/v1/agents/games/${gameId}/versions/${versionId}/validate`,
  { method: 'GET', headers },
)
const validateJson = await validateRes.json()
if (!validateJson.success) throw new Error(validateJson.error.message)
if (!validateJson.data.valid) {
  throw new Error(`Blocked: ${validateJson.data.blockers.map((b: { code: string }) => b.code).join(', ')}`)
}

const statusRes = await fetch(
  `${baseUrl}/v1/agents/games/${gameId}/versions/${versionId}/status`,
  { method: 'GET', headers },
)
const statusJson = await statusRes.json()
if (!statusJson.success) throw new Error(statusJson.error.message)

const readinessRes = await fetch(
  `${baseUrl}/v1/agents/games/${gameId}/launch-readiness?versionId=${versionId}`,
  { method: 'GET', headers },
)
const readinessJson = await readinessRes.json()
if (!readinessJson.success) throw new Error(readinessJson.error.message)
```


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)