원클릭으로
dev-command
// Interact with the running VSCode extension via Playwright. Use when automating, testing, or debugging the OpenCode webview UI.
// Interact with the running VSCode extension via Playwright. Use when automating, testing, or debugging the OpenCode webview UI.
Create a spec sheet for the given feature/fix request in specs/ directory. Use when planning a significant new feature or complex fix.
Implement a single phase of a spec. Use when given a spec file and a phase number to implement.
Browser automation CLI using Playwright. Use when automating browser workflows, filling forms, clicking elements, scraping pages, or debugging web issues.
Review a spec for under-specified areas, bugs, and adherence to the generate-spec skill. Use when asked to review, critique, or check a spec.
Update AGENTS.md files based on session learnings about a topic. Use when documenting patterns, commands, or conventions discovered during development.
Address PR review comments systematically. Use when responding to code review feedback on a pull request.
| name | dev-command |
| description | Interact with the running VSCode extension via Playwright. Use when automating, testing, or debugging the OpenCode webview UI. |
Launch and interact with the VSCode extension using pnpm dev.
pnpm dev # Launch VSCode in background tmux session
pnpm dev exec "<code>" # Execute JS in the webview (frame/page/browser available)
pnpm dev snapshot # Screenshot the VSCode window
pnpm dev stop # Stop the session
The exec command provides three objects:
frame — the webview active-frame (SolidJS UI). Use this for most interactions.page — the top-level VSCode Electron page. Use for screenshots or VSCode-level actions.browser — the Playwright Browser instance.All selectors below target elements inside frame.
# Type a message
pnpm dev exec "await frame.locator('[contenteditable]').pressSequentially('hello world')"
# Submit (Cmd+Enter)
pnpm dev exec "await frame.locator('[contenteditable]').press('Meta+Enter')"
# Clear and retype
pnpm dev exec "await frame.locator('[contenteditable]').fill(''); await frame.locator('[contenteditable]').pressSequentially('new text')"
# Stop generation
pnpm dev exec "await frame.locator('.shortcut-button--stop').click()"
# Submit button
pnpm dev exec "await frame.locator('.shortcut-button--secondary').click()"
# New session
pnpm dev exec "await frame.locator('.new-session-button').click()"
# Session switcher
pnpm dev exec "await frame.locator('.session-switcher-button').click()"
# Allow once
pnpm dev exec "await frame.locator('.permission-button--primary').click()"
# Allow always
pnpm dev exec "await frame.locator('button[aria-label=\"Allow always\"]').click()"
# Reject
pnpm dev exec "await frame.locator('button[aria-label=\"Reject\"]').click()"
# Allow all visible permissions
pnpm dev exec "const btns = await frame.locator('.permission-button--primary').all(); for (const b of btns) await b.click()"
# Get all message texts
pnpm dev exec "const msgs = await frame.locator('.message-text').allTextContents(); return msgs"
# Get last assistant message
pnpm dev exec "const msgs = await frame.locator('.message--assistant .message-text').allTextContents(); return msgs[msgs.length - 1]"
# Wait for assistant response
pnpm dev exec "await frame.locator('.message--assistant').last().waitFor()"
# Check if thinking
pnpm dev exec "return await frame.locator('.loading-indicator').isVisible()"
# Get input text
pnpm dev exec "return await frame.locator('[contenteditable]').textContent()"
# Count messages
pnpm dev exec "return await frame.locator('.message').count()"
# Get session error
pnpm dev exec "return await frame.locator('.session-error').textContent()"
undefined as a second argument to frame.evaluate(() => ...) — it causes "Too many arguments" errors. Just call with one arg.page, not frame — CDP screenshot only works on top-level targets.exec command handles this automatically.pressSequentially instead of fill for realistic typing. fill works for clearing.