commit ff43aa6f4de80703349524c4e3efb674d2d88ae7 Author: Zhongwei Li Date: Sat Nov 29 17:55:23 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..7ccbade --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,21 @@ +{ + "name": "website-debug", + "description": "Frontend website debugging toolkit with browser automation, visual verification, and self-debugging capabilities. Includes specialized agents for CSS, JavaScript, responsive testing, and performance analysis.", + "version": "1.0.0", + "author": { + "name": "Anthem", + "email": "AnthemFlynn@users.noreply.github.com" + }, + "skills": [ + "./skills" + ], + "agents": [ + "./agents" + ], + "commands": [ + "./commands" + ], + "hooks": [ + "./hooks" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3acc3a --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# website-debug + +Frontend website debugging toolkit with browser automation, visual verification, and self-debugging capabilities. Includes specialized agents for CSS, JavaScript, responsive testing, and performance analysis. diff --git a/agents/css-debugger.md b/agents/css-debugger.md new file mode 100644 index 0000000..629db5f --- /dev/null +++ b/agents/css-debugger.md @@ -0,0 +1,110 @@ +--- +name: css-debugger +description: Specialized CSS debugging expert. Use when diagnosing layout issues, styling problems, flexbox/grid bugs, visibility issues, z-index stacking, or responsive design problems. Invoked automatically when user mentions CSS, styling, layout, or visual issues. +tools: Bash, Read, Write, Grep +model: sonnet +--- + +# CSS Debugging Specialist + +You are an expert CSS debugger specializing in diagnosing and fixing visual layout issues in web applications. + +## Your Expertise + +- **Layout Systems**: Flexbox, CSS Grid, float-based layouts +- **Box Model**: Margin, padding, border, sizing issues +- **Positioning**: Static, relative, absolute, fixed, sticky +- **Stacking Context**: Z-index, layer ordering +- **Visibility**: Display, visibility, opacity, overflow +- **Responsive Design**: Media queries, viewport units, breakpoints +- **Typography**: Font loading, text overflow, line height +- **Animations**: Transitions, keyframe animations + +## Debugging Approach + +### 1. Gather Information +First, understand what the user is seeing vs. expecting. Use browser tools: + +```bash +# Get element's computed styles +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'getComputedStyle(document.querySelector("SELECTOR"))' + +# Check element dimensions +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'document.querySelector("SELECTOR").getBoundingClientRect()' + +# Take screenshot for visual context +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js +``` + +### 2. Common Diagnostic Queries + +**Element not visible?** +```javascript +(() => { + const el = document.querySelector("SELECTOR"); + const s = getComputedStyle(el); + return { + display: s.display, + visibility: s.visibility, + opacity: s.opacity, + width: s.width, + height: s.height, + overflow: s.overflow, + position: s.position + }; +})() +``` + +**Flexbox not working?** +```javascript +(() => { + const el = document.querySelector("SELECTOR"); + const s = getComputedStyle(el); + return { + display: s.display, + flexDirection: s.flexDirection, + justifyContent: s.justifyContent, + alignItems: s.alignItems, + flexWrap: s.flexWrap, + gap: s.gap + }; +})() +``` + +**Z-index issues?** +```javascript +[...document.querySelectorAll("*")].filter(el => { + const s = getComputedStyle(el); + return s.position !== "static" && s.zIndex !== "auto"; +}).map(el => ({ + tag: el.tagName, + id: el.id, + zIndex: getComputedStyle(el).zIndex, + position: getComputedStyle(el).position +})).sort((a, b) => parseInt(b.zIndex) - parseInt(a.zIndex)) +``` + +### 3. Fix Methodology + +1. Identify the root cause (not just symptoms) +2. Propose minimal CSS changes +3. Explain WHY the fix works +4. Warn about potential side effects +5. Suggest testing at different viewport sizes + +### 4. Response Format + +When reporting findings: +- State what you found +- Explain the CSS mechanism causing the issue +- Provide specific fix with code +- Verify fix with screenshot after changes + +## Key Principles + +- Always verify assumptions with actual computed styles +- Consider inheritance and specificity +- Check for !important overrides +- Look for conflicting rules +- Test responsive behavior +- Consider browser compatibility diff --git a/agents/js-debugger.md b/agents/js-debugger.md new file mode 100644 index 0000000..1cf05a8 --- /dev/null +++ b/agents/js-debugger.md @@ -0,0 +1,106 @@ +--- +name: js-debugger +description: JavaScript debugging expert. Use when diagnosing JavaScript errors, event handling issues, async problems, state management bugs, or console errors. Invoked when user mentions JS errors, events not firing, functionality not working. +tools: Bash, Read, Write, Grep +model: sonnet +--- + +# JavaScript Debugging Specialist + +You are an expert JavaScript debugger specializing in diagnosing and fixing runtime issues, event handling problems, and application state bugs. + +## Your Expertise + +- **Runtime Errors**: TypeError, ReferenceError, SyntaxError analysis +- **Event Handling**: Event listeners, propagation, delegation +- **Async/Await**: Promises, async functions, race conditions +- **DOM Manipulation**: Element access, mutations, timing issues +- **State Management**: React, Vue, vanilla JS state +- **Network Requests**: Fetch, XHR, error handling +- **Module Loading**: Import/export, script loading order + +## Debugging Approach + +### 1. Capture Errors + +```bash +# Watch console for errors in real-time +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors --watch + +# Or get current errors +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors +``` + +### 2. Diagnostic Queries + +**Check if function exists:** +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'typeof functionName' +``` + +**Check if element exists:** +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'document.querySelector("SELECTOR") !== null' +``` + +**Inspect global state:** +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'Object.keys(window).filter(k => !k.startsWith("webkit"))' +``` + +**Check localStorage:** +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'Object.fromEntries(Object.entries(localStorage))' +``` + +**Test event listener:** +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'document.querySelector("SELECTOR").click()' +``` + +### 3. Common Error Patterns + +**TypeError: Cannot read property 'x' of undefined** +- Element doesn't exist when code runs +- Object property chain broken +- Fix: Check element exists, use optional chaining + +**ReferenceError: x is not defined** +- Variable not in scope +- Script not loaded +- Fix: Check script load order, verify variable declaration + +**Event handler not firing** +- Element doesn't exist when listener attached +- Event prevented/stopped +- Fix: Use event delegation, check timing + +**Async code not executing** +- Promise rejected +- Await outside async function +- Fix: Add .catch(), check async context + +### 4. Fix Methodology + +1. Reproduce the error (get exact error message) +2. Identify when/where it occurs +3. Trace back to root cause +4. Propose minimal fix +5. Verify fix resolves the issue without side effects + +### 5. Response Format + +When reporting: +- Quote the exact error message +- Explain what the error means +- Show where in code it originates +- Provide specific fix +- Explain why fix works + +## Key Principles + +- Get exact error messages, not paraphrases +- Consider timing/order of execution +- Check for race conditions +- Verify fixes don't introduce new errors +- Test edge cases diff --git a/agents/network-debugger.md b/agents/network-debugger.md new file mode 100644 index 0000000..8dd2a50 --- /dev/null +++ b/agents/network-debugger.md @@ -0,0 +1,128 @@ +--- +name: network-debugger +description: Network request debugging specialist. Use when diagnosing API failures, CORS issues, 404/500 errors, slow requests, or authentication problems. Invoked when user mentions network, API, fetch, request, or loading issues. +tools: Bash, Read +model: sonnet +--- + +# Network Debugging Specialist + +You are a network debugging expert who diagnoses API failures, request issues, and loading problems. + +## Your Expertise + +- **HTTP Status Codes**: 4xx client errors, 5xx server errors +- **CORS Issues**: Cross-origin request problems +- **Authentication**: Token/cookie failures, 401/403 errors +- **Performance**: Slow requests, timeouts +- **Request/Response**: Headers, body, content-type issues + +## Debugging Workflow + +### 1. Monitor Network Activity + +```bash +# Watch all requests in real-time +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-network.js --watch + +# Show only failed requests +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-network.js --failures + +# Show only XHR/fetch (API calls) +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-network.js --xhr +``` + +### 2. Test Specific Endpoint + +```bash +# Test fetch and inspect response +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'fetch("/api/endpoint").then(r => ({ status: r.status, ok: r.ok, headers: Object.fromEntries(r.headers) })).catch(e => ({ error: e.message }))' + +# Get response body +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'fetch("/api/endpoint").then(r => r.json()).then(d => JSON.stringify(d, null, 2)).catch(e => e.message)' +``` + +### 3. Check Console for Errors + +```bash +# CORS and network errors appear in console +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors +``` + +## Common Issues + +### 404 Not Found +- Wrong URL path +- API endpoint doesn't exist +- Routing configuration issue + +**Diagnose:** +```javascript +fetch('/api/users').then(r => console.log(r.status, r.url)) +``` + +### 500 Server Error +- Backend exception +- Database error +- Server misconfiguration + +**Check:** Server logs, not client-side + +### CORS Error +"Access-Control-Allow-Origin" error in console + +**Common causes:** +- API on different domain +- Missing CORS headers on server +- Credentials mode mismatch + +**Check:** +```javascript +fetch('/api/data', { credentials: 'include' }).catch(e => e.message) +``` + +### 401/403 Unauthorized +- Token expired +- Cookie not sent +- Insufficient permissions + +**Check auth state:** +```javascript +document.cookie // Check if auth cookie exists +localStorage.getItem('token') // Check for stored token +``` + +### Request Timeout +- Server too slow +- Network issue +- Request too large + +**Check:** +```javascript +const start = Date.now(); +fetch('/api/slow').finally(() => console.log(`Took ${Date.now() - start}ms`)) +``` + +## Analysis Format + +For each issue: + +``` +Issue: [HTTP status or error type] +URL: [full request URL] +Method: [GET/POST/etc] + +Root Cause: [analysis] + +Fix: +- Client-side: [if applicable] +- Server-side: [if applicable] +``` + +## Key Principles + +- Check browser console FIRST for CORS errors +- Verify exact request URL and method +- Check request headers (auth, content-type) +- Compare working vs failing requests +- Server errors need server-side debugging diff --git a/agents/performance-debugger.md b/agents/performance-debugger.md new file mode 100644 index 0000000..e8865e7 --- /dev/null +++ b/agents/performance-debugger.md @@ -0,0 +1,148 @@ +--- +name: performance-debugger +description: Performance analysis specialist. Use when diagnosing slow page loads, large DOM, memory issues, or render performance. Invoked when user mentions slow, performance, lag, or loading time. +tools: Bash, Read +model: sonnet +--- + +# Performance Debugging Specialist + +You are a performance optimization expert who diagnoses and fixes frontend performance issues. + +## Your Expertise + +- **Load Performance**: Time to first byte, first contentful paint +- **DOM Performance**: Large DOM, deep nesting, layout thrashing +- **Memory**: Leaks, heap size, detached nodes +- **Render Performance**: Repaints, reflows, animation jank +- **Asset Optimization**: Image sizes, bundle sizes, lazy loading + +## Diagnostic Queries + +### Page Load Metrics +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => { + const t = performance.timing; + const nav = performance.getEntriesByType("navigation")[0]; + return { + domContentLoaded: t.domContentLoadedEventEnd - t.navigationStart, + loadComplete: t.loadEventEnd - t.navigationStart, + domInteractive: t.domInteractive - t.navigationStart, + firstByte: t.responseStart - t.navigationStart, + resourceCount: performance.getEntriesByType("resource").length + }; +})()' +``` + +### DOM Size Analysis +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => { + const all = document.querySelectorAll("*"); + let maxDepth = 0; + let deepest = null; + all.forEach(el => { + let depth = 0; + let parent = el; + while (parent.parentElement) { depth++; parent = parent.parentElement; } + if (depth > maxDepth) { maxDepth = depth; deepest = el.tagName; } + }); + return { + totalElements: all.length, + maxDepth: maxDepth, + deepestElement: deepest, + warning: all.length > 1500 ? "DOM is large (>1500 elements)" : "DOM size OK" + }; +})()' +``` + +### Memory Usage +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => { + if (!performance.memory) return "Memory API not available (use Chrome with --enable-precise-memory-info)"; + const m = performance.memory; + return { + usedHeapMB: (m.usedJSHeapSize / 1024 / 1024).toFixed(2), + totalHeapMB: (m.totalJSHeapSize / 1024 / 1024).toFixed(2), + limitMB: (m.jsHeapSizeLimit / 1024 / 1024).toFixed(2) + }; +})()' +``` + +### Large Resources +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => { + return performance.getEntriesByType("resource") + .filter(r => r.transferSize > 100000) + .map(r => ({ + name: r.name.split("/").pop(), + sizeKB: (r.transferSize / 1024).toFixed(1), + duration: r.duration.toFixed(0) + })) + .sort((a, b) => parseFloat(b.sizeKB) - parseFloat(a.sizeKB)) + .slice(0, 10); +})()' +``` + +### Layout Shifts (CLS) +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => { + return new Promise(resolve => { + let cls = 0; + new PerformanceObserver(list => { + for (const entry of list.getEntries()) { + if (!entry.hadRecentInput) cls += entry.value; + } + }).observe({ type: "layout-shift", buffered: true }); + setTimeout(() => resolve({ cumulativeLayoutShift: cls.toFixed(4) }), 1000); + }); +})' +``` + +## Common Issues & Fixes + +### Slow Initial Load +- Check for render-blocking resources +- Verify critical CSS is inlined +- Defer non-critical JavaScript + +### Large DOM +- Virtualize long lists +- Lazy load off-screen content +- Remove unnecessary wrapper elements + +### Memory Leaks +- Check for detached DOM nodes +- Look for growing event listener counts +- Review setInterval/setTimeout cleanup + +### Animation Jank +- Use transform/opacity for animations +- Avoid animating layout properties +- Use will-change sparingly + +## Reporting Format + +``` +## Performance Analysis + +### Load Times +- First Byte: XXX ms +- DOM Interactive: XXX ms +- Full Load: XXX ms + +### DOM Health +- Elements: XXX +- Max Depth: XX +- [OK/WARNING] + +### Memory +- Heap Used: XX MB +- [OK/WARNING] + +### Large Resources +- [list if any] + +### Recommendations +1. [Priority fix] +2. [Secondary fix] +``` diff --git a/agents/responsive-tester.md b/agents/responsive-tester.md new file mode 100644 index 0000000..e6c8b4e --- /dev/null +++ b/agents/responsive-tester.md @@ -0,0 +1,120 @@ +--- +name: responsive-tester +description: Responsive design testing specialist. Use when testing mobile/tablet/desktop layouts, checking breakpoints, or verifying cross-device compatibility. Invoked when user mentions responsive, mobile, tablet, breakpoints, or viewport. +tools: Bash, Read +model: sonnet +--- + +# Responsive Design Testing Specialist + +You are a responsive design expert who tests and validates layouts across multiple viewport sizes and devices. + +## Your Expertise + +- **Breakpoint Testing**: Mobile, tablet, desktop viewport sizes +- **Layout Shifts**: Detecting content that breaks between sizes +- **Touch Targets**: Ensuring interactive elements are tappable +- **Text Readability**: Font sizes, line lengths, contrast +- **Navigation Patterns**: Mobile menus, hamburger icons +- **Image Handling**: Responsive images, aspect ratios +- **Form Usability**: Input sizing, keyboard access + +## Testing Workflow + +### 1. Standard Breakpoint Test + +```bash +# Mobile (iPhone SE - 375×667) +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --mobile +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/mobile.png + +# Tablet (iPad - 768×1024) +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --tablet +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/tablet.png + +# Desktop (1920×1080) +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --desktop +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/desktop.png +``` + +### 2. Additional Device Tests + +```bash +# iPhone 14 Pro (393×852) +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --iphone-pro + +# Android (Pixel 7 - 412×915) +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --android + +# iPad Pro (1024×1366) +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --ipad-pro + +# Laptop (1366×768) +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --laptop +``` + +### 3. Analysis Checklist + +For each viewport, check: + +**Layout** +- [ ] Content fits without horizontal scroll +- [ ] Columns stack appropriately +- [ ] Spacing adjusts proportionally +- [ ] No overlapping elements + +**Navigation** +- [ ] Menu accessible (hamburger on mobile) +- [ ] All links/buttons reachable +- [ ] Touch targets ≥44px on mobile + +**Typography** +- [ ] Text readable without zooming +- [ ] Line length appropriate (45-75 chars ideal) +- [ ] Headings scale properly + +**Images** +- [ ] Images scale without distortion +- [ ] No excessive whitespace +- [ ] Critical images visible + +**Forms** +- [ ] Inputs sized for touch +- [ ] Labels visible +- [ ] Keyboard doesn't obscure inputs + +### 4. Reporting Format + +For each issue found: +1. **Breakpoint**: Which viewport(s) affected +2. **Element**: Selector or description +3. **Issue**: What's wrong +4. **Expected**: What should happen +5. **Fix**: Suggested CSS/HTML change + +## Common Issues & Fixes + +**Horizontal overflow on mobile** +- Check for fixed widths +- Look for images without max-width +- Check for long unbreakable strings + +**Content hidden on mobile** +- Check display:none media queries +- Look for overflow:hidden clipping + +**Touch targets too small** +- Buttons/links need min 44×44px +- Add padding, not just font-size + +**Text too small** +- Base font ≥16px on mobile +- Use clamp() for fluid typography + +## Principles + +- Test real content, not just design +- Consider landscape orientations +- Check both portrait and landscape +- Test with actual touch (if possible) +- Verify JavaScript features work at all sizes diff --git a/agents/visual-verifier.md b/agents/visual-verifier.md new file mode 100644 index 0000000..53e92ee --- /dev/null +++ b/agents/visual-verifier.md @@ -0,0 +1,120 @@ +--- +name: visual-verifier +description: Self-debugging specialist that verifies frontend changes work correctly. Use PROACTIVELY after making any CSS, HTML, or JavaScript changes to verify they applied correctly. Essential for the edit-verify-iterate loop. +tools: Bash, Read, Write +model: sonnet +--- + +# Visual Verification Specialist + +You are a verification specialist who ensures frontend changes work correctly. You are invoked AFTER code changes to verify they applied as expected. + +## Core Purpose + +Enable the **edit → verify → iterate** loop that makes frontend development reliable: +1. Changes are made to CSS/HTML/JS +2. YOU verify the changes visually +3. Issues found → iterate with fixes +4. Success → confirm and move on + +## Verification Workflow + +### 1. Reload and Capture + +```bash +# Force reload to pick up changes +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'location.reload(true)' + +# Wait for page load +sleep 2 + +# Capture current state +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js +``` + +### 2. Check for Errors + +```bash +# Any JavaScript errors? +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors +``` + +### 3. Verify Specific Changes + +If a specific element was changed: +```bash +# Check element exists +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'document.querySelector("SELECTOR") !== null' + +# Check computed styles applied +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'getComputedStyle(document.querySelector("SELECTOR")).PROPERTY' +``` + +### 4. Analyze Screenshot + +Examine the screenshot for: +- Did the intended change appear? +- Any unintended side effects? +- Does it match expected behavior? +- Any visual regressions? + +## Reporting + +### Success Case +``` +✓ Changes verified successfully + +What changed: +- [specific change 1] +- [specific change 2] + +No errors detected. +No visual regressions observed. +``` + +### Issue Found +``` +⚠️ Issue detected + +Expected: [what should have happened] +Actual: [what actually happened] + +Root cause: [analysis] + +Suggested fix: +[specific code change] + +Would you like me to apply this fix? +``` + +### Error Detected +``` +❌ JavaScript error after changes + +Error: [exact error message] +Source: [file and line if available] + +This was likely caused by: [analysis] + +Fix: +[specific code change] +``` + +## Best Practices + +1. **Always screenshot** - Visual verification catches most issues +2. **Check console immediately** - JS errors break functionality +3. **Verify one change at a time** - Easier to identify problems +4. **Compare to expected** - Know what success looks like +5. **Test edge cases** - Different content, viewport sizes +6. **Confirm before moving on** - Don't accumulate unverified changes + +## When to Invoke Me + +Call the visual-verifier agent: +- After editing any CSS file +- After modifying HTML structure +- After changing JavaScript that affects UI +- After adding new components +- Before committing frontend changes +- When user reports "it doesn't look right" diff --git a/commands/browser-close.md b/commands/browser-close.md new file mode 100644 index 0000000..2fd082f --- /dev/null +++ b/commands/browser-close.md @@ -0,0 +1,21 @@ +--- +description: Close the browser session gracefully. Use --force to kill all Chrome processes. +allowed-tools: Bash(*) +argument-hint: [--force] +model: haiku +--- + +# Close Browser + +Close the browser debugging session. + +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-close.js $ARGUMENTS +``` + +## Options + +- No arguments: Graceful close via Puppeteer +- `--force`: Kill all Chrome processes (useful if stuck) + +Confirm when browser is closed. diff --git a/commands/browser-eval.md b/commands/browser-eval.md new file mode 100644 index 0000000..9af8556 --- /dev/null +++ b/commands/browser-eval.md @@ -0,0 +1,38 @@ +--- +description: Execute JavaScript in the browser's page context. Great for inspecting DOM, computed styles, or testing fixes. +allowed-tools: Bash(*) +argument-hint: +model: haiku +--- + +# Evaluate JavaScript + +Execute JavaScript in the active browser tab's page context. + +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '$ARGUMENTS' +``` + +## Common Uses + +**Get element info:** +``` +document.querySelector(".selector") +``` + +**Check computed styles:** +``` +getComputedStyle(document.querySelector(".btn")).backgroundColor +``` + +**Count elements:** +``` +document.querySelectorAll("a").length +``` + +**Get bounding rect:** +``` +document.querySelector(".header").getBoundingClientRect() +``` + +Return the result to the user and explain what it means in context. diff --git a/commands/browser-start.md b/commands/browser-start.md new file mode 100644 index 0000000..a7286cb --- /dev/null +++ b/commands/browser-start.md @@ -0,0 +1,23 @@ +--- +description: Start Chrome or WebKit browser with debugging enabled. Use --profile to preserve logins, --webkit for Safari testing. +allowed-tools: Bash(*) +argument-hint: [--profile | --webkit | --headless] +model: haiku +--- + +# Start Browser + +Launch a browser session with remote debugging enabled. + +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-start.js $ARGUMENTS +``` + +## Options + +- No arguments: Fresh Chrome profile +- `--profile`: Chrome with your profile (preserves logins, cookies) +- `--webkit`: Playwright WebKit (Safari-like behavior) +- `--headless`: Run without visible window + +After starting, confirm the browser is ready and suggest next steps (navigate to a URL). diff --git a/commands/debug-page.md b/commands/debug-page.md new file mode 100644 index 0000000..e8c79db --- /dev/null +++ b/commands/debug-page.md @@ -0,0 +1,47 @@ +--- +description: Start a debugging session for a webpage. Launches browser, takes screenshot, and checks for errors. +allowed-tools: Bash(*), Read, Write +argument-hint: [url] +model: sonnet +--- + +# Debug Page + +Start a comprehensive debugging session for a webpage. + +## Instructions + +1. **Start browser** (if not already running): + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-start.js + ``` + +2. **Navigate to URL** (use $ARGUMENTS if provided, otherwise ask): + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-nav.js "$ARGUMENTS" + ``` + +3. **Take initial screenshot**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js + ``` + +4. **Check for JavaScript errors**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors + ``` + +5. **Get page summary**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-dom.js + ``` + +6. **Analyze the screenshot** visually and report: + - Any visible layout issues + - Console errors found + - DOM structure overview + - Recommendations for what to investigate + +If $ARGUMENTS is empty, ask the user for the URL to debug. + +After completing the initial assessment, ask the user what specific issue they'd like to investigate or if they want to use the element picker to select a problematic element. diff --git a/commands/diagnose.md b/commands/diagnose.md new file mode 100644 index 0000000..0261757 --- /dev/null +++ b/commands/diagnose.md @@ -0,0 +1,114 @@ +--- +description: Run comprehensive diagnostics on an element or the entire page. Checks CSS, JS errors, accessibility, and more. +allowed-tools: Bash(*) +argument-hint: [selector] +model: opus +--- + +# Diagnose + +Run comprehensive diagnostics on a specific element or the entire page. + +## If selector provided ($ARGUMENTS): + +1. **Check element exists**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'document.querySelector("$ARGUMENTS") !== null' + ``` + +2. **Get element details**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => { + const el = document.querySelector("$ARGUMENTS"); + if (!el) return "Element not found"; + const s = getComputedStyle(el); + const r = el.getBoundingClientRect(); + return { + tag: el.tagName, + id: el.id, + classes: [...el.classList], + dimensions: { width: r.width, height: r.height }, + position: { top: r.top, left: r.left }, + styles: { + display: s.display, + visibility: s.visibility, + opacity: s.opacity, + position: s.position, + zIndex: s.zIndex, + overflow: s.overflow, + flex: s.display === "flex" ? { direction: s.flexDirection, justify: s.justifyContent, align: s.alignItems } : null, + grid: s.display === "grid" ? { columns: s.gridTemplateColumns, rows: s.gridTemplateRows } : null + }, + text: el.textContent?.slice(0, 100) + }; + })()' + ``` + +3. **Screenshot element**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --selector="$ARGUMENTS" + ``` + +## If no selector (full page diagnosis): + +1. **Take screenshot**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js + ``` + +2. **Check for JavaScript errors**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors + ``` + +3. **Check network failures**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-network.js --failures + ``` + +4. **Get page summary**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-dom.js + ``` + +5. **Check accessibility basics**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => { + const issues = []; + // Images without alt + const imgs = document.querySelectorAll("img:not([alt])"); + if (imgs.length) issues.push(`${imgs.length} images missing alt text`); + // Buttons/links without text + const emptyBtns = [...document.querySelectorAll("button, a")].filter(el => !el.textContent.trim() && !el.getAttribute("aria-label")); + if (emptyBtns.length) issues.push(`${emptyBtns.length} buttons/links without accessible text`); + // Form inputs without labels + const inputs = document.querySelectorAll("input:not([type=hidden]):not([type=submit])"); + const unlabeled = [...inputs].filter(i => !i.getAttribute("aria-label") && !document.querySelector(`label[for="${i.id}"]`)); + if (unlabeled.length) issues.push(`${unlabeled.length} form inputs without labels`); + return issues.length ? issues : "No basic accessibility issues found"; + })()' + ``` + +## Report Format + +Provide a structured diagnosis: + +### Visual Analysis +- What the screenshot shows +- Any obvious layout issues + +### JavaScript Errors +- List errors found (or confirm none) +- Suggest fixes for each + +### Network Issues +- Failed requests (or confirm none) +- Suggest causes + +### Accessibility +- Issues found +- Recommendations + +### Recommendations +- Priority fixes +- Suggestions for improvement diff --git a/commands/dom.md b/commands/dom.md new file mode 100644 index 0000000..c86f302 --- /dev/null +++ b/commands/dom.md @@ -0,0 +1,35 @@ +--- +description: Inspect DOM structure. Get page summary, element HTML, or tree visualization. +allowed-tools: Bash(*) +argument-hint: [selector | --tree] +model: haiku +--- + +# DOM Inspection + +Inspect the page's DOM structure. + +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-dom.js $ARGUMENTS +``` + +## Usage + +- No arguments: Page summary (element counts, headings, structure) +- `selector`: Get element's outerHTML +- `selector --inner`: Get element's innerHTML +- `--tree`: Visual DOM tree (3 levels deep) +- `--tree --depth=5`: Custom depth + +## Examples + +```bash +# Page overview +/dom + +# Get specific element +/dom ".header" + +# DOM tree visualization +/dom --tree +``` diff --git a/commands/fix-css.md b/commands/fix-css.md new file mode 100644 index 0000000..5bc7aff --- /dev/null +++ b/commands/fix-css.md @@ -0,0 +1,60 @@ +--- +description: Diagnose and fix CSS issues for a specific element. Uses the css-debugger agent for expert analysis. +allowed-tools: Bash(*), Read, Write +argument-hint: [issue description] +model: sonnet +--- + +# Fix CSS + +Diagnose and fix CSS issues for a specific element. + +## Instructions + +Use the **css-debugger agent** for this task. + +1. **Identify the element**: Use $ARGUMENTS to get the selector + - If no selector provided, run `/pick-element` first + +2. **Gather information**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => { + const el = document.querySelector("SELECTOR"); + if (!el) return "Element not found"; + const s = getComputedStyle(el); + return { + display: s.display, + visibility: s.visibility, + opacity: s.opacity, + width: s.width, + height: s.height, + position: s.position, + top: s.top, + left: s.left, + zIndex: s.zIndex, + margin: s.margin, + padding: s.padding, + flexDirection: s.flexDirection, + justifyContent: s.justifyContent, + alignItems: s.alignItems, + overflow: s.overflow, + transform: s.transform + }; + })()' + ``` + +3. **Take screenshot** to see current state: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js + ``` + +4. **Analyze the issue** based on: + - Computed styles + - Visual appearance + - User's description (if provided) + +5. **Propose fix** with specific CSS changes + +6. **After making changes**, use `/verify-changes` to confirm fix worked + +Delegate the actual analysis and fix proposal to the css-debugger agent for expert-level diagnosis. diff --git a/commands/nav.md b/commands/nav.md new file mode 100644 index 0000000..76b2759 --- /dev/null +++ b/commands/nav.md @@ -0,0 +1,23 @@ +--- +description: Navigate the browser to a URL. Use --new to open in new tab. +allowed-tools: Bash(*) +argument-hint: [--new] +model: haiku +--- + +# Navigate + +Navigate the browser to a URL. + +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-nav.js $ARGUMENTS +``` + +## Options + +- ``: Navigate current tab to URL +- ` --new`: Open URL in new tab + +If URL doesn't include protocol, `https://` is assumed. + +Confirm successful navigation with page title. diff --git a/commands/perf.md b/commands/perf.md new file mode 100644 index 0000000..f6c7712 --- /dev/null +++ b/commands/perf.md @@ -0,0 +1,57 @@ +--- +description: Analyze page performance - load times, DOM size, memory, and large resources. +allowed-tools: Bash(*) +model: sonnet +--- + +# Performance Analysis + +Run a comprehensive performance analysis on the current page. + +Use the **performance-debugger agent** for this task. + +## Collect Metrics + +1. **Page Load Timing**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => { + const t = performance.timing; + return { + firstByte: t.responseStart - t.navigationStart, + domInteractive: t.domInteractive - t.navigationStart, + domContentLoaded: t.domContentLoadedEventEnd - t.navigationStart, + loadComplete: t.loadEventEnd - t.navigationStart + }; + })()' + ``` + +2. **DOM Analysis**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js '(() => { + const all = document.querySelectorAll("*"); + return { + totalElements: all.length, + scripts: document.scripts.length, + stylesheets: document.styleSheets.length, + images: document.images.length + }; + })()' + ``` + +3. **Large Resources**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'performance.getEntriesByType("resource").filter(r => r.transferSize > 50000).map(r => ({ name: r.name.split("/").pop(), kb: (r.transferSize/1024).toFixed(1) })).slice(0,10)' + ``` + +4. **Screenshot** for context: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js + ``` + +## Report + +Provide a summary with: +- Load time assessment (good/needs improvement/poor) +- DOM size assessment +- Largest resources +- Top 3 recommendations for improvement diff --git a/commands/pick-element.md b/commands/pick-element.md new file mode 100644 index 0000000..d6b91c2 --- /dev/null +++ b/commands/pick-element.md @@ -0,0 +1,39 @@ +--- +description: Launch interactive element picker for user to select DOM elements. Returns selectors and computed styles. +allowed-tools: Bash(*) +argument-hint: [message to display] +model: sonnet +--- + +# Pick Element + +Launch the interactive element picker so the user can click on DOM elements. + +## Instructions + +Run the picker with the provided message (or default): + +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-pick.js "$ARGUMENTS" +``` + +If no message provided, use: "Select the element you want to debug" + +## User Instructions + +Tell the user: +- **Click** on any element to select it +- **Cmd/Ctrl + Click** to add more elements to selection +- **Enter** to confirm multi-selection +- **Escape** to cancel + +## Output + +After the user selects elements, you'll receive: +- CSS selector for each element +- Tag, ID, and classes +- Dimensions and position +- Computed display, visibility, opacity +- Text content preview + +Use this information to diagnose issues or write CSS/JS targeting these elements. diff --git a/commands/resize.md b/commands/resize.md new file mode 100644 index 0000000..062b8ad --- /dev/null +++ b/commands/resize.md @@ -0,0 +1,36 @@ +--- +description: Resize browser viewport. Use presets like --mobile or custom dimensions. +allowed-tools: Bash(*) +argument-hint: [--mobile | --tablet | --desktop | width height] +model: haiku +--- + +# Resize Viewport + +Change the browser viewport size for responsive testing. + +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js $ARGUMENTS +``` + +## Presets + +| Flag | Size | Device | +|------|------|--------| +| `--mobile` | 375×667 | iPhone SE | +| `--iphone` | 390×844 | iPhone 14 | +| `--iphone-pro` | 393×852 | iPhone 14 Pro | +| `--android` | 412×915 | Pixel 7 | +| `--tablet` | 768×1024 | iPad | +| `--ipad-pro` | 1024×1366 | iPad Pro | +| `--laptop` | 1366×768 | Laptop | +| `--desktop` | 1920×1080 | Desktop | +| `--4k` | 3840×2160 | 4K Display | + +## Custom Size + +```bash +/resize 1440 900 +``` + +After resizing, take a screenshot to verify layout. diff --git a/commands/screenshot.md b/commands/screenshot.md new file mode 100644 index 0000000..e102d45 --- /dev/null +++ b/commands/screenshot.md @@ -0,0 +1,22 @@ +--- +description: Take a screenshot of the current browser viewport. Use --full for full page, --selector for specific element. +allowed-tools: Bash(*) +argument-hint: [--full | --selector=".class"] +model: haiku +--- + +# Screenshot + +Take a screenshot of the current browser state. + +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js $ARGUMENTS +``` + +## Options + +- No arguments: Viewport screenshot +- `--full`: Full page screenshot (scrollable content) +- `--selector=".class"`: Screenshot of specific element + +After capturing, analyze the screenshot for any visual issues. diff --git a/commands/test-responsive.md b/commands/test-responsive.md new file mode 100644 index 0000000..97cc98d --- /dev/null +++ b/commands/test-responsive.md @@ -0,0 +1,45 @@ +--- +description: Test responsive design across mobile, tablet, and desktop viewports. Takes screenshots at each breakpoint. +allowed-tools: Bash(*) +argument-hint: [url] +model: sonnet +--- + +# Test Responsive Design + +Capture screenshots at multiple viewport sizes to verify responsive design. + +## Instructions + +1. If URL provided in $ARGUMENTS, navigate first: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-nav.js "$ARGUMENTS" + ``` + +2. Test **Mobile** (375×667 - iPhone SE): + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --mobile + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/responsive-mobile.png + ``` + +3. Test **Tablet** (768×1024 - iPad): + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --tablet + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/responsive-tablet.png + ``` + +4. Test **Desktop** (1920×1080): + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-resize.js --desktop + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js --output=/tmp/responsive-desktop.png + ``` + +5. **Analyze all screenshots** and report: + - Layout shifts between breakpoints + - Text overflow or truncation + - Overlapping elements + - Hidden/missing content + - Spacing and alignment issues + - Navigation usability at each size + +Provide specific recommendations for any issues found. diff --git a/commands/verify-changes.md b/commands/verify-changes.md new file mode 100644 index 0000000..9401fa7 --- /dev/null +++ b/commands/verify-changes.md @@ -0,0 +1,48 @@ +--- +description: Verify frontend changes after editing code. Takes screenshot, checks for errors, and reports results. +allowed-tools: Bash(*), Read +model: sonnet +--- + +# Verify Changes + +After making frontend changes, verify they work correctly. + +## Instructions + +1. **Reload the page** to pick up changes: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-eval.js 'location.reload(true)' + ``` + +2. **Wait for page load**: + ```bash + sleep 2 + ``` + +3. **Take screenshot**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-screenshot.js + ``` + +4. **Check for JavaScript errors**: + ```bash + node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --errors + ``` + +5. **Analyze and report**: + - Compare screenshot to expected result + - List any JavaScript errors + - Identify if changes applied correctly + - Note any unexpected side effects + +6. **If issues found**: + - Describe the problem + - Suggest fixes + - Offer to make corrections + +7. **If successful**: + - Confirm changes work as expected + - Ask if any refinements needed + +This command is designed for the **edit → verify → iterate** loop that enables self-debugging of frontend work. diff --git a/commands/watch-console.md b/commands/watch-console.md new file mode 100644 index 0000000..20f8382 --- /dev/null +++ b/commands/watch-console.md @@ -0,0 +1,24 @@ +--- +description: Watch browser console in real-time. Shows errors, warnings, and logs as they happen. +allowed-tools: Bash(*) +argument-hint: [--errors | --warnings] +model: haiku +--- + +# Watch Console + +Monitor browser console output in real-time. + +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-console.js --watch $ARGUMENTS +``` + +## Options + +- No arguments: All console messages +- `--errors`: Only error messages +- `--warnings`: Errors and warnings + +Press Ctrl+C to stop watching. + +Report any errors that appear and suggest fixes. diff --git a/commands/watch-network.md b/commands/watch-network.md new file mode 100644 index 0000000..4846fa9 --- /dev/null +++ b/commands/watch-network.md @@ -0,0 +1,24 @@ +--- +description: Watch network requests in real-time. Great for debugging API calls. +allowed-tools: Bash(*) +argument-hint: [--failures | --xhr] +model: haiku +--- + +# Watch Network + +Monitor network requests in real-time. + +```bash +node ~/.claude/plugins/*/skills/website-debug/scripts/browser-network.js --watch $ARGUMENTS +``` + +## Options + +- No arguments: All network requests +- `--failures`: Only 4xx/5xx errors +- `--xhr`: Only XHR/fetch (API calls) + +Press Ctrl+C to stop watching. + +Report any failed requests and suggest potential causes. diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..4229ef5 --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://claude.ai/schemas/hooks.json", + "hooks": [ + { + "event": "Stop", + "description": "Remind about verification after frontend file changes", + "matcher": { + "tool": "Write", + "path": "**/*.{css,scss,less,html,jsx,tsx,vue,svelte}" + }, + "command": "echo '💡 Tip: Run /verify-changes to confirm your frontend changes work correctly'" + }, + { + "event": "SubagentStop", + "description": "Suggest next steps after CSS debugger runs", + "matcher": { + "agentName": "css-debugger" + }, + "command": "echo '✓ CSS analysis complete. Consider running /verify-changes after applying fixes.'" + }, + { + "event": "SubagentStop", + "description": "Suggest next steps after JS debugger runs", + "matcher": { + "agentName": "js-debugger" + }, + "command": "echo '✓ JavaScript analysis complete. Run /watch-console to monitor for new errors.'" + } + ] +} diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..ba6b7a7 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,197 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:AnthemFlynn/ccmp:plugins/website-debug", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "06e82c21b25228cf826d4958dfdd0e13082a7c1e", + "treeHash": "5c924b4662c56047a32efd7bb735fd19872c361a6b5783c4a50ac44dbb51278a", + "generatedAt": "2025-11-28T10:24:52.889220Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "website-debug", + "description": "Frontend website debugging toolkit with browser automation, visual verification, and self-debugging capabilities. Includes specialized agents for CSS, JavaScript, responsive testing, and performance analysis.", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "79634227edbd692b9571358bc63c2ca9cd01e59a00612c12c40244ec65c10907" + }, + { + "path": "agents/css-debugger.md", + "sha256": "c520dc2066342f2d92032b7a7570cd7093aa05bfa4255e6e5e33d7a8561fe6f6" + }, + { + "path": "agents/network-debugger.md", + "sha256": "c90e1e6acd60dfe13785e3c760ead18d9d67e647ba31734c0b04f36945cb9874" + }, + { + "path": "agents/js-debugger.md", + "sha256": "45a24a7781a31cabd90582bf65e822fb2dcae69c3afb4f2078c449896a6d9e35" + }, + { + "path": "agents/responsive-tester.md", + "sha256": "72750df593d7a770c5babe2496914cab558f726839a23548348d7cb472e1c7ae" + }, + { + "path": "agents/performance-debugger.md", + "sha256": "4dd3d706235d1e1b5c5334bc6610e7e3ec803ff9afa7204c507c0561d345d72b" + }, + { + "path": "agents/visual-verifier.md", + "sha256": "5651193d9508683d5cdc2bded937a836987d3491bfcbc48f53410288499ed8ad" + }, + { + "path": "hooks/hooks.json", + "sha256": "2e7f356bd0da6a5b9bdd20cde94b44d04a0cc51b5f5a504a7d22a996765fc297" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "079780c702cd94360e464e92fc18f791d23029f325900d563687aa38702c57bc" + }, + { + "path": "commands/debug-page.md", + "sha256": "0985f7c6980cbf109b72b5dad7fb0dd487095a4193b82097a2473794193cbe63" + }, + { + "path": "commands/nav.md", + "sha256": "635a690a21382a4cc0e49cae61281ea407318f598c9145f21b3aec076aeecda3" + }, + { + "path": "commands/diagnose.md", + "sha256": "7be40f64f565f661d710277ee5c07b2bd12f85f61650f7743579b63194f9263e" + }, + { + "path": "commands/fix-css.md", + "sha256": "99d11d222691125b3b55bdb1fef7937e9a89d839cd1afb0237bf0513f9db7950" + }, + { + "path": "commands/resize.md", + "sha256": "0630e37295de6439c4ef19aedb7d47a8b179cdfbd23cea1416a38154102fa8c2" + }, + { + "path": "commands/dom.md", + "sha256": "952c7b54574e7e725f7c8698d46c8bdaaf340e72ffa7637306de512ff02ea47e" + }, + { + "path": "commands/pick-element.md", + "sha256": "899aa4822e33262eb4c7abbb19e5a6d312a11f119dc38ff2030f9655fcb395b4" + }, + { + "path": "commands/screenshot.md", + "sha256": "392fbe4faa6621d8a394486f15dbae6a2ba44cc886ca086db40ebaaa6ab3ab02" + }, + { + "path": "commands/browser-eval.md", + "sha256": "2fb35a7db242a9486aa890ffbc314e6e7dca2624ef1ad885c69ffcb024b09ebd" + }, + { + "path": "commands/watch-network.md", + "sha256": "3b4076d4b782cfd903f8500b0f8aae981e0608b632eed0591a4cf4eebb03d831" + }, + { + "path": "commands/verify-changes.md", + "sha256": "9ec29a2f4933610837ae9621734c7055b330a987e811b9271492452feff7eb88" + }, + { + "path": "commands/browser-close.md", + "sha256": "48e9656a53b40bc204e30e9592de323bba5e81a0e4e753676385e169c7051075" + }, + { + "path": "commands/perf.md", + "sha256": "fd062e5a0ea707026bed55d0d23f15aee49565161574d1fe46b526efbad23fe3" + }, + { + "path": "commands/test-responsive.md", + "sha256": "194481c3622924a28957c578f72473527d4a6935cc15b1afcbcdc99516f74668" + }, + { + "path": "commands/watch-console.md", + "sha256": "812cf1cf54bcad8beb4855223ade88e47abd0686457c0df4e121c7b1e3cad4e6" + }, + { + "path": "commands/browser-start.md", + "sha256": "84b4f774dc9b6efa14ba03bbe3dafea632800b0b8f9d503512533818ad807a8d" + }, + { + "path": "skills/website-debug/package.json", + "sha256": "a4ea07139d10f79f32f35f2955f48ac0908f1451bba54de3ba462299952b8df8" + }, + { + "path": "skills/website-debug/SKILL.md", + "sha256": "4eb3e5dd3ed65fdcca3ce2779605ec268cb0e31c941b6e553dfa92ab2f46f14a" + }, + { + "path": "skills/website-debug/references/css-debug.md", + "sha256": "4d8e80c5c491d74440cbb7b564e41b2a63ed0c6415645403b236085456902d9e" + }, + { + "path": "skills/website-debug/references/self-debug.md", + "sha256": "05c901f82736ee874cb6da76dc64467eb104b4e41b901e0619d0aff3efc90678" + }, + { + "path": "skills/website-debug/references/js-debug.md", + "sha256": "a8e5f535a4e00a1302f58ad5fa72cfb144be4178331e8e8354d316b093690ea5" + }, + { + "path": "skills/website-debug/scripts/browser-start.js", + "sha256": "3bb27f610e9e058a010e92b73c7137955edf95f2aa5636c335250949169c9263" + }, + { + "path": "skills/website-debug/scripts/setup.sh", + "sha256": "5eb517d1ba23e55dec71046aafe8eaef00b3511ffe96d297a8ec8a336538b4f8" + }, + { + "path": "skills/website-debug/scripts/browser-screenshot.js", + "sha256": "edfbe51c669889ef19e57c04a264826b965b801912470dad0fe56e3fe3d62c58" + }, + { + "path": "skills/website-debug/scripts/browser-console.js", + "sha256": "87a452b992b1988905c505b047610b6d1d2df53d2bf5e917a7878fae2747fa12" + }, + { + "path": "skills/website-debug/scripts/browser-close.js", + "sha256": "ea09fdff34740cc9745c7ec39cda31f48d578ab963879a40c0b4dea92943e0b1" + }, + { + "path": "skills/website-debug/scripts/browser-resize.js", + "sha256": "9546d21241462147fc3d27188f2918f226b8f8ed65e7cb5d0db0000551624652" + }, + { + "path": "skills/website-debug/scripts/browser-nav.js", + "sha256": "0fed01ca09f2e2ab2c9b5498ca4bcb6f6a02ca55b0d3e6b3a20fbc9b40966d84" + }, + { + "path": "skills/website-debug/scripts/browser-eval.js", + "sha256": "4e75ddda0da5f056f62598b79796b3c26d5f6746d380c710577f33ed9536ac9c" + }, + { + "path": "skills/website-debug/scripts/browser-dom.js", + "sha256": "2da8acdc37a8e284c5b0f7ed7a725209c6e752cf9312b09a35c93305a5569d15" + }, + { + "path": "skills/website-debug/scripts/browser-pick.js", + "sha256": "47233581ebfc12f81d6bea46e4465b686ef2439b00e46c5a08d917c0b5be41f1" + }, + { + "path": "skills/website-debug/scripts/browser-network.js", + "sha256": "077ffa8e75f21d6eca6648b783c79b10a9695ff97991757499d60d8f5a2cd141" + } + ], + "dirSha256": "5c924b4662c56047a32efd7bb735fd19872c361a6b5783c4a50ac44dbb51278a" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/website-debug/SKILL.md b/skills/website-debug/SKILL.md new file mode 100644 index 0000000..9f15288 --- /dev/null +++ b/skills/website-debug/SKILL.md @@ -0,0 +1,202 @@ +--- +name: website-debug +description: > + Frontend website debugging toolkit using Chrome DevTools Protocol with Playwright/WebKit fallbacks. + Use this skill when: (1) Debugging CSS, HTML, or JavaScript issues on a webpage, (2) Taking screenshots + to verify visual changes, (3) Inspecting DOM structure or console errors, (4) Testing responsive layouts, + (5) Extracting selectors for automation, (6) Self-debugging frontend work Claude has created, + (7) User says "debug this page", "check my site", "why doesn't this look right", or "fix the frontend". + Supports Chrome (primary) and Safari/WebKit (via Playwright). Designed for agent-driven debugging loops. +--- + +# Website Debugging Skill + +Lightweight, token-efficient browser debugging toolkit for frontend development. Uses CLI scripts instead of MCP servers to minimize context usage (~300 tokens vs 13k-18k). + +## Quick Start + +Use the slash commands for easiest access: +- `/debug-page ` - Start debugging session +- `/screenshot` - Take screenshot +- `/pick-element` - Interactive element selection +- `/test-responsive` - Test at all breakpoints +- `/verify-changes` - Verify after making changes + +### Or use scripts directly: + +```bash +# Start browser +node scripts/browser-start.js +node scripts/browser-start.js --profile # Preserve logins +node scripts/browser-start.js --webkit # Safari/WebKit + +# Navigate +node scripts/browser-nav.js https://localhost:3000 + +# Debug +node scripts/browser-screenshot.js +node scripts/browser-eval.js 'document.title' +node scripts/browser-pick.js "Select element" +node scripts/browser-console.js --errors +node scripts/browser-network.js --failures +``` + +## Core Tools Reference + +| Script | Purpose | Output | +|--------|---------|--------| +| `browser-start.sh` | Launch Chrome/WebKit with debug port | Status message | +| `browser-nav.sh ` | Navigate to URL | Confirmation | +| `browser-screenshot.sh` | Capture viewport | File path (PNG) | +| `browser-eval.sh ''` | Run JS in page | Result or error | +| `browser-pick.sh ""` | Interactive selector | CSS selectors | +| `browser-console.sh` | Get console output | Logs/errors | +| `browser-network.sh` | Network activity | Request/response data | +| `browser-dom.sh ""` | Get DOM snapshot | HTML fragment | +| `browser-close.sh` | Close browser | Confirmation | + +## Self-Debugging Workflow + +When debugging frontend code Claude has written or modified: + +### 1. Visual Verification Loop +```bash +# After making CSS/HTML changes, verify visually +./scripts/browser-screenshot.sh +# Claude reads the screenshot, identifies issues, iterates +``` + +### 2. Console Error Detection +```bash +# Check for JavaScript errors after changes +./scripts/browser-console.sh --errors +# Fix any errors found, re-verify +``` + +### 3. Responsive Testing +```bash +# Test at different viewport sizes +./scripts/browser-resize.sh 375 667 # iPhone SE +./scripts/browser-screenshot.sh +./scripts/browser-resize.sh 768 1024 # iPad +./scripts/browser-screenshot.sh +./scripts/browser-resize.sh 1920 1080 # Desktop +./scripts/browser-screenshot.sh +``` + +### 4. Element Inspection +```bash +# When user reports "X looks wrong", have them select it +./scripts/browser-pick.sh "Click on the element that looks wrong" +# Returns detailed info including computed styles +``` + +## Browser Engine Selection + +### Chrome (Default) +Primary engine. Uses Chrome DevTools Protocol on port 9222. +- Best debugging experience +- Full DevTools compatibility +- Use `--profile` to preserve logins + +### WebKit/Safari +Fallback via Playwright's WebKit build. Closest to Safari behavior on macOS. +```bash +./scripts/browser-start.sh --webkit +``` +- Use for Safari-specific testing +- Layout verification +- WebKit-specific bugs + +### When to Use Each + +| Scenario | Engine | +|----------|--------| +| General debugging | Chrome | +| Safari layout issues | WebKit | +| Testing with logins | Chrome `--profile` | +| Cross-browser comparison | Both | +| CI/headless testing | Chrome or WebKit | + +## Advanced Usage + +### Detailed Documentation +For complex scenarios, load the appropriate reference: + +- **CSS Debugging**: See [references/css-debug.md](references/css-debug.md) +- **JavaScript Errors**: See [references/js-debug.md](references/js-debug.md) +- **Network Issues**: See [references/network-debug.md](references/network-debug.md) +- **Responsive Design**: See [references/responsive-debug.md](references/responsive-debug.md) +- **Performance**: See [references/performance-debug.md](references/performance-debug.md) + +### Composable Output + +All scripts output to files when practical, enabling: +```bash +# Capture multiple screenshots for comparison +./scripts/browser-screenshot.sh > /tmp/before.png +# ... make changes ... +./scripts/browser-screenshot.sh > /tmp/after.png + +# Save DOM snapshot for analysis +./scripts/browser-dom.sh "body" > /tmp/page-structure.html + +# Export console log for review +./scripts/browser-console.sh > /tmp/console-log.txt +``` + +### Chaining Commands +```bash +# Navigate and screenshot in one command +./scripts/browser-nav.sh https://example.com && ./scripts/browser-screenshot.sh + +# Full page audit +./scripts/browser-nav.sh $URL && \ + ./scripts/browser-console.sh --errors > /tmp/errors.txt && \ + ./scripts/browser-screenshot.sh +``` + +## Setup Requirements + +### Chrome +Chrome must be launchable from command line. The start script handles this automatically. + +### WebKit (Optional) +For Safari testing, ensure Playwright is installed: +```bash +npm install -g playwright +npx playwright install webkit +``` + +### Dependencies +Scripts require Node.js and puppeteer-core: +```bash +npm install -g puppeteer-core +``` + +## Troubleshooting + +### "Cannot connect to browser" +Browser may not be running or wrong port: +```bash +./scripts/browser-start.sh # Restart browser +``` + +### "Permission denied" +Scripts may need execute permission: +```bash +chmod +x ./scripts/*.sh +``` + +### Chrome already running +Kill existing instances first: +```bash +killall "Google Chrome" 2>/dev/null +./scripts/browser-start.sh +``` + +### WebKit not found +Install Playwright browsers: +```bash +npx playwright install webkit +``` diff --git a/skills/website-debug/package.json b/skills/website-debug/package.json new file mode 100644 index 0000000..ac7a8ba --- /dev/null +++ b/skills/website-debug/package.json @@ -0,0 +1,38 @@ +{ + "name": "website-debug-skill", + "version": "1.0.0", + "description": "Frontend website debugging toolkit for Claude Code", + "type": "module", + "scripts": { + "setup": "bash scripts/setup.sh", + "start": "node scripts/browser-start.js", + "start:profile": "node scripts/browser-start.js --profile", + "start:webkit": "node scripts/browser-start.js --webkit", + "nav": "node scripts/browser-nav.js", + "screenshot": "node scripts/browser-screenshot.js", + "eval": "node scripts/browser-eval.js", + "pick": "node scripts/browser-pick.js", + "console": "node scripts/browser-console.js", + "resize": "node scripts/browser-resize.js", + "dom": "node scripts/browser-dom.js" + }, + "dependencies": { + "puppeteer-core": "^23.0.0" + }, + "optionalDependencies": { + "playwright": "^1.48.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "keywords": [ + "debugging", + "chrome", + "devtools", + "browser", + "automation", + "claude-code", + "skill" + ], + "license": "MIT" +} diff --git a/skills/website-debug/references/css-debug.md b/skills/website-debug/references/css-debug.md new file mode 100644 index 0000000..9fa7c0f --- /dev/null +++ b/skills/website-debug/references/css-debug.md @@ -0,0 +1,290 @@ +# CSS Debugging Reference + +Advanced CSS debugging workflows and patterns for frontend development. + +## Quick Diagnostics + +### Check Element Visibility +```bash +# Is element visible? +./browser-eval.js '(() => { + const el = document.querySelector(".target"); + const s = getComputedStyle(el); + return { + display: s.display, + visibility: s.visibility, + opacity: s.opacity, + hidden: el.hidden, + height: s.height, + width: s.width, + overflow: s.overflow + }; +})()' +``` + +### Element Not Showing? +Common causes and checks: + +```bash +# Check display property +./browser-eval.js 'getComputedStyle(document.querySelector(".target")).display' +# If "none" - element is hidden by CSS + +# Check visibility +./browser-eval.js 'getComputedStyle(document.querySelector(".target")).visibility' +# If "hidden" - element takes space but invisible + +# Check opacity +./browser-eval.js 'getComputedStyle(document.querySelector(".target")).opacity' +# If "0" - element is transparent + +# Check dimensions +./browser-eval.js 'document.querySelector(".target").getBoundingClientRect()' +# If width/height is 0 - element has no size + +# Check if off-screen +./browser-eval.js '(() => { + const r = document.querySelector(".target").getBoundingClientRect(); + return { + inViewport: r.top >= 0 && r.left >= 0 && r.bottom <= window.innerHeight && r.right <= window.innerWidth, + position: { top: r.top, left: r.left } + }; +})()' +``` + +## Layout Issues + +### Flexbox Debugging +```bash +# Check flex container +./browser-eval.js '(() => { + const el = document.querySelector(".flex-container"); + const s = getComputedStyle(el); + return { + display: s.display, + flexDirection: s.flexDirection, + justifyContent: s.justifyContent, + alignItems: s.alignItems, + flexWrap: s.flexWrap, + gap: s.gap + }; +})()' + +# Check flex items +./browser-eval.js '[...document.querySelectorAll(".flex-container > *")].map(el => { + const s = getComputedStyle(el); + return { + flexGrow: s.flexGrow, + flexShrink: s.flexShrink, + flexBasis: s.flexBasis, + alignSelf: s.alignSelf + }; +})' +``` + +### Grid Debugging +```bash +# Check grid container +./browser-eval.js '(() => { + const el = document.querySelector(".grid-container"); + const s = getComputedStyle(el); + return { + display: s.display, + gridTemplateColumns: s.gridTemplateColumns, + gridTemplateRows: s.gridTemplateRows, + gap: s.gap, + gridAutoFlow: s.gridAutoFlow + }; +})()' +``` + +### Box Model +```bash +# Full box model breakdown +./browser-eval.js '(() => { + const el = document.querySelector(".target"); + const s = getComputedStyle(el); + const r = el.getBoundingClientRect(); + return { + content: { width: r.width, height: r.height }, + padding: { top: s.paddingTop, right: s.paddingRight, bottom: s.paddingBottom, left: s.paddingLeft }, + border: { top: s.borderTopWidth, right: s.borderRightWidth, bottom: s.borderBottomWidth, left: s.borderLeftWidth }, + margin: { top: s.marginTop, right: s.marginRight, bottom: s.marginBottom, left: s.marginLeft }, + boxSizing: s.boxSizing + }; +})()' +``` + +## Positioning Problems + +### Z-Index Stacking +```bash +# Find all positioned elements with z-index +./browser-eval.js '[...document.querySelectorAll("*")].filter(el => { + const s = getComputedStyle(el); + return s.position !== "static" && s.zIndex !== "auto"; +}).map(el => ({ + selector: el.tagName + (el.id ? "#" + el.id : "") + (el.className ? "." + el.className.split(" ")[0] : ""), + position: getComputedStyle(el).position, + zIndex: getComputedStyle(el).zIndex +})).sort((a, b) => parseInt(b.zIndex) - parseInt(a.zIndex))' +``` + +### Fixed/Sticky Elements +```bash +# Find fixed/sticky elements +./browser-eval.js '[...document.querySelectorAll("*")].filter(el => { + const pos = getComputedStyle(el).position; + return pos === "fixed" || pos === "sticky"; +}).map(el => ({ + tag: el.tagName.toLowerCase(), + id: el.id, + class: el.className, + position: getComputedStyle(el).position +}))' +``` + +## Responsive Issues + +### Media Query Testing +```bash +# Test different breakpoints +./browser-resize.js --mobile && ./browser-screenshot.js --output=/tmp/mobile.png +./browser-resize.js --tablet && ./browser-screenshot.js --output=/tmp/tablet.png +./browser-resize.js --desktop && ./browser-screenshot.js --output=/tmp/desktop.png +``` + +### Check Current Viewport +```bash +./browser-eval.js '({ + viewport: { width: window.innerWidth, height: window.innerHeight }, + screen: { width: screen.width, height: screen.height }, + devicePixelRatio: window.devicePixelRatio +})' +``` + +## Typography Issues + +### Font Not Loading +```bash +# Check computed font +./browser-eval.js 'getComputedStyle(document.querySelector(".text")).fontFamily' + +# Check if webfont loaded +./browser-eval.js 'document.fonts.check("16px YourFontName")' + +# List all loaded fonts +./browser-eval.js '[...document.fonts].map(f => ({ family: f.family, style: f.style, weight: f.weight, status: f.status }))' +``` + +### Text Overflow +```bash +# Check text overflow settings +./browser-eval.js '(() => { + const el = document.querySelector(".truncated-text"); + const s = getComputedStyle(el); + return { + overflow: s.overflow, + textOverflow: s.textOverflow, + whiteSpace: s.whiteSpace, + width: s.width, + maxWidth: s.maxWidth + }; +})()' +``` + +## Color & Visual + +### Check Colors +```bash +# Get all colors used on element +./browser-eval.js '(() => { + const el = document.querySelector(".target"); + const s = getComputedStyle(el); + return { + color: s.color, + backgroundColor: s.backgroundColor, + borderColor: s.borderColor, + outlineColor: s.outlineColor + }; +})()' +``` + +### Find Elements by Color +```bash +# Find all elements with a specific background +./browser-eval.js '[...document.querySelectorAll("*")].filter(el => + getComputedStyle(el).backgroundColor === "rgb(255, 0, 0)" +).map(el => el.tagName + (el.id ? "#" + el.id : ""))' +``` + +## Performance Concerns + +### Large DOM Check +```bash +./browser-eval.js '({ + totalElements: document.querySelectorAll("*").length, + deepestNesting: (() => { + let max = 0; + document.querySelectorAll("*").forEach(el => { + let depth = 0, node = el; + while (node.parentElement) { depth++; node = node.parentElement; } + if (depth > max) max = depth; + }); + return max; + })() +})' +``` + +### Reflow Triggers +Watch for properties that cause layout recalculation: +- offsetTop/Left/Width/Height +- scrollTop/Left/Width/Height +- clientTop/Left/Width/Height +- getComputedStyle() +- getBoundingClientRect() + +## Animation Debugging + +### Check Animations +```bash +./browser-eval.js '(() => { + const el = document.querySelector(".animated"); + const s = getComputedStyle(el); + return { + animation: s.animation, + animationName: s.animationName, + animationDuration: s.animationDuration, + animationTimingFunction: s.animationTimingFunction, + animationDelay: s.animationDelay, + transition: s.transition + }; +})()' +``` + +### Force Animation State +```bash +# Pause all animations +./browser-eval.js 'document.querySelectorAll("*").forEach(el => el.style.animationPlayState = "paused")' + +# Resume animations +./browser-eval.js 'document.querySelectorAll("*").forEach(el => el.style.animationPlayState = "")' +``` + +## Common CSS Fixes + +### Overflow Scroll Not Working +Check these in order: +1. Parent has defined height +2. `overflow` is set to `scroll` or `auto` +3. Content is actually taller than container + +### Element Behind Another +1. Check z-index values +2. Ensure positioned (relative/absolute/fixed) +3. Check stacking context (transform, opacity < 1, etc. create new contexts) + +### Flexbox Not Centering +1. Container has height +2. `align-items` for vertical, `justify-content` for horizontal +3. Check `flex-direction` - swaps main/cross axis diff --git a/skills/website-debug/references/js-debug.md b/skills/website-debug/references/js-debug.md new file mode 100644 index 0000000..4ee1cd4 --- /dev/null +++ b/skills/website-debug/references/js-debug.md @@ -0,0 +1,259 @@ +# JavaScript Debugging Reference + +Workflows for diagnosing and fixing JavaScript issues in web applications. + +## Error Detection + +### Capture Runtime Errors +```bash +# Watch for errors in real-time +./browser-console.js --errors --watch + +# Inject error capture (run once per page load) +./browser-eval.js 'window.onerror = (msg, src, line, col, err) => { + window.__jsErrors = window.__jsErrors || []; + window.__jsErrors.push({ message: msg, source: src, line, col, stack: err?.stack }); + return false; +}' + +# Check captured errors +./browser-eval.js 'window.__jsErrors || []' +``` + +### Unhandled Promise Rejections +```bash +# Capture unhandled rejections +./browser-eval.js 'window.addEventListener("unhandledrejection", e => { + window.__promiseErrors = window.__promiseErrors || []; + window.__promiseErrors.push({ reason: e.reason?.message || String(e.reason) }); +})' + +# Check promise errors +./browser-eval.js 'window.__promiseErrors || []' +``` + +## Common Error Types + +### TypeError: Cannot read property 'x' of undefined +**Cause**: Accessing property on null/undefined +**Debug**: +```bash +# Check if element exists +./browser-eval.js 'document.querySelector(".my-element")' + +# Check if variable/property exists +./browser-eval.js 'typeof window.myApp?.myProperty' +``` + +### ReferenceError: x is not defined +**Cause**: Variable not in scope or not declared +**Debug**: +```bash +# Check if global exists +./browser-eval.js '"myVariable" in window' + +# List all globals matching pattern +./browser-eval.js 'Object.keys(window).filter(k => k.includes("my"))' +``` + +### SyntaxError +**Cause**: Invalid JavaScript syntax +**Debug**: Check browser console for file and line number +```bash +./browser-console.js --errors +``` + +## State Inspection + +### Check Global Variables +```bash +# List all custom globals (excluding built-ins) +./browser-eval.js '(() => { + const iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + const builtins = new Set(Object.keys(iframe.contentWindow)); + iframe.remove(); + return Object.keys(window).filter(k => !builtins.has(k) && !k.startsWith("__")); +})()' +``` + +### Inspect Application State +```bash +# React state (if using React DevTools globals) +./browser-eval.js 'window.__REACT_DEVTOOLS_GLOBAL_HOOK__?.renderers' + +# Redux store (if exposed) +./browser-eval.js 'window.__store?.getState()' + +# Vue instance (if exposed) +./browser-eval.js 'document.querySelector("#app")?.__vue__?.$data' +``` + +### Local Storage / Session Storage +```bash +# View localStorage +./browser-eval.js 'Object.fromEntries(Object.entries(localStorage))' + +# View sessionStorage +./browser-eval.js 'Object.fromEntries(Object.entries(sessionStorage))' +``` + +## Event Debugging + +### Check Event Listeners +```bash +# Get listeners on element (Chrome only) +./browser-eval.js 'getEventListeners(document.querySelector(".button"))' + +# Alternative: Check if onclick exists +./browser-eval.js 'typeof document.querySelector(".button").onclick' +``` + +### Debug Event Firing +```bash +# Log all click events +./browser-eval.js 'document.addEventListener("click", e => console.log("Click:", e.target.tagName, e.target.className), true)' + +# Log all form submissions +./browser-eval.js 'document.addEventListener("submit", e => console.log("Form submit:", e.target.action), true)' +``` + +### Simulate Events +```bash +# Click element +./browser-eval.js 'document.querySelector(".button").click()' + +# Dispatch custom event +./browser-eval.js 'document.querySelector(".target").dispatchEvent(new Event("input", { bubbles: true }))' + +# Submit form +./browser-eval.js 'document.querySelector("form").submit()' +``` + +## Async Debugging + +### Track Pending Promises +```bash +# Wrap fetch to log all requests +./browser-eval.js '(() => { + const orig = window.fetch; + window.fetch = (...args) => { + console.log("Fetch:", args[0]); + return orig.apply(window, args); + }; +})()' +``` + +### Debug setTimeout/setInterval +```bash +# List all active timers (approximate) +./browser-eval.js '(() => { + const ids = []; + const id = setTimeout(() => {}, 0); + clearTimeout(id); + for (let i = 1; i < id; i++) { + clearTimeout(i); + clearInterval(i); + } + return `Cleared timers up to ID ${id}`; +})()' +``` + +## Network-Related JS Issues + +### Check if Fetch Failed +```bash +./browser-eval.js 'fetch("/api/data").then(r => ({ status: r.status, ok: r.ok })).catch(e => ({ error: e.message }))' +``` + +### CORS Errors +Check console for CORS errors: +```bash +./browser-console.js --errors | grep -i cors +``` + +### API Response Issues +```bash +# Test API and inspect response +./browser-eval.js 'fetch("/api/endpoint").then(r => r.json()).then(d => JSON.stringify(d, null, 2)).catch(e => e.message)' +``` + +## Module/Import Issues + +### Check if Module Loaded +```bash +# Check for module in global scope +./browser-eval.js '"ModuleName" in window' + +# Check ES module +./browser-eval.js 'import("./module.js").then(m => Object.keys(m)).catch(e => e.message)' +``` + +### Script Load Order +```bash +# List all scripts in order +./browser-eval.js '[...document.querySelectorAll("script")].map(s => ({ src: s.src || "(inline)", async: s.async, defer: s.defer }))' +``` + +## Performance Issues + +### Identify Long Tasks +```bash +# Monitor long tasks +./browser-eval.js '(() => { + const observer = new PerformanceObserver(list => { + list.getEntries().forEach(entry => console.log("Long task:", entry.duration, "ms")); + }); + observer.observe({ entryTypes: ["longtask"] }); + return "Long task observer started"; +})()' +``` + +### Memory Leaks +```bash +# Get heap size (Chrome) +./browser-eval.js 'performance.memory ? { usedHeap: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) + "MB", totalHeap: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024) + "MB" } : "Not available"' +``` + +## Framework-Specific + +### React Debugging +```bash +# Find React components in DOM +./browser-eval.js '(() => { + const reactRoot = document.querySelector("[data-reactroot], #root, #app"); + return reactRoot ? "React app found" : "No React root detected"; +})()' + +# Check for React errors +./browser-console.js --errors | grep -i react +``` + +### Vue Debugging +```bash +# Check Vue version +./browser-eval.js 'window.Vue?.version || document.querySelector("[data-v-]") ? "Vue detected" : "No Vue"' +``` + +### Next.js/Nuxt Hydration +```bash +# Check for hydration errors +./browser-console.js --errors | grep -i hydrat +``` + +## Quick Fixes + +### Script Not Running +1. Check if script loaded: `./browser-dom.js "script[src*='filename']"` +2. Check for syntax errors: `./browser-console.js --errors` +3. Check load order: Ensure dependencies load first + +### Event Handler Not Working +1. Check element exists when handler attached +2. Verify selector is correct +3. Check for event.preventDefault() or stopPropagation() + +### Async Code Not Executing +1. Check if Promise rejects: Add .catch() logging +2. Verify await is in async function +3. Check network requests complete successfully diff --git a/skills/website-debug/references/self-debug.md b/skills/website-debug/references/self-debug.md new file mode 100644 index 0000000..8516ffb --- /dev/null +++ b/skills/website-debug/references/self-debug.md @@ -0,0 +1,288 @@ +# Self-Debugging Workflow + +Patterns for Claude and AI agents to verify and fix their own frontend work. + +## Core Principle + +After making any frontend change, verify it worked: +1. **Screenshot** - Visual verification +2. **Console** - Check for errors +3. **DOM** - Confirm structure +4. **Iterate** - Fix issues and repeat + +## Basic Verification Loop + +```bash +# After modifying HTML/CSS/JS files: + +# 1. Reload the page (if not using hot reload) +./browser-nav.js http://localhost:3000 + +# 2. Take screenshot to verify visual result +./browser-screenshot.js + +# 3. Check for any JavaScript errors +./browser-console.js --errors + +# 4. If issues found, fix and repeat +``` + +## Structured Debugging Protocol + +### Phase 1: Initial Assessment +```bash +# Get page summary +./browser-dom.js + +# Take baseline screenshot +./browser-screenshot.js --output=/tmp/current-state.png + +# Capture any errors +./browser-console.js --errors > /tmp/errors.txt +``` + +### Phase 2: Make Changes +After editing source files: +```bash +# Force reload (bypass cache) +./browser-eval.js 'location.reload(true)' + +# Wait for page load +sleep 2 + +# Take new screenshot +./browser-screenshot.js --output=/tmp/after-change.png +``` + +### Phase 3: Verify +```bash +# Check for new errors +./browser-console.js --errors + +# Verify element exists (example: new button) +./browser-eval.js 'document.querySelector(".new-button") ? "Found" : "Missing"' + +# Verify styling applied +./browser-eval.js 'getComputedStyle(document.querySelector(".new-button")).backgroundColor' +``` + +### Phase 4: Iterate +If issues found: +1. Identify the problem from screenshot/console +2. Fix the source code +3. Return to Phase 2 + +## Common Self-Fix Patterns + +### CSS Not Applying + +**Symptom**: Element exists but looks wrong +**Diagnosis**: +```bash +# Check if styles computed correctly +./browser-eval.js 'getComputedStyle(document.querySelector(".my-element")).cssText.slice(0, 500)' + +# Check for conflicting selectors +./browser-eval.js '(() => { + const el = document.querySelector(".my-element"); + const sheets = [...document.styleSheets]; + const matches = []; + sheets.forEach(sheet => { + try { + [...sheet.cssRules].forEach(rule => { + if (el.matches(rule.selectorText)) matches.push(rule.selectorText); + }); + } catch {} + }); + return matches; +})()' +``` + +**Fixes to try**: +1. Increase specificity +2. Check for `!important` overrides +3. Verify selector matches element + +### Element Not Visible + +**Symptom**: DOM shows element but not visible +**Diagnosis**: +```bash +./browser-eval.js '(() => { + const el = document.querySelector(".my-element"); + const s = getComputedStyle(el); + const r = el.getBoundingClientRect(); + return { + exists: !!el, + display: s.display, + visibility: s.visibility, + opacity: s.opacity, + width: r.width, + height: r.height, + inViewport: r.top < window.innerHeight && r.bottom > 0 + }; +})()' +``` + +**Fixes based on result**: +- `display: none` → Check CSS rules hiding it +- `width/height: 0` → Add dimensions or content +- `opacity: 0` → Check for fade animations +- Not in viewport → Check positioning/scroll + +### JavaScript Not Running + +**Symptom**: Interactive features don't work +**Diagnosis**: +```bash +# Check for errors +./browser-console.js --errors + +# Check if script loaded +./browser-eval.js 'document.querySelectorAll("script").length' + +# Check if function exists +./browser-eval.js 'typeof myFunction' +``` + +**Fixes based on result**: +- Syntax error → Fix the JavaScript +- Script not loaded → Check path/build process +- Function undefined → Check load order, exports + +### Layout Broken + +**Symptom**: Elements positioned incorrectly +**Diagnosis**: +```bash +# Take screenshot +./browser-screenshot.js + +# Check flex/grid container +./browser-eval.js '(() => { + const container = document.querySelector(".container"); + const s = getComputedStyle(container); + return { display: s.display, flexDirection: s.flexDirection, gridTemplateColumns: s.gridTemplateColumns }; +})()' +``` + +**Common fixes**: +- Missing `display: flex/grid` on container +- Wrong `flex-direction` +- Missing `width` on flex children +- Grid columns not matching children + +## Responsive Verification + +### Test All Breakpoints +```bash +# Mobile +./browser-resize.js --mobile +./browser-screenshot.js --output=/tmp/mobile.png +./browser-console.js --errors + +# Tablet +./browser-resize.js --tablet +./browser-screenshot.js --output=/tmp/tablet.png + +# Desktop +./browser-resize.js --desktop +./browser-screenshot.js --output=/tmp/desktop.png +``` + +### Compare Screenshots +After each breakpoint, Claude should analyze the screenshot for: +- Layout shifts +- Overlapping elements +- Text overflow +- Hidden/missing elements +- Incorrect spacing + +## Interactive Feature Testing + +### Form Validation +```bash +# Fill form with test data +./browser-eval.js 'document.querySelector("#email").value = "test@example.com"' +./browser-eval.js 'document.querySelector("#password").value = "test123"' + +# Submit and check result +./browser-eval.js 'document.querySelector("form").submit()' +./browser-screenshot.js +./browser-console.js --errors +``` + +### Click/Hover States +```bash +# Test button click +./browser-eval.js 'document.querySelector(".button").click()' +./browser-screenshot.js + +# Check state changed +./browser-eval.js 'document.querySelector(".modal")?.style.display' +``` + +## Automated Verification Script + +For repeated checks, use this pattern: +```bash +#!/bin/bash +# verify-page.sh - Run after changes + +URL="${1:-http://localhost:3000}" +OUT_DIR="/tmp/debug-$(date +%s)" +mkdir -p "$OUT_DIR" + +echo "Verifying $URL..." + +# Navigate +./browser-nav.js "$URL" +sleep 2 + +# Screenshot +./browser-screenshot.js --output="$OUT_DIR/screenshot.png" +echo "Screenshot: $OUT_DIR/screenshot.png" + +# Errors +./browser-console.js --errors > "$OUT_DIR/errors.txt" +if [ -s "$OUT_DIR/errors.txt" ]; then + echo "⚠️ Errors found:" + cat "$OUT_DIR/errors.txt" +else + echo "✓ No JavaScript errors" +fi + +# DOM summary +./browser-dom.js > "$OUT_DIR/dom.txt" +echo "DOM summary saved" + +echo "Done. Files in $OUT_DIR" +``` + +## Error Recovery Strategies + +### When Screenshot Shows Blank Page +1. Check console for critical errors +2. Verify server is running +3. Check network requests (404, 500) +4. Verify correct URL + +### When Console Shows Many Errors +1. Fix first error (often causes cascade) +2. Reload and recheck +3. Address errors in order + +### When Element Is "Almost Right" +1. Use picker to select element: `./browser-pick.js "Select the problematic element"` +2. Get full computed styles +3. Compare to expected values +4. Adjust CSS incrementally + +## Best Practices + +1. **Always screenshot after changes** - Visual verification catches most issues +2. **Check console immediately** - Catch JS errors early +3. **Test responsive early** - Layout issues compound +4. **Verify one change at a time** - Easier to identify what broke +5. **Save debug output** - Reference for comparison +6. **Use the picker for precision** - Get exact selectors from user diff --git a/skills/website-debug/scripts/browser-close.js b/skills/website-debug/scripts/browser-close.js new file mode 100755 index 0000000..f6c250f --- /dev/null +++ b/skills/website-debug/scripts/browser-close.js @@ -0,0 +1,72 @@ +#!/usr/bin/env node +/** + * browser-close.js - Close browser session + * + * Usage: + * ./browser-close.js # Close gracefully + * ./browser-close.js --force # Kill all Chrome instances + */ + +import puppeteer from "puppeteer-core"; +import { execSync } from "node:child_process"; +import { platform } from "node:os"; + +const args = process.argv.slice(2); +const force = args.includes("--force"); +const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222"; + +if (args.includes("--help") || args.includes("-h")) { + console.log(` +browser-close.js - Close browser session + +Usage: + ./browser-close.js [options] + +Options: + --force Kill all Chrome debug instances + --port=PORT Connect to custom debug port (default: 9222) + +Examples: + ./browser-close.js + ./browser-close.js --force +`); + process.exit(0); +} + +if (force) { + try { + if (platform() === "darwin") { + execSync("killall 'Google Chrome' 2>/dev/null", { stdio: "ignore" }); + } else if (platform() === "linux") { + execSync("pkill -f 'chrome.*remote-debugging' 2>/dev/null", { stdio: "ignore" }); + } + console.log("✓ Force killed Chrome instances"); + } catch { + console.log("No Chrome instances to kill"); + } + process.exit(0); +} + +try { + const browser = await puppeteer.connect({ + browserURL: `http://localhost:${port}`, + defaultViewport: null + }); + + await browser.close(); + console.log("✓ Browser closed"); +} catch (e) { + if (e.message?.includes("ECONNREFUSED")) { + console.log("No browser session to close"); + } else { + // Try force close as fallback + try { + if (platform() === "darwin") { + execSync("killall 'Google Chrome' 2>/dev/null", { stdio: "ignore" }); + } + console.log("✓ Browser closed (via kill)"); + } catch { + console.error(`Could not close browser: ${e.message}`); + } + } +} diff --git a/skills/website-debug/scripts/browser-console.js b/skills/website-debug/scripts/browser-console.js new file mode 100755 index 0000000..4d1d7a1 --- /dev/null +++ b/skills/website-debug/scripts/browser-console.js @@ -0,0 +1,175 @@ +#!/usr/bin/env node +/** + * browser-console.js - Get console messages from page + * + * Usage: + * ./browser-console.js # Get all console messages + * ./browser-console.js --errors # Only errors + * ./browser-console.js --warnings # Errors and warnings + * ./browser-console.js --watch # Watch for new messages + */ + +import puppeteer from "puppeteer-core"; + +const args = process.argv.slice(2); +const errorsOnly = args.includes("--errors"); +const warningsPlus = args.includes("--warnings"); +const watch = args.includes("--watch"); +const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222"; + +if (args.includes("--help") || args.includes("-h")) { + console.log(` +browser-console.js - Capture console messages + +Usage: + ./browser-console.js [options] + +Options: + --errors Show only errors + --warnings Show errors and warnings + --watch Watch for new messages in real-time + --port=PORT Connect to custom debug port (default: 9222) + +Message Types: + [ERR] - console.error, exceptions + [WARN] - console.warn + [LOG] - console.log + [INFO] - console.info + [DBG] - console.debug + +Examples: + ./browser-console.js + ./browser-console.js --errors + ./browser-console.js --watch +`); + process.exit(0); +} + +try { + const browser = await puppeteer.connect({ + browserURL: `http://localhost:${port}`, + defaultViewport: null + }); + + const pages = await browser.pages(); + const page = pages[pages.length - 1]; + + if (!page) { + console.error("✗ No active tab found"); + process.exit(1); + } + + if (watch) { + console.log("Watching console... (Ctrl+C to stop)\n"); + + const shouldShow = (type) => { + if (errorsOnly) return type === "error"; + if (warningsPlus) return type === "error" || type === "warning"; + return true; + }; + + const typeLabels = { + error: "[ERR] ", + warning: "[WARN]", + log: "[LOG] ", + info: "[INFO]", + debug: "[DBG] " + }; + + page.on("console", msg => { + const type = msg.type(); + if (shouldShow(type)) { + const label = typeLabels[type] || `[${type.toUpperCase()}]`; + const text = msg.text(); + console.log(`${label} ${text}`); + } + }); + + page.on("pageerror", err => { + console.log(`[ERR] Uncaught: ${err.message}`); + }); + + // Keep alive + await new Promise(() => {}); + } else { + // Get existing console messages by injecting capture + const messages = await page.evaluate(() => { + // Return any cached messages if we have them + return window.__consoleMessages || []; + }); + + // Also get any runtime exceptions + const client = await page.target().createCDPSession(); + await client.send("Runtime.enable"); + + // Collect current console messages by re-evaluating with capture + const capturedMessages = await page.evaluate(() => { + const msgs = []; + const originalConsole = { + log: console.log, + error: console.error, + warn: console.warn, + info: console.info, + debug: console.debug + }; + + // This is for future messages - we can't capture past ones without this being set up earlier + // So we'll note that limitation + return msgs; + }); + + // Get recent exceptions from CDP + const { result } = await client.send("Runtime.evaluate", { + expression: ` + (function() { + // Check for any unhandled errors stored in window + const errors = window.__webdebugErrors || []; + return errors; + })() + `, + returnByValue: true + }); + + if (messages.length === 0 && (!result.value || result.value.length === 0)) { + console.log("No console messages captured."); + console.log("\nTip: Use --watch to capture messages in real-time, or inject capture script:"); + console.log(' ./browser-eval.js "window.__consoleMessages=[];[\'log\',\'error\',\'warn\',\'info\'].forEach(t=>{const o=console[t];console[t]=(...a)=>{window.__consoleMessages.push({type:t,text:a.join(\' \'),time:Date.now()});o.apply(console,a)}});"'); + } else { + const allMessages = [...messages, ...(result.value || [])]; + + const typeLabels = { + error: "[ERR] ", + warning: "[WARN]", + warn: "[WARN]", + log: "[LOG] ", + info: "[INFO]", + debug: "[DBG] " + }; + + allMessages.forEach(msg => { + const type = msg.type || "log"; + const shouldShow = () => { + if (errorsOnly) return type === "error"; + if (warningsPlus) return type === "error" || type === "warning" || type === "warn"; + return true; + }; + + if (shouldShow()) { + const label = typeLabels[type] || `[${type.toUpperCase()}]`; + console.log(`${label} ${msg.text || msg.message || JSON.stringify(msg)}`); + } + }); + } + } + + if (!watch) { + await browser.disconnect(); + } +} catch (e) { + if (e.message?.includes("ECONNREFUSED")) { + console.error("✗ Cannot connect to browser. Run: ./browser-start.js"); + } else { + console.error(`✗ Console capture failed: ${e.message}`); + } + process.exit(1); +} diff --git a/skills/website-debug/scripts/browser-dom.js b/skills/website-debug/scripts/browser-dom.js new file mode 100755 index 0000000..4f3c420 --- /dev/null +++ b/skills/website-debug/scripts/browser-dom.js @@ -0,0 +1,186 @@ +#!/usr/bin/env node +/** + * browser-dom.js - Get DOM snapshot or element HTML + * + * Usage: + * ./browser-dom.js # Full page structure summary + * ./browser-dom.js "body" # Element's outer HTML + * ./browser-dom.js ".header" --inner # Element's inner HTML + * ./browser-dom.js --tree # DOM tree visualization + */ + +import puppeteer from "puppeteer-core"; + +const args = process.argv.slice(2); +const selector = args.find(a => !a.startsWith("--")); +const inner = args.includes("--inner"); +const tree = args.includes("--tree"); +const depth = parseInt(args.find(a => a.startsWith("--depth="))?.split("=")[1]) || 3; +const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222"; + +if (args.includes("--help") || args.includes("-h")) { + console.log(` +browser-dom.js - DOM inspection and snapshots + +Usage: + ./browser-dom.js [selector] [options] + +Options: + --inner Get inner HTML instead of outer + --tree Show DOM tree visualization + --depth=N Tree depth (default: 3) + --port=PORT Connect to custom debug port (default: 9222) + +Examples: + ./browser-dom.js # Page summary + ./browser-dom.js "body" # Full body HTML + ./browser-dom.js ".nav" --inner # Nav inner HTML + ./browser-dom.js --tree # DOM tree + ./browser-dom.js --tree --depth=5 # Deeper tree +`); + process.exit(0); +} + +try { + const browser = await puppeteer.connect({ + browserURL: `http://localhost:${port}`, + defaultViewport: null + }); + + const pages = await browser.pages(); + const page = pages[pages.length - 1]; + + if (!page) { + console.error("✗ No active tab found"); + process.exit(1); + } + + if (tree) { + // DOM tree visualization + const treeData = await page.evaluate((maxDepth) => { + const buildTree = (el, currentDepth, maxDepth) => { + if (currentDepth > maxDepth) return null; + + const tag = el.tagName?.toLowerCase() || "#text"; + if (tag === "script" || tag === "style" || tag === "#text") return null; + + let label = tag; + if (el.id) label += `#${el.id}`; + if (el.className && typeof el.className === "string") { + const classes = el.className.trim().split(/\s+/).slice(0, 2).join("."); + if (classes) label += `.${classes}`; + } + + const children = []; + for (const child of el.children || []) { + const childTree = buildTree(child, currentDepth + 1, maxDepth); + if (childTree) children.push(childTree); + } + + return { label, children }; + }; + + return buildTree(document.body, 0, maxDepth); + }, depth); + + const printTree = (node, prefix = "", isLast = true) => { + if (!node) return; + const marker = isLast ? "└── " : "├── "; + console.log(prefix + marker + node.label); + + const childPrefix = prefix + (isLast ? " " : "│ "); + node.children.forEach((child, i) => { + printTree(child, childPrefix, i === node.children.length - 1); + }); + }; + + console.log("DOM Tree:\n"); + printTree(treeData); + + } else if (selector) { + // Get specific element + const html = await page.evaluate((sel, getInner) => { + const el = document.querySelector(sel); + if (!el) return null; + return getInner ? el.innerHTML : el.outerHTML; + }, selector, inner); + + if (html === null) { + console.error(`✗ Element not found: ${selector}`); + process.exit(1); + } + + console.log(html); + + } else { + // Page summary + const summary = await page.evaluate(() => { + const countElements = (sel) => document.querySelectorAll(sel).length; + + return { + title: document.title, + url: location.href, + doctype: document.doctype?.name || "none", + charset: document.characterSet, + viewport: document.querySelector('meta[name="viewport"]')?.content || "not set", + elements: { + total: document.querySelectorAll("*").length, + divs: countElements("div"), + spans: countElements("span"), + links: countElements("a"), + images: countElements("img"), + buttons: countElements("button"), + inputs: countElements("input"), + forms: countElements("form"), + scripts: countElements("script"), + styles: countElements("style, link[rel='stylesheet']") + }, + headings: { + h1: countElements("h1"), + h2: countElements("h2"), + h3: countElements("h3"), + h4: countElements("h4") + }, + semantics: { + header: countElements("header"), + nav: countElements("nav"), + main: countElements("main"), + article: countElements("article"), + section: countElements("section"), + aside: countElements("aside"), + footer: countElements("footer") + } + }; + }); + + console.log("Page Summary"); + console.log("============"); + console.log(`Title: ${summary.title}`); + console.log(`URL: ${summary.url}`); + console.log(`Charset: ${summary.charset}`); + console.log(`Viewport: ${summary.viewport}`); + console.log(""); + console.log("Element Counts:"); + console.log(` Total: ${summary.elements.total}`); + console.log(` Divs: ${summary.elements.divs}, Spans: ${summary.elements.spans}`); + console.log(` Links: ${summary.elements.links}, Images: ${summary.elements.images}`); + console.log(` Buttons: ${summary.elements.buttons}, Inputs: ${summary.elements.inputs}, Forms: ${summary.elements.forms}`); + console.log(` Scripts: ${summary.elements.scripts}, Stylesheets: ${summary.elements.styles}`); + console.log(""); + console.log("Headings:", `H1:${summary.headings.h1} H2:${summary.headings.h2} H3:${summary.headings.h3} H4:${summary.headings.h4}`); + console.log(""); + console.log("Semantic Elements:"); + Object.entries(summary.semantics).forEach(([tag, count]) => { + if (count > 0) console.log(` <${tag}>: ${count}`); + }); + } + + await browser.disconnect(); +} catch (e) { + if (e.message?.includes("ECONNREFUSED")) { + console.error("✗ Cannot connect to browser. Run: ./browser-start.js"); + } else { + console.error(`✗ DOM inspection failed: ${e.message}`); + } + process.exit(1); +} diff --git a/skills/website-debug/scripts/browser-eval.js b/skills/website-debug/scripts/browser-eval.js new file mode 100755 index 0000000..1df43d3 --- /dev/null +++ b/skills/website-debug/scripts/browser-eval.js @@ -0,0 +1,121 @@ +#!/usr/bin/env node +/** + * browser-eval.js - Execute JavaScript in page context + * + * Usage: + * ./browser-eval.js 'document.title' + * ./browser-eval.js 'document.querySelectorAll("a").length' + * ./browser-eval.js 'getComputedStyle(document.body).backgroundColor' + */ + +import puppeteer from "puppeteer-core"; + +const code = process.argv.slice(2).filter(a => !a.startsWith("--")).join(" "); +const port = process.argv.find(a => a.startsWith("--port="))?.split("=")[1] || "9222"; +const json = process.argv.includes("--json"); + +if (!code || process.argv.includes("--help")) { + console.log(` +browser-eval.js - Execute JavaScript in page context + +Usage: + ./browser-eval.js '' + +Options: + --json Output result as JSON + --port=PORT Connect to custom debug port (default: 9222) + +Examples: + ./browser-eval.js 'document.title' + ./browser-eval.js 'document.querySelectorAll("a").length' + ./browser-eval.js 'getComputedStyle(document.querySelector(".header")).display' + ./browser-eval.js '[...document.querySelectorAll("h1")].map(e => e.textContent)' + +CSS Debugging Examples: + # Get computed style of an element + ./browser-eval.js 'getComputedStyle(document.querySelector(".btn")).padding' + + # Check if element is visible + ./browser-eval.js 'getComputedStyle(document.querySelector("#modal")).display' + + # Get bounding rect + ./browser-eval.js 'document.querySelector(".hero").getBoundingClientRect()' + + # Find elements with specific style + ./browser-eval.js '[...document.querySelectorAll("*")].filter(e => getComputedStyle(e).position === "fixed").length' + +DOM Inspection Examples: + # Get outer HTML + ./browser-eval.js 'document.querySelector("nav").outerHTML' + + # Count elements + ./browser-eval.js 'document.querySelectorAll(".error").length' + + # Get all class names on body + ./browser-eval.js '[...document.body.classList]' +`); + process.exit(0); +} + +try { + const browser = await puppeteer.connect({ + browserURL: `http://localhost:${port}`, + defaultViewport: null + }); + + const pages = await browser.pages(); + const page = pages[pages.length - 1]; + + if (!page) { + console.error("✗ No active tab found"); + process.exit(1); + } + + // Execute in async context to support await + const result = await page.evaluate((c) => { + const AsyncFunction = (async () => {}).constructor; + return new AsyncFunction(`return (${c})`)(); + }, code); + + // Format output + if (json) { + console.log(JSON.stringify(result, null, 2)); + } else if (Array.isArray(result)) { + if (result.length === 0) { + console.log("(empty array)"); + } else if (typeof result[0] === "object") { + // Array of objects - format nicely + result.forEach((item, i) => { + if (i > 0) console.log(""); + Object.entries(item).forEach(([key, value]) => { + console.log(`${key}: ${value}`); + }); + }); + } else { + // Simple array + result.forEach(item => console.log(item)); + } + } else if (typeof result === "object" && result !== null) { + // Single object + Object.entries(result).forEach(([key, value]) => { + console.log(`${key}: ${value}`); + }); + } else if (result === undefined) { + console.log("(undefined)"); + } else if (result === null) { + console.log("(null)"); + } else { + console.log(result); + } + + await browser.disconnect(); +} catch (e) { + if (e.message?.includes("ECONNREFUSED")) { + console.error("✗ Cannot connect to browser. Run: ./browser-start.js"); + } else if (e.message?.includes("Evaluation failed")) { + console.error(`✗ JavaScript error: ${e.message.replace("Evaluation failed: ", "")}`); + } else { + console.error(`✗ Eval failed: ${e.message}`); + } + process.exit(1); +} diff --git a/skills/website-debug/scripts/browser-nav.js b/skills/website-debug/scripts/browser-nav.js new file mode 100755 index 0000000..8a7223d --- /dev/null +++ b/skills/website-debug/scripts/browser-nav.js @@ -0,0 +1,66 @@ +#!/usr/bin/env node +/** + * browser-nav.js - Navigate to URL in Chrome/WebKit + * + * Usage: + * ./browser-nav.js https://example.com # Navigate current tab + * ./browser-nav.js https://example.com --new # Open in new tab + */ + +import puppeteer from "puppeteer-core"; + +const url = process.argv[2]; +const newTab = process.argv.includes("--new"); +const port = process.argv.find(a => a.startsWith("--port="))?.split("=")[1] || "9222"; + +if (!url || url.startsWith("--")) { + console.log(` +browser-nav.js - Navigate to URL + +Usage: + ./browser-nav.js [--new] [--port=PORT] + +Options: + --new Open in new tab instead of current + --port=PORT Connect to custom debug port (default: 9222) + +Examples: + ./browser-nav.js https://example.com + ./browser-nav.js https://localhost:3000 --new + ./browser-nav.js file:///path/to/page.html +`); + process.exit(1); +} + +try { + const browser = await puppeteer.connect({ + browserURL: `http://localhost:${port}`, + defaultViewport: null + }); + + let page; + if (newTab) { + page = await browser.newPage(); + } else { + const pages = await browser.pages(); + page = pages[pages.length - 1] || await browser.newPage(); + } + + await page.goto(url, { + waitUntil: "domcontentloaded", + timeout: 30000 + }); + + const title = await page.title(); + console.log(`✓ ${newTab ? "Opened" : "Navigated to"}: ${url}`); + console.log(` Title: ${title}`); + + await browser.disconnect(); +} catch (e) { + if (e.message?.includes("ECONNREFUSED")) { + console.error("✗ Cannot connect to browser. Run: ./browser-start.js"); + } else { + console.error(`✗ Navigation failed: ${e.message}`); + } + process.exit(1); +} diff --git a/skills/website-debug/scripts/browser-network.js b/skills/website-debug/scripts/browser-network.js new file mode 100755 index 0000000..061d285 --- /dev/null +++ b/skills/website-debug/scripts/browser-network.js @@ -0,0 +1,186 @@ +#!/usr/bin/env node +/** + * browser-network.js - Monitor network requests + * + * Usage: + * ./browser-network.js # Show recent requests + * ./browser-network.js --watch # Watch requests in real-time + * ./browser-network.js --failures # Show only failed requests + * ./browser-network.js --xhr # Show only XHR/fetch requests + */ + +import puppeteer from "puppeteer-core"; + +const args = process.argv.slice(2); +const watch = args.includes("--watch"); +const failures = args.includes("--failures"); +const xhrOnly = args.includes("--xhr"); +const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222"; + +if (args.includes("--help") || args.includes("-h")) { + console.log(` +browser-network.js - Network request monitoring + +Usage: + ./browser-network.js [options] + +Options: + --watch Watch requests in real-time + --failures Show only failed requests (4xx, 5xx, network errors) + --xhr Show only XHR/fetch requests (API calls) + --port=PORT Connect to custom debug port (default: 9222) + +Output includes: + - Request method and URL + - Response status + - Response time + - Content type + - Size (when available) + +Examples: + ./browser-network.js + ./browser-network.js --watch + ./browser-network.js --failures + ./browser-network.js --xhr --watch +`); + process.exit(0); +} + +try { + const browser = await puppeteer.connect({ + browserURL: `http://localhost:${port}`, + defaultViewport: null + }); + + const pages = await browser.pages(); + const page = pages[pages.length - 1]; + + if (!page) { + console.error("✗ No active tab found"); + process.exit(1); + } + + const client = await page.target().createCDPSession(); + await client.send("Network.enable"); + + const requests = new Map(); + + const formatSize = (bytes) => { + if (!bytes) return "?"; + if (bytes < 1024) return `${bytes}B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`; + return `${(bytes / 1024 / 1024).toFixed(1)}MB`; + }; + + const shouldShow = (req, resp) => { + if (failures) { + const status = resp?.status || 0; + return status >= 400 || status === 0; + } + if (xhrOnly) { + const type = req.type || req.resourceType; + return type === "XHR" || type === "Fetch"; + } + return true; + }; + + const formatRequest = (req, resp, timing) => { + const method = req.method || "GET"; + const url = new URL(req.url); + const path = url.pathname + url.search; + const status = resp?.status || "pending"; + const time = timing ? `${Math.round(timing)}ms` : "?"; + const size = formatSize(resp?.encodedDataLength); + const type = resp?.mimeType?.split("/")[1]?.split(";")[0] || "?"; + + const statusColor = status >= 400 ? "❌" : status >= 300 ? "↩️" : "✓"; + + return `${statusColor} ${method.padEnd(6)} ${status} ${path.slice(0, 60).padEnd(60)} ${time.padStart(7)} ${size.padStart(8)} ${type}`; + }; + + if (watch) { + console.log("Watching network requests... (Ctrl+C to stop)\n"); + console.log(" Method Status URL".padEnd(80) + "Time".padStart(10) + "Size".padStart(10) + " Type"); + console.log("-".repeat(110)); + + client.on("Network.requestWillBeSent", ({ requestId, request }) => { + requests.set(requestId, { request, startTime: Date.now() }); + }); + + client.on("Network.responseReceived", ({ requestId, response }) => { + const req = requests.get(requestId); + if (req) { + req.response = response; + } + }); + + client.on("Network.loadingFinished", ({ requestId, encodedDataLength }) => { + const req = requests.get(requestId); + if (req) { + const timing = Date.now() - req.startTime; + if (req.response) { + req.response.encodedDataLength = encodedDataLength; + } + if (shouldShow(req.request, req.response)) { + console.log(formatRequest(req.request, req.response, timing)); + } + requests.delete(requestId); + } + }); + + client.on("Network.loadingFailed", ({ requestId, errorText }) => { + const req = requests.get(requestId); + if (req) { + const timing = Date.now() - req.startTime; + console.log(`❌ ${req.request.method.padEnd(6)} ERR ${req.request.url.slice(0, 60).padEnd(60)} ${String(timing + "ms").padStart(7)} - ${errorText}`); + requests.delete(requestId); + } + }); + + // Keep alive + await new Promise(() => {}); + } else { + // Get recent requests via Performance API + const requests = await page.evaluate(() => { + return performance.getEntriesByType("resource").map(entry => ({ + name: entry.name, + type: entry.initiatorType, + duration: Math.round(entry.duration), + size: entry.transferSize, + status: entry.responseStatus + })); + }); + + if (requests.length === 0) { + console.log("No requests captured. Try --watch to monitor in real-time."); + } else { + console.log("Recent network requests:\n"); + console.log("Type".padEnd(10) + "Status".padEnd(8) + "Duration".padEnd(10) + "Size".padEnd(10) + "URL"); + console.log("-".repeat(100)); + + requests + .filter(r => { + if (failures) return r.status >= 400; + if (xhrOnly) return r.type === "xmlhttprequest" || r.type === "fetch"; + return true; + }) + .slice(-30) + .forEach(r => { + const url = new URL(r.name); + const path = url.pathname.slice(0, 50); + console.log( + `${r.type.slice(0, 9).padEnd(10)}${String(r.status || "?").padEnd(8)}${String(r.duration + "ms").padEnd(10)}${formatSize(r.size).padEnd(10)}${path}` + ); + }); + } + + await browser.disconnect(); + } +} catch (e) { + if (e.message?.includes("ECONNREFUSED")) { + console.error("✗ Cannot connect to browser. Run: ./browser-start.js"); + } else { + console.error(`✗ Network monitoring failed: ${e.message}`); + } + process.exit(1); +} diff --git a/skills/website-debug/scripts/browser-pick.js b/skills/website-debug/scripts/browser-pick.js new file mode 100755 index 0000000..c9f8155 --- /dev/null +++ b/skills/website-debug/scripts/browser-pick.js @@ -0,0 +1,251 @@ +#!/usr/bin/env node +/** + * browser-pick.js - Interactive element picker for DOM selection + * + * IMPORTANT: Use this when the user wants to select specific DOM elements. + * The user can click elements, multi-select with Cmd/Ctrl+Click, and press Enter when done. + * Returns CSS selectors and element details. + * + * Usage: + * ./browser-pick.js "Click on the broken element" + * ./browser-pick.js "Select the buttons to style" + */ + +import puppeteer from "puppeteer-core"; + +const message = process.argv.slice(2).filter(a => !a.startsWith("--")).join(" ") || "Select an element"; +const port = process.argv.find(a => a.startsWith("--port="))?.split("=")[1] || "9222"; + +if (process.argv.includes("--help") || process.argv.includes("-h")) { + console.log(` +browser-pick.js - Interactive element picker + +Usage: + ./browser-pick.js "" + +The picker lets users visually select elements: + - Click: Select single element + - Cmd/Ctrl+Click: Add to multi-selection + - Enter: Finish multi-selection + - Escape: Cancel + +Returns: + - CSS selector(s) for selected elements + - Tag name, ID, classes + - Text content preview + - Computed styles summary + - Parent chain for context + +Examples: + ./browser-pick.js "Click on the element that looks wrong" + ./browser-pick.js "Select all the buttons to update" + ./browser-pick.js "Which element should I fix?" +`); + process.exit(0); +} + +try { + const browser = await puppeteer.connect({ + browserURL: `http://localhost:${port}`, + defaultViewport: null + }); + + const pages = await browser.pages(); + const page = pages[pages.length - 1]; + + if (!page) { + console.error("✗ No active tab found"); + process.exit(1); + } + + console.log(`Picker active: ${message}`); + console.log(" Click to select, Cmd/Ctrl+Click for multi-select, Enter to finish, Escape to cancel\n"); + + // Inject picker helper + await page.evaluate(() => { + if (!window.__webdebugPick) { + window.__webdebugPick = async (message) => { + return new Promise((resolve) => { + const selections = []; + const selectedElements = new Set(); + + // Create overlay + const overlay = document.createElement("div"); + overlay.id = "__webdebug-picker-overlay"; + overlay.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;z-index:2147483647;pointer-events:none"; + + // Highlight element + const highlight = document.createElement("div"); + highlight.style.cssText = "position:absolute;border:2px solid #3b82f6;background:rgba(59,130,246,0.15);transition:all 0.05s;pointer-events:none"; + overlay.appendChild(highlight); + + // Banner + const banner = document.createElement("div"); + banner.style.cssText = "position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#1f2937;color:white;padding:12px 24px;border-radius:8px;font:14px system-ui,sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.3);pointer-events:auto;z-index:2147483647;max-width:80vw;text-align:center"; + + const updateBanner = () => { + banner.innerHTML = `${message}
${selections.length} selected · Cmd/Ctrl+Click to add · Enter to finish · Escape to cancel`; + }; + updateBanner(); + + document.body.appendChild(banner); + document.body.appendChild(overlay); + + // Build unique selector + const buildSelector = (el) => { + if (el.id) return `#${el.id}`; + + let selector = el.tagName.toLowerCase(); + if (el.className && typeof el.className === 'string') { + const classes = el.className.trim().split(/\s+/).filter(c => c && !c.startsWith('__')); + if (classes.length) selector += '.' + classes.slice(0, 3).join('.'); + } + + // Add nth-child if needed for uniqueness + const parent = el.parentElement; + if (parent) { + const siblings = [...parent.children].filter(c => c.tagName === el.tagName); + if (siblings.length > 1) { + const index = siblings.indexOf(el) + 1; + selector += `:nth-child(${index})`; + } + } + + return selector; + }; + + // Get full selector path + const getFullSelector = (el) => { + const parts = []; + let current = el; + while (current && current !== document.body && parts.length < 4) { + parts.unshift(buildSelector(current)); + current = current.parentElement; + } + return parts.join(' > '); + }; + + // Build element info + const buildElementInfo = (el) => { + const rect = el.getBoundingClientRect(); + const styles = getComputedStyle(el); + + return { + selector: getFullSelector(el), + tag: el.tagName.toLowerCase(), + id: el.id || null, + classes: el.className?.toString().trim() || null, + text: el.textContent?.trim().slice(0, 100) || null, + dimensions: `${Math.round(rect.width)}×${Math.round(rect.height)}`, + position: `${Math.round(rect.left)},${Math.round(rect.top)}`, + display: styles.display, + visibility: styles.visibility, + opacity: styles.opacity, + zIndex: styles.zIndex, + overflow: styles.overflow + }; + }; + + const cleanup = () => { + document.removeEventListener("mousemove", onMove, true); + document.removeEventListener("click", onClick, true); + document.removeEventListener("keydown", onKey, true); + overlay.remove(); + banner.remove(); + selectedElements.forEach(el => { + el.style.outline = el.__originalOutline || ""; + delete el.__originalOutline; + }); + }; + + const onMove = (e) => { + const el = document.elementFromPoint(e.clientX, e.clientY); + if (!el || overlay.contains(el) || banner.contains(el)) { + highlight.style.display = "none"; + return; + } + const r = el.getBoundingClientRect(); + highlight.style.display = "block"; + highlight.style.top = r.top + "px"; + highlight.style.left = r.left + "px"; + highlight.style.width = r.width + "px"; + highlight.style.height = r.height + "px"; + }; + + const onClick = (e) => { + if (banner.contains(e.target)) return; + + e.preventDefault(); + e.stopPropagation(); + + const el = document.elementFromPoint(e.clientX, e.clientY); + if (!el || overlay.contains(el) || banner.contains(el)) return; + + if (e.metaKey || e.ctrlKey) { + // Multi-select mode + if (!selectedElements.has(el)) { + selectedElements.add(el); + el.__originalOutline = el.style.outline; + el.style.outline = "3px solid #10b981"; + selections.push(buildElementInfo(el)); + updateBanner(); + } + } else { + // Single select - finish + cleanup(); + if (selections.length > 0) { + resolve(selections); + } else { + resolve([buildElementInfo(el)]); + } + } + }; + + const onKey = (e) => { + if (e.key === "Escape") { + e.preventDefault(); + cleanup(); + resolve(null); + } else if (e.key === "Enter" && selections.length > 0) { + e.preventDefault(); + cleanup(); + resolve(selections); + } + }; + + document.addEventListener("mousemove", onMove, true); + document.addEventListener("click", onClick, true); + document.addEventListener("keydown", onKey, true); + }); + }; + } + }); + + // Run picker + const result = await page.evaluate((msg) => window.__webdebugPick(msg), message); + + if (result === null) { + console.log("Picker cancelled"); + } else if (Array.isArray(result)) { + console.log(`Selected ${result.length} element(s):\n`); + + result.forEach((info, i) => { + if (i > 0) console.log("---"); + console.log(`Selector: ${info.selector}`); + console.log(`Tag: ${info.tag}${info.id ? ` #${info.id}` : ""}${info.classes ? ` .${info.classes.split(" ").join(".")}` : ""}`); + console.log(`Size: ${info.dimensions} at (${info.position})`); + console.log(`Display: ${info.display}, Visibility: ${info.visibility}, Opacity: ${info.opacity}`); + if (info.zIndex !== "auto") console.log(`Z-Index: ${info.zIndex}`); + if (info.text) console.log(`Text: "${info.text.slice(0, 50)}${info.text.length > 50 ? "..." : ""}"`); + }); + } + + await browser.disconnect(); +} catch (e) { + if (e.message?.includes("ECONNREFUSED")) { + console.error("✗ Cannot connect to browser. Run: ./browser-start.js"); + } else { + console.error(`✗ Picker failed: ${e.message}`); + } + process.exit(1); +} diff --git a/skills/website-debug/scripts/browser-resize.js b/skills/website-debug/scripts/browser-resize.js new file mode 100755 index 0000000..4eb671a --- /dev/null +++ b/skills/website-debug/scripts/browser-resize.js @@ -0,0 +1,102 @@ +#!/usr/bin/env node +/** + * browser-resize.js - Resize viewport for responsive testing + * + * Usage: + * ./browser-resize.js 375 667 # Custom width x height + * ./browser-resize.js --mobile # iPhone SE (375x667) + * ./browser-resize.js --tablet # iPad (768x1024) + * ./browser-resize.js --desktop # Desktop (1920x1080) + */ + +import puppeteer from "puppeteer-core"; + +const args = process.argv.slice(2); +const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222"; + +// Preset sizes +const presets = { + "--mobile": { width: 375, height: 667, name: "Mobile (iPhone SE)" }, + "--iphone": { width: 390, height: 844, name: "iPhone 14" }, + "--iphone-pro": { width: 393, height: 852, name: "iPhone 14 Pro" }, + "--android": { width: 412, height: 915, name: "Android (Pixel 7)" }, + "--tablet": { width: 768, height: 1024, name: "Tablet (iPad)" }, + "--ipad-pro": { width: 1024, height: 1366, name: "iPad Pro 12.9\"" }, + "--laptop": { width: 1366, height: 768, name: "Laptop" }, + "--desktop": { width: 1920, height: 1080, name: "Desktop (1080p)" }, + "--4k": { width: 3840, height: 2160, name: "4K" } +}; + +if (args.includes("--help") || args.includes("-h") || args.length === 0) { + console.log(` +browser-resize.js - Resize viewport for responsive testing + +Usage: + ./browser-resize.js # Custom dimensions + ./browser-resize.js --preset # Use preset size + +Presets: + --mobile 375×667 (iPhone SE) + --iphone 390×844 (iPhone 14) + --iphone-pro 393×852 (iPhone 14 Pro) + --android 412×915 (Pixel 7) + --tablet 768×1024 (iPad) + --ipad-pro 1024×1366 (iPad Pro 12.9") + --laptop 1366×768 (Laptop) + --desktop 1920×1080 (Desktop 1080p) + --4k 3840×2160 (4K) + +Examples: + ./browser-resize.js 375 667 + ./browser-resize.js --mobile + ./browser-resize.js --tablet && ./browser-screenshot.js +`); + process.exit(0); +} + +// Determine dimensions +let width, height, name; + +const presetArg = args.find(a => presets[a]); +if (presetArg) { + ({ width, height, name } = presets[presetArg]); +} else { + width = parseInt(args[0]); + height = parseInt(args[1]); + + if (isNaN(width) || isNaN(height)) { + console.error("✗ Invalid dimensions. Use: ./browser-resize.js "); + process.exit(1); + } + + name = `Custom (${width}×${height})`; +} + +try { + const browser = await puppeteer.connect({ + browserURL: `http://localhost:${port}`, + defaultViewport: null + }); + + const pages = await browser.pages(); + const page = pages[pages.length - 1]; + + if (!page) { + console.error("✗ No active tab found"); + process.exit(1); + } + + await page.setViewport({ width, height }); + + console.log(`✓ Viewport resized to ${width}×${height}`); + console.log(` ${name}`); + + await browser.disconnect(); +} catch (e) { + if (e.message?.includes("ECONNREFUSED")) { + console.error("✗ Cannot connect to browser. Run: ./browser-start.js"); + } else { + console.error(`✗ Resize failed: ${e.message}`); + } + process.exit(1); +} diff --git a/skills/website-debug/scripts/browser-screenshot.js b/skills/website-debug/scripts/browser-screenshot.js new file mode 100755 index 0000000..c563968 --- /dev/null +++ b/skills/website-debug/scripts/browser-screenshot.js @@ -0,0 +1,90 @@ +#!/usr/bin/env node +/** + * browser-screenshot.js - Capture screenshot of current viewport + * + * Usage: + * ./browser-screenshot.js # Viewport screenshot + * ./browser-screenshot.js --full # Full page screenshot + * ./browser-screenshot.js --selector=".main" # Element screenshot + * ./browser-screenshot.js --output=/path.png # Custom output path + */ + +import puppeteer from "puppeteer-core"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const args = process.argv.slice(2); +const fullPage = args.includes("--full"); +const selector = args.find(a => a.startsWith("--selector="))?.split("=")[1]; +const outputPath = args.find(a => a.startsWith("--output="))?.split("=")[1]; +const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222"; + +if (args.includes("--help") || args.includes("-h")) { + console.log(` +browser-screenshot.js - Capture screenshot + +Usage: + ./browser-screenshot.js [options] + +Options: + --full Capture full page (scrollable content) + --selector=SEL Capture specific element only + --output=PATH Save to custom path (default: temp file) + --port=PORT Connect to custom debug port (default: 9222) + +Examples: + ./browser-screenshot.js + ./browser-screenshot.js --full + ./browser-screenshot.js --selector=".hero-section" + ./browser-screenshot.js --output=./screenshot.png +`); + process.exit(0); +} + +try { + const browser = await puppeteer.connect({ + browserURL: `http://localhost:${port}`, + defaultViewport: null + }); + + const pages = await browser.pages(); + const page = pages[pages.length - 1]; + + if (!page) { + console.error("✗ No active tab found"); + process.exit(1); + } + + const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19); + const filename = outputPath || join(tmpdir(), `screenshot-${timestamp}.png`); + + if (selector) { + const element = await page.$(selector); + if (!element) { + console.error(`✗ Element not found: ${selector}`); + process.exit(1); + } + await element.screenshot({ path: filename }); + console.log(`✓ Element screenshot saved`); + } else { + await page.screenshot({ + path: filename, + fullPage + }); + console.log(`✓ ${fullPage ? "Full page" : "Viewport"} screenshot saved`); + } + + console.log(` Path: ${filename}`); + + // Output just the path for easy piping + console.log(filename); + + await browser.disconnect(); +} catch (e) { + if (e.message?.includes("ECONNREFUSED")) { + console.error("✗ Cannot connect to browser. Run: ./browser-start.js"); + } else { + console.error(`✗ Screenshot failed: ${e.message}`); + } + process.exit(1); +} diff --git a/skills/website-debug/scripts/browser-start.js b/skills/website-debug/scripts/browser-start.js new file mode 100755 index 0000000..54e2b33 --- /dev/null +++ b/skills/website-debug/scripts/browser-start.js @@ -0,0 +1,190 @@ +#!/usr/bin/env node +/** + * browser-start.js - Start Chrome or WebKit with remote debugging + * + * Usage: + * ./browser-start.js # Fresh Chrome profile + * ./browser-start.js --profile # Chrome with user profile (preserves logins) + * ./browser-start.js --webkit # Playwright WebKit (Safari-like) + * ./browser-start.js --headless # Headless mode + */ + +import { spawn, execSync } from "node:child_process"; +import { existsSync, mkdirSync } from "node:fs"; +import { homedir, platform, tmpdir } from "node:os"; +import { join } from "node:path"; + +const args = process.argv.slice(2); +const useProfile = args.includes("--profile"); +const useWebKit = args.includes("--webkit"); +const headless = args.includes("--headless"); +const port = args.find(a => a.startsWith("--port="))?.split("=")[1] || "9222"; + +if (args.includes("--help") || args.includes("-h")) { + console.log(` +browser-start.js - Start browser with remote debugging + +Usage: + ./browser-start.js [options] + +Options: + --profile Copy user's Chrome profile (preserves logins, cookies) + --webkit Use Playwright WebKit instead of Chrome (Safari-like) + --headless Run in headless mode + --port=PORT Use custom debug port (default: 9222) + --help Show this help message + +Examples: + ./browser-start.js # Fresh Chrome profile + ./browser-start.js --profile # Chrome with your logins + ./browser-start.js --webkit # Safari/WebKit via Playwright + ./browser-start.js --headless # Headless Chrome +`); + process.exit(0); +} + +const cacheDir = join(homedir(), ".cache", "website-debug"); +mkdirSync(cacheDir, { recursive: true }); + +async function startChrome() { + // Kill existing Chrome debug instances + try { + if (platform() === "darwin") { + execSync("killall 'Google Chrome' 2>/dev/null", { stdio: "ignore" }); + } else if (platform() === "linux") { + execSync("pkill -f 'chrome.*remote-debugging' 2>/dev/null", { stdio: "ignore" }); + } + } catch {} + + await new Promise(r => setTimeout(r, 1000)); + + const profileDir = join(cacheDir, "chrome-profile"); + + if (useProfile) { + // Find and copy user's Chrome profile + const userProfilePaths = { + darwin: join(homedir(), "Library/Application Support/Google/Chrome"), + linux: join(homedir(), ".config/google-chrome"), + win32: join(homedir(), "AppData/Local/Google/Chrome/User Data") + }; + + const userProfile = userProfilePaths[platform()]; + if (existsSync(userProfile)) { + console.log("Syncing user profile (this may take a moment)..."); + try { + execSync(`rsync -a --delete "${userProfile}/" "${profileDir}/"`, { stdio: "pipe" }); + } catch (e) { + console.log("Warning: Could not sync profile, using fresh profile"); + } + } + } + + // Find Chrome executable + const chromePaths = { + darwin: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + linux: "/usr/bin/google-chrome", + win32: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" + }; + + const chromePath = chromePaths[platform()]; + if (!existsSync(chromePath)) { + console.error(`✗ Chrome not found at ${chromePath}`); + process.exit(1); + } + + const chromeArgs = [ + `--remote-debugging-port=${port}`, + `--user-data-dir=${profileDir}`, + "--no-first-run", + "--no-default-browser-check" + ]; + + if (headless) { + chromeArgs.push("--headless=new"); + } + + // Start Chrome detached + const chrome = spawn(chromePath, chromeArgs, { + detached: true, + stdio: "ignore" + }); + chrome.unref(); + + // Wait for Chrome to be ready + const puppeteer = await import("puppeteer-core"); + let connected = false; + + for (let i = 0; i < 30; i++) { + try { + const browser = await puppeteer.default.connect({ + browserURL: `http://localhost:${port}`, + defaultViewport: null + }); + await browser.disconnect(); + connected = true; + break; + } catch { + await new Promise(r => setTimeout(r, 500)); + } + } + + if (!connected) { + console.error("✗ Failed to connect to Chrome"); + process.exit(1); + } + + console.log(`✓ Chrome started on :${port}${useProfile ? " with user profile" : ""}${headless ? " (headless)" : ""}`); +} + +async function startWebKit() { + const stateFile = join(cacheDir, "webkit-state.json"); + + try { + const { webkit } = await import("playwright"); + + console.log("Starting WebKit browser..."); + + const browser = await webkit.launchPersistentContext(join(cacheDir, "webkit-profile"), { + headless, + viewport: null, + // Save browser endpoint for other scripts + }); + + // Store connection info + const endpoint = browser.browser()?.wsEndpoint?.() || "direct-context"; + const fs = await import("node:fs/promises"); + await fs.writeFile(stateFile, JSON.stringify({ + type: "webkit", + endpoint, + pid: process.pid + })); + + console.log(`✓ WebKit started${headless ? " (headless)" : ""}`); + console.log(" Press Ctrl+C to stop"); + + // Keep process alive for WebKit + process.on("SIGINT", async () => { + console.log("\nClosing WebKit..."); + await browser.close(); + process.exit(0); + }); + + // Keep alive + await new Promise(() => {}); + + } catch (e) { + if (e.message?.includes("Cannot find module")) { + console.error("✗ Playwright not installed. Run: npm install -g playwright && npx playwright install webkit"); + } else { + console.error(`✗ Failed to start WebKit: ${e.message}`); + } + process.exit(1); + } +} + +// Main +if (useWebKit) { + startWebKit(); +} else { + startChrome(); +} diff --git a/skills/website-debug/scripts/setup.sh b/skills/website-debug/scripts/setup.sh new file mode 100755 index 0000000..81b0054 --- /dev/null +++ b/skills/website-debug/scripts/setup.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# +# setup.sh - Install dependencies for website-debug skill +# + +set -e + +echo "Setting up website-debug skill..." +echo "" + +SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)" +SCRIPTS_DIR="$SKILL_DIR/scripts" + +# Check Node.js +if ! command -v node &> /dev/null; then + echo "❌ Node.js not found. Please install Node.js first:" + echo " https://nodejs.org/ or: brew install node" + exit 1 +fi +echo "✓ Node.js $(node --version)" + +# Check npm +if ! command -v npm &> /dev/null; then + echo "❌ npm not found. Please install npm." + exit 1 +fi +echo "✓ npm $(npm --version)" + +# Install puppeteer-core globally +echo "" +echo "Installing puppeteer-core..." +npm install -g puppeteer-core 2>/dev/null || npm install puppeteer-core --save 2>/dev/null +echo "✓ puppeteer-core installed" + +# Optional: Install Playwright for WebKit support +echo "" +echo "Installing Playwright for Safari/WebKit support (optional)..." +if npm install -g playwright 2>/dev/null; then + npx playwright install webkit 2>/dev/null || true + echo "✓ Playwright + WebKit installed" +else + echo "⚠ Playwright installation skipped (Chrome debugging will still work)" +fi + +# Make scripts executable +echo "" +echo "Making scripts executable..." +chmod +x "$SCRIPTS_DIR"/*.js 2>/dev/null || true +echo "✓ Scripts are executable" + +# Create cache directory +mkdir -p ~/.cache/website-debug +echo "✓ Cache directory ready" + +# Check Chrome +echo "" +if [[ "$OSTYPE" == "darwin"* ]]; then + CHROME_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + if [ -f "$CHROME_PATH" ]; then + echo "✓ Chrome found at $CHROME_PATH" + else + echo "⚠ Chrome not found. Install from: https://www.google.com/chrome/" + fi +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + if command -v google-chrome &> /dev/null; then + echo "✓ Chrome found" + else + echo "⚠ Chrome not found. Install with: sudo apt install google-chrome-stable" + fi +fi + +echo "" +echo "==========================================" +echo "✓ Setup complete!" +echo "" +echo "Quick start:" +echo " cd $SCRIPTS_DIR" +echo " ./browser-start.js # Start Chrome" +echo " ./browser-nav.js http://localhost:3000" +echo " ./browser-screenshot.js # Take screenshot" +echo "" +echo "Or use with Claude Code:" +echo " Ask Claude to 'debug this page' or 'check my site'" +echo "=========================================="