Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when the user wants real-time object detection in a web app - boxing, labeling, or counting things from the camera or a photo, or deploying a custom-trained YOLO/YOLOX model - running on-device in the browser with no server and no API keys. The Gipity web-vision-detect kit and object-spotter starter app.
.claude/skills/gipityai-web-vision-detect/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 78% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 59% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 28% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 81% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 3% | 0% |
<!-- GENERATED from platform/docs/skills/web-vision-detect.md by platform/scripts/sync-claude-plugin.ts - do not edit here. -->
> Gipity required. This skill needs the gipity CLI linked to a project. If gipity status errors or shows no project, run the setup flow in the gipity skill first (in Claude Code or Grok: /gipity:setup; in Codex or any other agent, follow the gipity skill's setup steps directly). > > This doc is shared across Gipity surfaces; where it names an agent tool, use the CLI equivalent: add → gipity add <name>, file_write/file_read/file_delete → edit files in the project directory directly (they auto-sync), project_deploy → gipity deploy dev, code_execute → gipity sandbox run. The live version of this doc: gipity skill read web-vision-detect.
web-vision-detect is a kit - a reusable building block added into an existing web app. It runs YOLOX (Apache-2.0) on ONNX Runtime Web so an app can read the camera (or a still image) and do real-time object detection entirely in the browser - boxes, labels, and confidence for the 80 COCO classes, or for a custom-trained model the user brings.
On-device: no server, no upload, the camera stream never leaves the device. Inference runs on WebGPU where the browser has it, with automatic WASM (SIMD) fallback. Web only - it needs getUserMedia (for camera use), WASM, and a canvas, so it runs only on HTTPS or localhost.
This kit is the high-accuracy sibling of web-vision-mediapipe: use that one for gesture and pose, this one when detection is the product - counting, labeling, inventory, custom classes.
Start a fresh detection app - add the object-spotter starter, a fullscreen camera app with the kit pre-installed that boxes, labels, and counts objects live, detects picked photos, and switches between three speed/accuracy presets:
add name=object-spotter title="..."Add detection to an existing web app - install the kit into it:
add name=web-vision-detectThis copies the kit to src/packages/web-vision-detect/ and wires the import map in src/index.html (the kit specifier plus onnxruntime-web). There is no deploy phase - it is pure client-side, so a plain static app needs nothing else.
The whole job is two elements - a <video> for the camera and a <canvas> overlaying it - plus one call:
jsimport { mountDetect } from '@gipity/web-vision-detect'; const vision = await mountDetect({ video: document.querySelector('video'), canvas: document.querySelector('canvas'), model: 'nano', // 'nano' | 'tiny' | 's' | custom spec camera: { facingMode: 'environment' }, // rear camera is the default here onFps: (fps) => { hud.textContent = `${fps} FPS`; }, onResult: ({ detections }) => { /* app logic - shape below */ }, }); await vision.switchModel('s'); // trade frame rate for accuracy const r = await vision.detect(img); // one-off detection on an <img> or canvas vision.stop(); // release camera + free model memory
Each detection is { label, classId, score, box: { x, y, width, height } } in source-frame pixels - drawing on a canvas sized to the frame lines up 1:1 (mountDetect already draws boxes; onResult is for app logic like counting).
Detections arrive sorted by descending score, and suppression is class-aware - a box only suppresses an overlapping box of the same classId. So one real-world object can surface as several overlapping detections with different labels: an ambiguous animal yields both a cat box and a dog box. Don't assume one detection per object. When a class must not fire for a look-alike (dog but never cat), compare the overlapping boxes' scores and require a margin, rather than acting on the first matching label you find.
For a custom loop, compose the low-level exports instead: createDetector, startCamera, createLoop, drawDetections, plus pure-math decodeYolox / decodeYolo / nms. See src/packages/web-vision-detect/examples/ and its README.md.
| model | Download | COCO mAP | Use when | |----------|----------|----------|----------| | nano (default) | 3.7 MB | 25.8 | Instant start, phones, casual demos | | tiny | 20 MB | 32.8 | Noticeably better accuracy, still fast | | s | 36 MB | 40.5 | Accuracy is the point; fine on WebGPU |
Presets are official YOLOX exports hosted on the Gipity CDN, fetched on first use and browser-cached. Custom models: pass model: { url, format, inputSize, labels } - format: 'yolox' for YOLOX exports, format: 'yolo' for Ultralytics YOLOv8/v11 model.export(format='onnx'). This is the deploy path for "I trained a detector on Roboflow/Ultralytics and want it in an app".
web-vision-mediapipe. Detection accuracy, counting, or custom classes -> this kit. The MediaPipe kit's EfficientDet-Lite detector is demo-grade; this kit's tiny/s presets are meaningfully stronger.object-fit: cover on both keeps the overlay aligned. A front camera reads naturally with transform: scaleX(-1) on the video only - pass mirror and the kit flips box geometry while captions stay upright.mountDetect from a click handler, not on page load, and deploy over HTTPS.--camera plays a picture as the webcam. gipity page eval <url> --camera ./street.jpg --wait 8000 "<read the counts the app rendered>" (also on page screenshot/inspect) runs the app's actual camera → detection → onResult path against that image, so the labels and counts you assert are the app's own. Don't ship debug hooks on window to test this - see app-debugging.--fixture - it hosts a real image and injects a fetchable fixtureUrl. A file-upload detector can't be driven through its <input type=file> from an eval (browsers block setting a file input), and you should never deploy a throwaway image into the app to test it. Instead host a real image and run the kit's own detect path over it - --fixture uploads the file, hands your eval a CORS-permissive fixtureUrl, then deletes the hosted copy afterward:bashgipity page eval <url> --fixture ./street.jpg \ "(async()=>{ const { createDetector } = await import('@gipity/web-vision-detect'); const img = new Image(); img.crossOrigin='anonymous'; img.src = fixtureUrl; await img.decode(); const d = await createDetector({ model:'tiny' }); const r = await d.detect(img); await d.close(); const counts={}; for (const x of r.detections) counts[x.label]=(counts[x.label]||0)+1; return counts; })()"
This exercises the deployed page's real kit + model + CDN download over real pixels (crossOrigin='anonymous' keeps the image untainted so ONNX Runtime can read it), so the returned counts are the app's own detector - no fixture is ever synced or shipped.
createLoop (or mountDetect), which skips camera frames while an inference is in flight.nano at camera speed on most laptops and recent phones; plain WASM is several times slower. result.backend / vision.currentBackend() says which one loaded - stay on nano when it reports wasm.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→fail | 8,903 | 5,533 | -38% | 1 | 1 | 0% | 1,705 | 2,571 | +51% | 0 | 0 | — |
case-03 | fail→fail | 15,498 | 4,225 | -73% | 1 | 1 | 0% | 3,205 | 2,587 | -19% | 0 | 0 | — |
case-01 | fail→fail | 16,650 | 6,321 | -62% | 1 | 1 | 0% | 3,459 | 2,772 | -20% | 0 | 0 | — |
case-04 | pass→pass | 15,879 | 6,247 | -61% | 1 | 1 | 0% | 2,333 | 3,338 | +43% | 0 | 0 | — |
case-05 | pass→pass | 22,205 | 16,331 | -26% | 1 | 1 | 0% | 4,015 | 5,253 | +31% | 0 | 0 | — |
case-06 | fail→pass | 10,059 | 5,608 | -44% | 1 | 1 | 0% | 1,824 | 3,249 | +78% | 0 | 0 | — |
case-07 | fail→fail | 8,506 | 3,288 | -61% | 1 | 1 | 0% | 1,514 | 2,709 | +79% | 0 | 0 | — |
case-08 | fail→pass | 14,660 | 10,541 | -28% | 1 | 1 | 0% | 2,649 | 4,209 | +59% | 0 | 0 | — |
case-09 | fail→pass | 14,713 | 4,197 | -71% | 1 | 1 | 0% | 2,422 | 3,111 | +28% | 0 | 0 | — |
case-10 | fail→pass | 13,164 | 9,701 | -26% | 1 | 1 | 0% | 2,221 | 4,016 | +81% | 0 | 0 | — |
case-11 | fail→pass | 13,809 | 1,965 | -86% | 1 | 1 | 0% | 2,480 | 2,559 | +3% | 0 | 0 | — |
case-12 | pass→fail | 13,058 | 7,486 | -43% | 1 | 1 | 0% | 2,300 | 3,576 | +55% | 0 | 0 | — |
case-13 | fail→pass | 16,207 | 5,024 | -69% | 1 | 1 | 0% | 3,460 | 3,200 | -8% | 0 | 0 | — |
case-14 | pass→pass | 12,157 | 7,916 | -35% | 1 | 1 | 0% | 1,969 | 3,522 | +79% | 0 | 0 | — |
case-15 | fail→pass | 15,004 | 7,696 | -49% | 1 | 1 | 0% | 2,615 | 3,538 | +35% | 0 | 0 | — |
case-16 | fail→pass | 14,215 | 4,295 | -70% | 1 | 1 | 0% | 2,809 | 2,935 | +4% | 0 | 0 | — |
case-17 | fail→pass | 12,332 | 2,808 | -77% | 1 | 1 | 0% | 2,007 | 2,678 | +33% | 0 | 0 | — |
case-18 | fail→pass | 7,568 | 2,089 | -72% | 1 | 1 | 0% | 1,309 | 2,551 | +95% | 0 | 0 | — |
case-19 | fail→pass | 18,148 | 5,700 | -69% | 1 | 1 | 0% | 2,917 | 3,321 | +14% | 0 | 0 | — |
case-20 | pass→pass | 12,642 | 5,027 | -60% | 1 | 1 | 0% | 2,302 | 3,187 | +38% | 0 | 0 | — |
case-21 | fail→pass | 9,571 | 2,502 | -74% | 1 | 1 | 0% | 1,650 | 2,622 | +59% | 0 | 0 | — |
case-22 | fail→pass | 18,253 | 9,259 | -49% | 1 | 1 | 0% | 3,261 | 4,046 | +24% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 20 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +55 percentage points is the difference between those two pass rates over the 20 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.