# Sentinel > Hosting for user-made, AI-first games. Publish a browser game or an Android APK and get a playable URL. ## Quickest path (shell access) ## Publishing games to Sentinel To publish a browser game (or an Android .apk), run from the game's folder: npx -y https://sentinel.cybercorpresearch.com/cli/sentinel.tgz --json - Works on a folder with index.html, a JS project with a build script (it runs the build), a .zip, a single .html file, or an .apk. Engine web exports (Godot, Unity, …) are fine. - The first run prints a sign-in link; show it to the user. Or set SENTINEL_TOKEN. - Output is JSON: game.playUrl is the link to give the user. Re-running updates the same game. - If it reports findings with level "error", fix them and run again. Never put API keys in client code. - deployment.qa has the boot check: load time, fps, console errors and a screenshotUrl. Look at the screenshot. - Check that the game actually plays, in a real browser (screenshots saved locally, console output printed): npx -y https://sentinel.cybercorpresearch.com/cli/sentinel.tgz playtest "screenshot start; press Space; hold ArrowRight 600; screenshot moved; read window.score" Steps: wait ms · press Key [n] · hold Key ms · click x y · tap x y · move x y · drag x1 y1 x2 y2 · type text · screenshot [label] · read . Desktop is 1280x720; --phone is 390x844 with touch. Fix what you see, deploy again. - Preview before publishing: `sentinel dev` (local player, SDK mocked, reloads on change). - Useful flags: --name , --title, --description, --tag , --preview (don't go live). - Optional SDK for leaderboards, cloud saves and purchases: , then window.Sentinel?.submitScore(n), Sentinel.save(k, v) / load(k), Sentinel.purchase(sku). Guard with ?. so the game still runs locally. Declare products in sentinel.json: "products": [{ "sku", "title", "price", "kind" }]. ## MCP (Claude Code, Codex, Cursor, VS Code, Claude Desktop, Windsurf, Gemini CLI) Local stdio server: command "npx", args ["-y", "https://sentinel.cybercorpresearch.com/cli/sentinel.tgz", "mcp"] Tools: deploy_game(path, name?, title?, description?, tags?, madeWith?, preview?), check_game(path), playtest_game(name, steps[], viewport?, deploymentId?), list_games(), get_game(name), rollback(name, deploymentId?), login() deploy_game and playtest_game return screenshots as images, plus console output and frame rate. Remote MCP for chat apps (no file system): https://sentinel.cybercorpresearch.com/mcp with Bearer auth: publish_html(html, ...), publish_game(files[], ...), playtest_game, list_games, get_game, rollback. Claude Code: claude mcp add --scope user sentinel -- npx -y https://sentinel.cybercorpresearch.com/cli/sentinel.tgz mcp ## HTTP API (Authorization: Bearer ) POST https://sentinel.cybercorpresearch.com/api/v1/publish multipart: file=@game.zip | file=@index.html | file=@app.apk, optional name, title, target=preview, meta (JSON) or JSON: { "name"?, "title"?, "files": [{ "path": "index.html", "content": "...", "encoding"?: "utf8"|"base64" }] } → 201 { game: { slug, playUrl, productionUrl, dashboardUrl, ... }, deployment: { id, number, status, findings, url } } Limit 100 MB. Use the incremental protocol for larger builds. Incremental (content-addressed, what the CLI does): POST https://sentinel.cybercorpresearch.com/api/v1/deployments { game: { id? | slug? | title? }, files: [{ path, sha (hex sha256), size }], meta?, target?: "production"|"preview", allowSecrets? } → 409 { error: { code: "missing_files", missing: [sha...] } } PUT https://sentinel.cybercorpresearch.com/api/v1/files/ body: raw bytes (server verifies the hash) POST https://sentinel.cybercorpresearch.com/api/v1/deployments (same body) → 201 Blocked deploys → 422 { error: { code: "deploy_blocked", findings: [{ level, code, message, fix }] } } GET https://sentinel.cybercorpresearch.com/api/v1/games public catalog (?q, ?tag, ?engine, ?sort=new|popular) GET https://sentinel.cybercorpresearch.com/api/v1/games?mine=1 your games GET https://sentinel.cybercorpresearch.com/api/v1/games/ one game PATCH https://sentinel.cybercorpresearch.com/api/v1/games/ { title?, description?, tags?, madeWith?, orientation?, aspectRatio?, controls? } GET https://sentinel.cybercorpresearch.com/api/v1/deployments?game= POST https://sentinel.cybercorpresearch.com/api/v1/deployments//promote (make live; also rollback) GET https://sentinel.cybercorpresearch.com/api/v1/deployments/ includes qa: { loadMs, fps, screenshotUrl, pageErrors, consoleErrors, failedRequests } ## Playtests (see the game running, from any tool) POST https://sentinel.cybercorpresearch.com/api/v1/playtests { "game": "", "steps": ["press Space", "wait 500", "screenshot jumped"], "viewport"?: "desktop"|"phone", "deployment"?: "" } → 200 { playtest: { status, shots: [{ label, url, atMs }], reads, console, pageErrors, failedRequests, fps, error } } Waits for the result (pass "wait": false for 202, then GET https://sentinel.cybercorpresearch.com/api/v1/playtests/). Steps, one per item (or one string split on ";"): wait 500 · press Space [n] · hold ArrowRight 800 · click 640 360 · tap 195 600 · move x y · drag x1 y1 x2 y2 [ms] type text · screenshot [label] · read Key names are Playwright's: Space, Enter, ArrowLeft, KeyW, a… Coordinates are screenshot pixels (desktop 1280x720, phone 390x844 with touch). A final screenshot is always taken. Limits: 60 steps, 8 screenshots, 30s of waits/holds, 120 playtests/hour. Your own games only. ## Auth Device flow (what `sentinel login` does): POST https://sentinel.cybercorpresearch.com/api/v1/auth/device → { userCode, verificationUriComplete, deviceCode } show the URL to the user, then poll POST https://sentinel.cybercorpresearch.com/api/v1/auth/device/token { deviceCode } until 200 { token }. Or create a token at https://sentinel.cybercorpresearch.com/settings/tokens and set SENTINEL_TOKEN. ## Rules - Static files only; nothing runs server-side. Builds happen where the game was made. - Entry point: index.html at the top level (or a single .html file). Absolute paths like /assets/x.png work. - .env and credential files are never uploaded. API keys found in game code block the deploy. - Limits: 1 GB per deploy, 250 MB per file, 20,000 files, APK 300 MB; 120 deploys/hour and 5 GB uploaded/day per user. Over a limit → 429 { error: { code: "rate_limited", retryAfter } } and a Retry-After header. ## Game SDK (optional, served on every game origin) then window.Sentinel: init() → { user: { handle } | null, game, embedded } gameplayStart() / gameplayStop() submitScore(score, { board? }) → { best, rank, saved }; leaderboard({ board?, limit? }) → [{ player, score }] save(key, value) / load(key) (cloud saves per player; localStorage fallback) purchase(sku) → { ok, sku, entitlementId? } (the player confirms in a Sentinel dialog); entitlements() → [sku] ads.requestBreak(kind) → { shown } (hook; no ads yet) on("pause" | "resume", fn) Outside the Sentinel player every call degrades gracefully. Guard calls with window.Sentinel?. ## Optional sentinel.json (next to your game) { "name": "my-game", "title": "My Game", "description": "...", "tags": ["arcade"], "madeWith": ["claude-code"], "orientation": "landscape" | "portrait", "outputDirectory": "dist", "buildCommand": "npm run build", "products": [{ "sku": "gold-skin", "title": "Gold skin", "price": 0.99, "kind": "unlock" | "consumable" }] } CLI: npx -y https://sentinel.cybercorpresearch.com/cli/sentinel.tgz