
Vitexec
- 259 installs
- 18 repo stars
- Updated July 29, 2026
- drawcall-ai/vitexec
Run temporary JavaScript snippets inside a live Vite app's browser page to inspect client state, DOM, WebGL, and read browser logs or captured artifacts.
About
vitexec lets an AI agent execute ad-hoc snippets inside a running Vite app's browser to inspect state, DOM, canvas/WebGL, and capture screenshots or profiles. Developers use it to verify, debug, or profile runtime-only behavior without editing app files.
- Reads client state, imported modules, and canvas/WebGL/Three.js state at runtime
- Supports screenshots, recordings, CPU/network/performance traces, and heap snapshots
Vitexec by the numbers
- 259 all-time installs (skills.sh)
- Ranked #144 of 596 Debugging skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
npx skills add https://github.com/drawcall-ai/vitexec --skill vitexecAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 259 |
|---|---|
| repo stars | ★ 18 |
| Last updated | July 29, 2026 |
| Repository | drawcall-ai/vitexec ↗ |
What it does
Run temporary JavaScript snippets inside a live Vite app's browser page to inspect client state, DOM, WebGL, and read browser logs or captured artifacts.
Files
vitexec
Use vitexec when the truth lives in the running browser: client state, imported app modules, DOM, canvas/WebGL, screenshots, recordings, or browser-only errors.
Do not use it for questions static files, unit tests, or TypeScript can answer directly.
References
- For mouse, keyboard, pointer lock, gamepad, or other input, read references/inputs.md.
- For CPU, network, performance timeline, or heap analysis, read references/performance.md.
- For WebXR, read references/webxr.md.
Workflow
1. Identify the page path if it is not /. 2. Write the smallest snippet that performs the user-like action or reads the browser-only state. 3. Run vitexec '<snippet>', adding --path, --gpu, --screenshot, --record, --cpu-profile, --network-trace, --performance-trace, --heap-snapshot, --timeout, or --config only when needed. 4. Treat stdout as browser logs. It starts with logs:.
If vitexec itself is missing, install vitexec with the package manager already used by the project.
vitexec 'console.log("ready")'For structured state, log JSON:
vitexec --path /cart '
import { useCartStore } from "/src/store/cart.ts";
document.querySelector("[data-testid=add-to-cart]")?.click();
await new Promise((resolve) => requestAnimationFrame(resolve));
console.log("cart", JSON.stringify(useCartStore.getState()));
'Guidance
- Prefer importing exported app state over scraping DOM when state is available.
- Use direct state reads for observation and assertions, not to bypass user interaction.
- Use live progress logs and focused assertions to early-exit on failures and see current progress.
- Keep logs concise; overly verbose logs become unreadable and unnecessarily fill the context.
- Prefer browser-root imports such as
/src/store.ts, not local filesystem paths. - Use
--gpufor WebGL, canvas, Three.js, and WebXR behavior. - If the local machine has no usable GPU, use
--gpu --browser-ws-endpoint <ws-url>to connect to a remote Playwright server that was started with the right host-specific GPU settings. - If repeated runs need the same endpoint or artifact settings, prefer
VITEXEC_*environment variables over repeating long flags. - Use screenshots or recordings only when visual evidence matters.
- Do not leave temporary code in the app when
vitexeccan inspect it from outside.
Reading a screenshot as proof
A screenshot is only proof if you read it critically — "something rendered" is not "it works and looks right". When the evidence is a screenshot or clip, look at it for tells of unfinished work and treat any you find as a defect to fix, not as proof of done:
- A character standing in a T-pose (or not animating) — its rig/animation isn't driving the model.
- Flat solid-color boxes/planes standing in for real objects — placeholder geometry that needs a real asset or material.
- Untextured surfaces (a flat-color ground, gray "clay") — missing materials.
- Objects that float with no contact shadow — missing shadows or grounding.
- A flat, raw render with no finishing pass.
Pair the picture with state assertions: confirm the player-visible outcome from real app state (the count changed, the entity was removed, the animation state advanced), not just that the frame drew.
interface:
display_name: "vitexec"
short_description: "Inspect and profile live Vite apps"
default_prompt: "Use $vitexec to inspect or profile a live Vite app with browser logs and artifacts."
Inputs
Test through the same input path a user would use. Do not prove behavior by calling app internals that skip the interaction.
Pattern:
1. Read state to choose a target or assertion. 2. Send realistic input events. 3. Wait one or two animation frames. 4. Read state again to verify.
State reads are for observation, not cheating. Good: game.getSnapshot() to find a canvas target. Bad: game.setScore(999).
Common Inputs
- Mouse/pointer: dispatch
pointerdown,pointerup, thenclickwith realclientX/clientY. - Canvas: convert app coordinates through
canvas.getBoundingClientRect(). - Captured mouse: dispatch a pointer capture/start event, then
mousemovewithmovementX/movementY. - Keyboard: focus the element, dispatch
keydown, update the value, dispatchinput, thenkeyup. - Gamepad: override
navigator.getGamepads(), dispatchgamepadconnected, and advance frames. - WebXR: use webxr.md; drive IWER headset/controllers/hands instead of patching game state.
Minimal pointer shape:
const rect = target.getBoundingClientRect();
const clientX = rect.left + rect.width / 2;
const clientY = rect.top + rect.height / 2;
target.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, button: 0, buttons: 1, clientX, clientY, pointerId: 1, pointerType: "mouse" }));
target.dispatchEvent(new PointerEvent("pointerup", { bubbles: true, button: 0, buttons: 0, clientX, clientY, pointerId: 1, pointerType: "mouse" }));
target.dispatchEvent(new MouseEvent("click", { bubbles: true, button: 0, clientX, clientY }));Performance Analysis
Use vitexec artifacts for live-browser slowness, jank, leaks, or network behavior. Logs stay in stdout; artifacts go to disk.
Choose The Artifact
| Symptom | Capture |
|---|---|
| Expensive JavaScript | --cpu-profile ./artifacts/cpu.cpuprofile |
| Failed/slow/large requests | --network-trace ./artifacts/network.har |
| Jank, long tasks, rendering cost | --performance-trace ./artifacts/performance.trace.json |
| Retained objects/leak | --heap-snapshot ./artifacts/heap.json |
| Visual end state | --screenshot ./artifacts/page.png |
| Temporal visual issue | --record ./artifacts/run.webm |
Capture both --cpu-profile and --performance-trace when unsure whether the issue is JS or browser rendering. Heap output is vitexec-specific: it is decoded into jq-friendly nodes, edges, and summary.
First Queries
jq -r '
([.samples[]?] | length) as $total
| .nodes as $nodes
| [.samples[]?] | group_by(.) | map({id:.[0], count:length, pct:(length*100/$total)}) | sort_by(-.count)[:30]
| .[] as $hit
| ($nodes[] | select(.id==$hit.id)) as $node
| [($hit.count|tostring), (($hit.pct*10|floor/10)|tostring), ($node.callFrame.functionName // "(anonymous)"), ($node.callFrame.url // ""), (($node.callFrame.lineNumber // -1)+1|tostring)] | @tsv
' ./artifacts/cpu.cpuprofilejq '.log.entries[]
| {url:.request.url, method:.request.method, status:.response.status, time:.time, bytes:(.response.bodySize // .response.content.size // 0)}
| select(.status >= 400 or .time > 1000)
' ./artifacts/network.harjq -r '
.traceEvents
| map(select(.ph=="X" and (.dur // 0) > 1000))
| group_by(.name)
| map({name:.[0].name, count:length, totalMs:(map(.dur // 0)|add/1000), maxMs:(map(.dur // 0)|max/1000), cat:(.[0].cat // "")})
| sort_by(-.totalMs)[:30]
| .[] | [.totalMs, .maxMs, .count, .name, .cat] | @tsv
' ./artifacts/performance.trace.jsonjq '.summary.topConstructorsByCount[0:30]' ./artifacts/heap.json
jq '.nodes[] | select(.name | test("Detached|Leak|Store|Cache|Buffer"))' ./artifacts/heap.json
jq '.edges[] | select(.name | test("payload|listeners|subscribers|cache|store"))' ./artifacts/heap.jsonWebXR
Use IWER with vitexec for WebXR tests.
Docs:
- https://meta-quest.github.io/immersive-web-emulation-runtime/getting-started.html
- https://meta-quest.github.io/immersive-web-emulation-runtime/action.html
Do not fake XR outcomes by mutating app state. Install IWER, enter the XR session through the app's normal path, move the emulated headset/controllers/hands, press/select like a user, then inspect app state.
State access is for understanding and assertions, not bypassing interaction.
Shape
vitexec --gpu ./vitexec/webxr-test.tsimport { XRDevice, metaQuest3 } from "iwer";
const xrDevice = new XRDevice(metaQuest3);
xrDevice.installRuntime();
// Trigger the app's normal "Enter VR" path.
await window.xrPrecisionThrow.store.enterVR();
if (xrDevice.sessionOffered) xrDevice.grantOfferedSession();
// Act through IWER.
xrDevice.controllers.right?.position.set(0, 1.25, -0.85);
xrDevice.controllers.right?.updateButtonValue("trigger", 1);
await new Promise((resolve) => requestAnimationFrame(resolve));
xrDevice.controllers.right?.updateButtonValue("trigger", 0);
// Assert through app state.
console.log("xr", JSON.stringify({
active: Boolean(xrDevice.activeSession),
status: window.xrPrecisionThrow.getStatus()
}));Useful IWER controls:
- Headset:
xrDevice.position,xrDevice.quaternion,xrDevice.recenter(). - Controllers:
position,quaternion,updateButtonValue(),updateAxes(). - Hands/platform:
primaryInputMode, hand pinch APIs, visibility state.
Use --record or --screenshot only when visual timing or rendering matters.