/serve-game — open a Crumb-generated game in the browser
When the user wants to open a previously generated game from a Crumb session, auto-detect single-file vs multi-file and serve accordingly.
Why this matters
Crumb generates two artifact shapes:
| Shape | Layout | Open with |
|---|---|---|
| Single-file | artifacts/game.html (Phaser inlined or single <script>) |
file:// direct (browser allows everything from a single file) |
| Multi-file | artifacts/<game>/index.html + src/main.js + src/scenes/ + sw.js + manifest.webmanifest |
MUST be served over http:// — file:// blocks <script type="module"> imports and serviceWorker.register |
Multi-file is the new default (post-2026-05-03). Opening multi-file with file:// shows
"Loading…" forever because the ES module imports silently fail with "module specifier
does not start with /, ./, or ../ over file:" or the service worker registration
throws. The fix is always: spin up a local http server, open http://localhost:<port>/.
How to trigger
User says any of:
- "이전 게임 열어줘" / "전에 만든 거 열어줘" / "그 게임 다시 보자"
- "고양이 퍼즐 열어줘" / "match-3 게임 열어줘" (pitch keyword disambiguates)
- "open the previous game" / "open the cat puzzle" / "show me what we built"
- "serve the multi-file game" / "demo 열어줘"
Or explicit slash: /serve-game [<pitch keyword>].
Procedure
1. Locate the artifact
Search candidates under ~/.crumb/projects/. Prefer the most recently modified session
that matches the user's pitch keyword (if given).
# Find every game artifact (both shapes).
{
find ~/.crumb/projects -path '*/artifacts/game.html' 2>/dev/null
find ~/.crumb/projects -path '*/artifacts/*/index.html' 2>/dev/null
find ~/.crumb/projects -path '*/sessions/*/index.html' 2>/dev/null
} | xargs -I{} sh -c 'printf "%s\t%s\n" "$(stat -f %m "{}" 2>/dev/null || stat -c %Y "{}")" "{}"' \
| sort -nr | head -10
If the user gave a keyword (e.g. "고양이"), additionally grep
spec.md / index.html / game.html siblings to disambiguate:
grep -liE "고양이|cat|match-3|drag|merge|<keyword>" \
"$(dirname "$candidate")/spec.md" "$candidate" 2>/dev/null
2. Decide single vs multi
candidate="<picked path>"
if grep -qE '<script[^>]+type=["''']module["''']|navigator\.serviceWorker' "$candidate"; then
shape="multi"
else
shape="single"
fi
3a. Single-file → open file:// directly
open "$candidate"
# or on Linux: xdg-open "$candidate"
3b. Multi-file → serve over http and open
serve_dir="$(dirname "$candidate")"
# Pick a free port starting at 8765
port=8765
while lsof -iTCP:$port -sTCP:LISTEN >/dev/null 2>&1; do
port=$((port + 1))
done
log="/tmp/crumb-serve-$port.log"
( cd "$serve_dir" && python3 -m http.server "$port" >"$log" 2>&1 & )
sleep 0.5
open "http://localhost:$port/"
echo "served $serve_dir at http://localhost:$port/ (logs: $log)"
echo "stop with: pkill -f 'http.server $port'"
Notes:
- Always background the server (
( ... & )subshell) — never block the conversation thread. - Log file in
/tmp/so failures are diagnosable without re-running. - Port collision is real on shared dev boxes — increment until a free port is found.
- macOS uses
open, Linux usesxdg-open(detect withcommand -v).
4. Report
After opening, tell the user:
- Which session / pitch was picked (so they can correct if you guessed wrong)
- The URL (or
file://path) - The stop command (only when http server was started)
If multiple plausible candidates exist and you cannot disambiguate, list the
top 3 by mtime + 1-line spec.md snippet and ask the user to pick.
5. Stopping the server
When the user says "그만" / "stop" / "close the server" / "kill it":
# List active crumb-serve servers
lsof -nP -iTCP -sTCP:LISTEN 2>/dev/null | grep python3 | awk '{print $2, $9}'
# Stop the one we started
pkill -f 'http.server <port>'
Or aggressively: pkill -f 'http.server.*876[0-9]' for the 8760-8769 default range.
Don'ts
- ❌ Open multi-file games with
file://— Loading… forever, silent fail. - ❌ Run
python3 -m http.serverin the foreground — blocks the conversation. - ❌ Pick a candidate without checking mtime; the user almost always wants the most recent.
- ❌ Skip the
pitch keyworddisambiguation when the user supplied one — you'll open the wrong game and waste their time. - ❌ Hardcode
~/.crumb— read$CRUMB_HOMEenv var first, fall through to~/.crumbif unset (same convention as the rest of Crumb).
Examples
"고양이 퍼즐 열어줘" →
- find candidates, grep
cat|고양이|match-3against spec.md siblings - pick the most recent matching
index.html - detect
<script type="module">→ multi-file python3 -m http.server 8765in artifact dir, backgroundopen http://localhost:8765/- report: "Opened 고양이 퍼즐 (cat-tap-match3-v1, session 01KQMS9...) at http://localhost:8765/ — to stop:
pkill -f 'http.server 8765'"
"그 단순한 demo 열어줘" →
- find candidates, grep
single|demo|simple - pick most recent
game.html(single-file) - detect no
type="module"→ single-file open <path>directly- report: "Opened Drag-Color-Catcher (session 01KQMM7...) — file:// direct open"
Reference
- Multi-file vs single-file game shape:
agents/specialists/game-design.md§1 envelope - Session storage paths:
src/paths.tsgetCrumbHome()(honors$CRUMB_HOME) - Frontier convergence on multi-file PWA shape:
wiki/concepts/bagelcode-final-design-2026.md§3