davidekingsss/dsh-screen-eye ↗★ 0

dsh-screen-eye

Autonomous screen vision for DeepSeek Harness on macOS and Windows: the agent captures the screen and receives the image itself in one call. 适合需要AI具备屏幕视觉能力、自动捕获并分析macOS/Windows屏幕的任务。

Package
dsh-screen-eye
Compatibility
Unverified
Harness peer range
*
Version
0.1.0
License
MIT
Last updated
Sep 16, 2026

Install

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:davidekingsss/dsh-screen-eye

Configuration

All keys are optional — and all of them are editable from the harness's own settings page, Settings → Screen Eye, or by hand in ~/.dsh/settings.yaml under screen-eye:. Either way an edit reaches the next call without a restart, and the plugin's mount entry stays the base that a cleared field falls back to. docs/settings.md has the three layers, the page, and the two platform constraints behind it.

KeyDefaultMeaning
outputDir`
/Screen Eye`Where captured PNGs are written — the system pictures folder, in a folder of its own so fifty screenshots do not land among the user's photographs.
localeenLanguage of the onboarding text: en or zh.
timeoutMs300000Budget for one whole call, including any wait_for_change. The capture gets what the wait did not spend.
frames / interval_ms1 / 200Frames per call and the target gap between them. At most 600, which is the provider's per-request image limit rather than a policy here — past it the extra images would be taken and then replaced with a placeholder. Each frame is one image and at most 384 vision tokens (about 380 measured), so the count is a cost decision and it is yours.
duration_ms—State how long the motion lasts and let the tool size the burst, instead of computing frames and interval yourself.

frames, interval_ms and duration_ms describe one burst and any two of them determine the third: frames with an interval gives the span, frames with a duration gives the interval that divides it, an interval with a duration gives how many frames fit. All three at once is refused rather than resolved.

That is also the cost lever. Each frame is an image and images are what cost, so holding the window fixed and raising interval_ms trades resolution for cost — the same motion watched with fewer images — while lowering it spends more for a finer sample. A window given alone is sampled at the ordinary frame count, not at the maximum, because the maximum is the most expensive answer and was not asked for — unless the call is waiting for a change first, where the ending is the screen's to decide and the count is headroom.

wait_for_change: true also settles when the burst ends: it stops once the picture stops changing, so frames is an upper bound and the reply says which ending happened. wait_timeout_ms (default 30000) is how long it waits for the motion to start. until_still: false opts out, recording the full window instead — the right call for a span rather than an event. | maxDimension | 4096 | Largest side, in pixels, a capture may have. 4096 is where the provider's per-image side limit lands once a request carries fifteen or more images, and a burst can carry hundreds. A capture over the cap is refused with its size named rather than resized — so a display larger than this needs the field raised to be captured whole. | | keepRecent | 50 | How many of the newest captures to keep in outputDir. A capture is a few-megabyte PNG and an agent using its eyes takes many, so the directory is bounded by default. 0 keeps everything. | | requireImageCapableModel | true | Refuse a capture when the calling model declares no image input, instead of returning a picture it cannot see. | | deleteAfterCommit | false | Delete the PNG once it is committed to the attachment store. Off by default, so the returned path stays re-readable. |

Captures are committed without the records that would disqualify them from lossless storage. On macOS that means stripping the ICC profile, EXIF and iTXt records screencapture attaches to every shot, while the file on disk keeps them; those records decide whether the attachment store keeps the bytes or re-encodes them, so removing them is what lets a capture be stored as written instead of as WebP at quality 85. On Windows there is nothing to strip: GDI+ writes none of them, so a capture already satisfies the store's condition. The store converts to sRGB either way, so nothing that survives is lost, and the file you can open keeps its colour profile.

Retention only ever removes files this plugin wrote: regular files, direct children of outputDir, whose names match the exact shape it generates (shot--.png). It is never recursive, it never touches another naming scheme, and it never removes the capture it just returned.