1. Determine Requirements
Before doing anything else:
- •Check if user wants a shareable URL → sets
--no-openflag later - •Identify which MUI packages to include:
- •
@mui/material(always) - •
@mui/icons-material(if icons mentioned) - •
@mui/x-data-grid(if Data Grid mentioned) - •
@mui/x-date-pickers(if Date Pickers mentioned) - •
@mui/x-charts(if Charts mentioned) - •
@mui/x-tree-view(if Tree View mentioned)
- •
2. Create Demo Code
Write src/Demo.tsx:
- •Use
using-mui-componentsskill for quality MUI code - •Must have default export
- •Add extra MUI packages to
dependenciesin package.json if needed
3. Build Config & Execute
Build JSON config with all files (see File Templates below).
Write config using Bash heredoc (avoids Write tool "file not read" error):
cat > /tmp/stackblitz-config.json << 'EOF'
{ ... }
EOF
Run script — if user wants shareable URL, use --no-open:
# If user wants shareable URL: python3 <skill-dir>/create-stackblitz.py /tmp/stackblitz-config.json --no-open # Otherwise (opens in browser): python3 <skill-dir>/create-stackblitz.py /tmp/stackblitz-config.json
Resolve <skill-dir> from where this file was loaded.
4. Post-execution
If shareable URL requested
4.1 Check if agent-browser is installed:
command -v agent-browser
4.2 If NOT installed, ask user via AskUserQuestion:
- •Question: "agent-browser is required for shareable URLs but not installed. Install it?"
- •Options: "Yes, install it" / "No, I'll click Fork manually"
If yes, run:
npm install -g agent-browser && agent-browser install
If no, open sandbox normally and inform user to click Fork button.
4.3 If installed, run the fork workflow:
IMPORTANT: agent-browser prepends https:// to URLs, breaking file://. You MUST use local HTTP server.
# Get the HTML file path from script output (e.g., /var/folders/.../stackblitz-XXXXX.html)
HTML_FILE="stackblitz-XXXXX.html"
TEMP_DIR="/var/folders/.../T" # directory containing the HTML file
# Kill any existing server on port 8765
lsof -ti:8765 | xargs kill -9 2>/dev/null || true
# Start local server
python3 -m http.server 8765 --directory "$TEMP_DIR" &
SERVER_PID=$!
# Verify server is running
sleep 1
curl -s -o /dev/null http://localhost:8765/ || { echo "Server failed"; kill $SERVER_PID 2>/dev/null; exit 1; }
# Open in agent-browser (NEVER use file:// URLs)
agent-browser open "http://localhost:8765/$HTML_FILE"
# Wait for StackBlitz to load
agent-browser wait 5000
# Find and click Fork button
agent-browser snapshot -i # Look for 'button "Fork"'
agent-browser click "@eN" # Replace @eN with actual ref from snapshot
# Get forked URL
sleep 5
agent-browser get url
# Output: https://stackblitz.com/edit/<project-id>?file=src%2FDemo.tsx
# Cleanup
kill $SERVER_PID 2>/dev/null
If NOT requested
Inform user: "Click the Fork button in the sandbox to get a shareable URL."
File Templates
Config Structure
{
"title": "Project Title",
"description": "Project description",
"template": "node",
"initial_file": "src/Demo.tsx",
"files": {
"src/Demo.tsx": "...",
"src/index.tsx": "...",
"index.html": "...",
"package.json": "...",
"tsconfig.json": "...",
"tsconfig.node.json": "...",
"vite.config.ts": "..."
}
}
src/Demo.tsx
User-provided component. Must export default.
src/index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom/client";
import { StyledEngineProvider } from "@mui/material/styles";
import Demo from "./Demo";
ReactDOM.createRoot(document.querySelector("#root")!).render(
<React.StrictMode>
<StyledEngineProvider injectFirst>
<Demo />
</StyledEngineProvider>
</React.StrictMode>,
);
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>MUI Sandbox</title>
<meta name="viewport" content="initial-scale=1, width=device-width" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
package.json
CRITICAL: Must include dependencies and devDependencies with "latest" versions. Without these, sandbox won't run.
{
"private": true,
"description": "<description>",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@mui/material": "latest",
"react-dom": "latest",
"react": "latest",
"@emotion/react": "latest",
"@emotion/styled": "latest",
"typescript": "latest"
},
"devDependencies": {
"@vitejs/plugin-react": "latest",
"vite": "latest",
"@types/react-dom": "latest",
"@types/react": "latest"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
tsconfig.node.json
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
define: { "process.env": {} },
});