feat: add plan-mode-game hook and supportingFiles installer#481
feat: add plan-mode-game hook and supportingFiles installer#481
Conversation
Add entertainment/plan-mode-game hook component that launches a browser-based game hub (Dino Runner, Snake, Flappy Bird) while Claude plans. Games react to Claude's activity via SSE, pause on Notification, and show confetti on exit. Also implement supportingFiles field processing in the hook installer so hooks with multiple supporting files are properly downloaded and placed at their declared destinations. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
6 issues found across 9 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="cli-tool/components/hooks/entertainment/plan-mode-game.html">
<violation number="1" location="cli-tool/components/hooks/entertainment/plan-mode-game.html:934">
P1: Avoid rendering SSE-provided activity values via `innerHTML`; this allows HTML/script injection from `tool_name`/`tool_input`.</violation>
</file>
<file name="cli-tool/src/index.js">
<violation number="1" location="cli-tool/src/index.js:1163">
P1: Validate `supportingFiles[].destination` before writing files; current code allows path traversal/absolute-path writes outside the Claude directory.</violation>
</file>
<file name="cli-tool/components/hooks/entertainment/plan-mode-game-start.sh">
<violation number="1" location="cli-tool/components/hooks/entertainment/plan-mode-game-start.sh:24">
P2: Persist the background server PID after startup; otherwise the existing PID-file guard never works and duplicate servers can be launched.</violation>
</file>
<file name="cli-tool/components/hooks/entertainment/plan-mode-game-server.js">
<violation number="1" location="cli-tool/components/hooks/entertainment/plan-mode-game-server.js:25">
P2: Do not allow wildcard CORS on this local control server; it lets arbitrary sites call local state-changing endpoints.</violation>
<violation number="2" location="cli-tool/components/hooks/entertainment/plan-mode-game-server.js:116">
P1: Validate/coerce shared scores to numbers before signing; current direct HTML interpolation allows XSS in the verification page.</violation>
</file>
<file name="cli-tool/components/hooks/entertainment/plan-mode-game-announce.sh">
<violation number="1" location="cli-tool/components/hooks/entertainment/plan-mode-game-announce.sh:9">
P2: Add a short timeout to the `/done` POST so this hook cannot block when the local game server is unresponsive.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| item.className='activity-item new'; | ||
| const icon=TOOL_ICONS[toolName]||TOOL_ICONS.default; | ||
| const time=new Date().toLocaleTimeString('en',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'}); | ||
| item.innerHTML=`<div class="activity-icon">${icon}</div><div class="activity-content"><div class="activity-tool">${toolName}</div>${detail?`<div class="activity-detail">${detail}</div>`:''}</div><div class="activity-time">${time}</div>`; |
There was a problem hiding this comment.
P1: Avoid rendering SSE-provided activity values via innerHTML; this allows HTML/script injection from tool_name/tool_input.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cli-tool/components/hooks/entertainment/plan-mode-game.html, line 934:
<comment>Avoid rendering SSE-provided activity values via `innerHTML`; this allows HTML/script injection from `tool_name`/`tool_input`.</comment>
<file context>
@@ -0,0 +1,1076 @@
+ item.className='activity-item new';
+ const icon=TOOL_ICONS[toolName]||TOOL_ICONS.default;
+ const time=new Date().toLocaleTimeString('en',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'});
+ item.innerHTML=`<div class="activity-icon">${icon}</div><div class="activity-content"><div class="activity-tool">${toolName}</div>${detail?`<div class="activity-detail">${detail}</div>`:''}</div><div class="activity-time">${time}</div>`;
+ list.appendChild(item);
+ list.scrollTop=list.scrollHeight;
</file context>
| const sfResponse = await fetch(fileUrl); | ||
| if (sfResponse.ok) { | ||
| const sfContent = await sfResponse.text(); | ||
| additionalFiles[sf.destination] = { |
There was a problem hiding this comment.
P1: Validate supportingFiles[].destination before writing files; current code allows path traversal/absolute-path writes outside the Claude directory.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cli-tool/src/index.js, line 1163:
<comment>Validate `supportingFiles[].destination` before writing files; current code allows path traversal/absolute-path writes outside the Claude directory.</comment>
<file context>
@@ -1150,9 +1150,34 @@ async function installIndividualHook(hookName, targetDir, options) {
+ const sfResponse = await fetch(fileUrl);
+ if (sfResponse.ok) {
+ const sfContent = await sfResponse.text();
+ additionalFiles[sf.destination] = {
+ content: sfContent,
+ executable: sf.executable === true
</file context>
| req.on('end', () => { | ||
| try { | ||
| const { dino, snake, flappy } = JSON.parse(body); | ||
| const payload = { d: dino || 0, s: snake || 0, f: flappy || 0, t: Date.now() }; |
There was a problem hiding this comment.
P1: Validate/coerce shared scores to numbers before signing; current direct HTML interpolation allows XSS in the verification page.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cli-tool/components/hooks/entertainment/plan-mode-game-server.js, line 116:
<comment>Validate/coerce shared scores to numbers before signing; current direct HTML interpolation allows XSS in the verification page.</comment>
<file context>
@@ -0,0 +1,225 @@
+ req.on('end', () => {
+ try {
+ const { dino, snake, flappy } = JSON.parse(body);
+ const payload = { d: dino || 0, s: snake || 0, f: flappy || 0, t: Date.now() };
+ const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
+ const sig = crypto.createHmac('sha256', hmacSecret).update(payloadB64).digest('hex').slice(0, 16);
</file context>
| fi | ||
|
|
||
| # Start server in background | ||
| nohup node "$SCRIPT_DIR/server.js" > /dev/null 2>&1 & |
There was a problem hiding this comment.
P2: Persist the background server PID after startup; otherwise the existing PID-file guard never works and duplicate servers can be launched.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cli-tool/components/hooks/entertainment/plan-mode-game-start.sh, line 24:
<comment>Persist the background server PID after startup; otherwise the existing PID-file guard never works and duplicate servers can be launched.</comment>
<file context>
@@ -0,0 +1,35 @@
+fi
+
+# Start server in background
+nohup node "$SCRIPT_DIR/server.js" > /dev/null 2>&1 &
+
+# Wait for server to be ready
</file context>
| const hmacSecret = getSecret(); | ||
|
|
||
| const server = http.createServer((req, res) => { | ||
| res.setHeader('Access-Control-Allow-Origin', '*'); |
There was a problem hiding this comment.
P2: Do not allow wildcard CORS on this local control server; it lets arbitrary sites call local state-changing endpoints.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cli-tool/components/hooks/entertainment/plan-mode-game-server.js, line 25:
<comment>Do not allow wildcard CORS on this local control server; it lets arbitrary sites call local state-changing endpoints.</comment>
<file context>
@@ -0,0 +1,225 @@
+const hmacSecret = getSecret();
+
+const server = http.createServer((req, res) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
</file context>
| PORT=3456 | ||
|
|
||
| # Send announcement | ||
| curl -s -X POST "http://localhost:$PORT/done" -d "Claude Finished!" > /dev/null 2>&1 |
There was a problem hiding this comment.
P2: Add a short timeout to the /done POST so this hook cannot block when the local game server is unresponsive.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cli-tool/components/hooks/entertainment/plan-mode-game-announce.sh, line 9:
<comment>Add a short timeout to the `/done` POST so this hook cannot block when the local game server is unresponsive.</comment>
<file context>
@@ -0,0 +1,18 @@
+PORT=3456
+
+# Send announcement
+curl -s -X POST "http://localhost:$PORT/done" -d "Claude Finished!" > /dev/null 2>&1
+
+# Remove plan-active flag
</file context>
| curl -s -X POST "http://localhost:$PORT/done" -d "Claude Finished!" > /dev/null 2>&1 | |
| curl -s --max-time 1 -X POST "http://localhost:$PORT/done" -d "Claude Finished!" > /dev/null 2>&1 || true |
PR #481 Review: Plan Mode Game HookOverviewThis PR introduces an entertaining and innovative feature that launches browser-based mini-games (Dino Runner, Snake, Flappy Bird) while Claude is in Plan Mode, with real-time activity tracking via SSE. Usefulness Assessment: ⭐⭐⭐⭐ (4/5)Strengths1. Creative Developer Experience Enhancement
2. Well-Architected Implementation
3. Non-Intrusive Design
4. Supporting Files Feature
Weaknesses & Security ConcernsCritical Issues (P1):
Important Issues (P2):
RecommendationsImmediate Fixes Required// Fix 1: Sanitize activity rendering
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// Use: item.textContent or escapeHtml() instead of innerHTML// Fix 2: Validate destination paths
const path = require('path');
const claudeDir = path.resolve(targetDir, '.claude');
const destPath = path.resolve(targetDir, sf.destination);
if (!destPath.startsWith(claudeDir)) {
throw new Error(`Invalid destination path: ${sf.destination}`);
}// Fix 3: Validate scores
const payload = {
d: Math.max(0, parseInt(dino) || 0),
s: Math.max(0, parseInt(snake) || 0),
f: Math.max(0, parseInt(flappy) || 0),
t: Date.now()
};# Fix 4: Persist PID
nohup node "$SCRIPT_DIR/server.js" > /dev/null 2>&1 &
echo $! > "$PID_FILE"// Fix 5: Restrict CORS
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3456');# Fix 6: Add timeout
curl -s --max-time 1 -X POST "http://localhost:$PORT/done" -d "Claude Finished!" > /dev/null 2>&1 || trueFuture Improvements
ConclusionThis PR is highly creative and adds genuine value to the developer experience. The concept is sound and the implementation demonstrates good architectural thinking. However, security issues must be addressed before merging. Recommendation: Approve with Required ChangesThe security vulnerabilities are straightforward to fix and don't require architectural changes. Once the 6 identified issues are resolved, this feature will be a delightful addition to the project. Impact on Project
Overall Assessment: Innovative feature with solid execution that needs security hardening before merge. The |
Summary
entertainment/plan-mode-game: launches a browser-based game hub (Dino Runner, Snake, Flappy Bird) while Claude is in Plan Mode. Games react to Claude's tool activity via SSE, pause when Claude needs input, and show a confetti announcement when planning completes.supportingFilesfield processing ininstallIndividualHook()so hooks with multiple supporting files (scripts, HTML, JS) are downloaded and placed at their declared destinations. Previously only.pyand.shfiles with matching names were detected.Test plan
npx claude-code-templates@latest --hook entertainment/plan-mode-game.claude/hooks/plan-mode-game/🤖 Generated with Claude Code
Summary by cubic
Adds a new
entertainment/plan-mode-gamehook that launches a small game hub during Plan Mode and reacts to tool activity, plus installer support for multi-file hooks via a newsupportingFilesfield.cli-tool/components/hooks/entertainment/*), CLI (cli-tool/src/index.js).entertainment/plan-mode-game(Dino, Snake, Flappy). Shows live tool activity via SSE, pauses on Notification, and announces completion.installIndividualHook()now processessupportingFiles, downloads assets to declared destinations, and strips non-settings fields before merge..plan-active,server.pid, and a local.secretHMAC file. No new env vars or external secrets.docs/components.json.Written for commit 7835dfb. Summary will update on new commits.