An official plugin that
- Language
- TypeScript
- License
- MIT
- Branch
- main
Install
$ dsh plugin --profile web add github:liustack/modlensRun the command above in your terminal to install this plugin via the dsh CLI. You can switch Profile in the top-right corner. New to dsh? Read the beginner tutorial
One-Line Positioning
modlens is the official visual plugin for DeepSeek Harness, giving native non-image-supporting DeepSeek/GLM text models a pair of eyes: after installation, you can directly paste screenshots in dsh, drop an image path, or add a dragged image, and the model can "see" the content and answer based on it.
Core Capabilities
- Register
modlens_read_imagetool for text models, pass a local path or http(s) URL to convert to structured JSON evidence (full transcription, layout blocks, entity relationships, uncertain items) - Automatically wrap all providers routing DeepSeek/GLM pure text models, each route generating a set of model variants with "(modlens vision)" suffix; both vendors' built-in vision models are automatically excluded
- Browser-side intercepts pasted screenshots, first POST to local route, then insert the image as a path into the input box (same "path trigger" interaction as OpenCode, Pi)
- Built-in six vision engines (Gemini API, Anthropic API, any OpenAI compatible endpoint, Antigravity CLI, Claude Code CLI, Kimi Code CLI), when unspecified composed into a failover chain
recover-pastesubcommand retrieves pasted image bytes from Claude Code / Pi / OpenCode local session storage, dropping them into a 0700 private directorydoctorsubcommand performs pure local health checks: Node version, engine readiness, current selection, host, guard determination, no quota consumption, no network requests
Technical Implementation
- Language: TypeScript (Node.js ESM), a small amount of hand-written CJS for browser side (
dsh/client.js) - Key dependencies:
commander(CLI routing),undici(remote image download + DNS pinning),@biomejs/biome(lint, devDep only); host side only uses Node built-in modules - Architecture pattern: Dual-form package — the same npm package is both CLI (
bin: modlens→dist/main.js) and dsh cordis plugin (dsh.bundle.patch→cordis.patch.ymlinjects@liustack/modlensinto dsh cordis registry, exported fromdsh/index.jsasapply(ctx, config)). DSH browser side has another zero-dependency lazy-CJS client (dsh/client.js) intercepting pastes - Entry points: CLI entry
src/main.ts; DSH plugin entrydsh/index.js; browser clientdsh/client.js
Use Cases
Daily use of dsh Web/Desktop App to chat with DeepSeek or GLM text models, frequently needing to paste screenshots or drop image paths to ask questions — after installation no commands to memorize, just paste normally in chat. Also suitable for developers wanting to squeeze a single text model but not wanting to switch models, and not wanting to write their own OCR/vision scripts.
Prerequisites & Compatibility
| Dependency | Minimum Version | Description |
|---|---|---|
| Node.js | >=22.19 | package.json engines.node declaration; OpenCode paste recovery further depends on node:sqlite (unflagged from Node 22.13+) |
| dsh | No specific version declared | Injected via dsh.bundle.patch (cordis.patch.yml); install via dsh plugin --profile web add github:liustack/modlens; package.json declares no peerDependencies |
| Platform | macOS / Windows / Linux | src/util/winExec.ts handles Windows .cmd shim; src/auto/discover.ts cross-platform probes PATH for harness CLI |
| Native modules | None | No node-gyp dependencies; remote image download uses undici's built-in Agent; OpenCode recovery uses Node built-in node:sqlite |
| External accounts | At least one vision source | Defaults to Antigravity CLI (key-free); can also configure Gemini / Anthropic API key or any OpenAI compatible endpoint; can borrow this machine's other harness CLI login |
Installation
dsh plugin --profile web add github:liustack/modlens
Configuration
Config file at ~/.modlens/config.json (0600 permission, keys automatically masked when rendered via modlens config show). Commonly writable keys:
| Config | Type | Description | Default |
|---|---|---|---|
provider | string | Vision engine preference name (when unspecified, all ready engines form failover chain) | antigravity-cli (Antigravity CLI) |
providers.<name>.apiKey | string | API key for that engine; <name> ∈ gemini-api / openai / anthropic. modlens config set <name>.apiKey without value triggers hidden input | none |
providers.<name>.baseUrl | string | Custom API endpoint; openai must be explicitly configured (no default to avoid misrouting to OpenAI official) | engine's own default |
providers.<name>.model | string | Model name used by default for that engine | engine's own default |
providers.<name>.proxy | string | Dedicated proxy URL for that engine, falls back to top-level proxy, then to HTTPS_PROXY / HTTP_PROXY | none |
providers.<name>.extraBody | object | JSON merged into API request body (commonly used to disable thinking, e.g. {"thinking":{"type":"disabled"}}); reserved fields: contents / messages / model / schema etc. cannot be overridden | none |
providers.<name>.structuredOutput | boolean | Let OpenAI compatible endpoints self-enforce output with response_format: json_schema; default false (some gateways will 400) | false |
proxy | string | Fallback proxy URL for all API providers; remote image download (SSRF-guarded) unaffected | none |
guards.denyModels | string array | glob pattern list; if current model matches, refuse to start vision engine (make way for native vision models) | [] |
guards.allowModels | string array | Non-empty enters "whitelist mode": only matched models will invoke engine, others rejected | [] |
guards.denyWhenUnknown | boolean | Whether to deny reading when current model cannot be identified (default allow, to avoid mistakenly blocking text models) | false |
reuse.claude / codex / opencode / pi / grok | boolean | Whether to allow borrowing that harness' login on this machine for reading; claude defaults to true (backward compatible) | claude=true / others unasked |
MODLENS_MODEL / MODLENS_HARNESS / MODLENS_DSH_CLI | env var | Directly tell modlens current model / host / CLI path, overriding detection | none |
FAQ
Q: Who is modlens for? Why do regular DeepSeek/GLM models need it?
A: The main chat models like DeepSeek-V4 and GLM under the default dsh host are pure text and cannot directly read pasted images. modlens converts these images into structured text evidence through an external vision engine, then feeds them back to the text model, making "screenshot + question" a complete conversation.
Q: Is it usable after installation by default, or do I need to configure an API key myself?
A: Defaults to Antigravity CLI (key-free, browser login once), zero config needed. For faster results, configure a free Gemini key typically completes in 5-10 seconds; other OpenAI compatible endpoints (DashScope, SiliconFlow, OpenRouter, self-hosted vLLM/Ollama, etc.) can also connect directly.
Q: What changes will I see after installation? Which model do I need to select?
A: The model selector will show additional entries with "(modlens vision)" suffix, like DeepSeek-V4-Flash (modlens vision), DeepSeek-V4-Pro (modlens vision). Switch to these entries and paste images, thumbnails directly visible, experience close to Codex App. Not switching also works, image goes as path into input box.
Q: Which engine processes an image? Which account's quota gets deducted?
A: When unspecified, all configured engines form a failover chain: API-class fast lanes attempt first, agent CLI as fallback; each result's meta.attempts lists attempt sequence, meta.warnings indicates which harness login was reused and whose quota was deducted, never silent quota deduction.
Q: What does modlens recover-paste do?
A: Claude Code, Pi, OpenCode these terminals by default store pasted images directly in session storage without falling to ordinary temp files. recover-paste reads those pasted bytes from these harness' local storage and saves them to a secure directory (0700). OpenCode recovery requires Node 22.13+ providing node:sqlite.
Q: How do I troubleshoot when errors occur?
A: Run modlens doctor, it reports Node version, which providers are ready, which will be selected, which harness was detected, all local checks, no quota consumption, no network requests. Error messages usually include fix commands, e.g., when key is missing it tells you directly modlens config set <provider>.apiKey.
Q: What remains after uninstallation?
A: Delete this plugin package to uninstall. What stays on machine is only ~/.modlens/config.json (your explicitly configured keys, endpoints, reuse authorizations) and the recovery directory under ~/.modlens/; not placed into dsh hooks, also doesn't touch any harness configuration, host itself can seamlessly revert to original state.
Getting Started Difficulty
Entry level — one dsh installation command plus a default engine (Antigravity CLI key-free browser login) can get it running; wanting "advanced configuration" (proxy, custom OpenAI endpoint, borrowing other harness credentials) requires reading the configuration manual.
Known Issues & Limitations
- Kimi Code CLI lacks server-side enforcement like
--json-schema, can only rely on prompt templates + fault-tolerant parsing; and because kimi scans the shared skill directory, there's possibility of being recursively triggered by modlens itself (mitigated viaMODLENS_INSIDE_KIMI_CLIand--skills-dirpointing to empty directory, but whether model takes that path is model's own decision, occasionally hard to reproduce) openaiengine doesn't enableresponse_format: json_schemaby default because some compatible gateways don't recognize this field and 400; when server-side enforcement needed explicitlymodlens config set openai.structuredOutput true- Remote image download has 25 MB limit (
src/imageInput.tsMAX_REMOTE_IMAGE_BYTES), covers a dense screenshot or high-res photo, but large volume originals will be rejected - Paste file supports png/jpeg/gif/webp/heic/heif (others like bmp / svg / raw not accepted); validated through file magic bytes, doesn't trust extension names
- Kimi / Codex / OpenCode / Claude CLI routes can only read local path files; remote URLs must use API-class engines (gemini-api / openai / anthropic)
- Antigravity CLI's free quota is a weekly shared bucket (desktop App / CLI / SDK combined), concurrent sub-agents will deplete quickly (issue docs explicitly state, need to wait for reset or switch to
gemini-api)
ModLens
Give a text-only model sight, and just paste the image.
🥇 The most capable vision plugin for DeepSeek Harness (dsh) 🥇
简体中文 · Troubleshooting · Configuration · Output contract · Security · 🔎 ModSearch (web search)
The flagship DeepSeek and GLM chat models are text-only and cannot read images. ModLens is a plug-in vision engine that gives a text-only model sight. ModLens reads images pasted straight into the chat, no saving to a file and passing a path first.
Talk to us
Issues are welcome any time: open one. And come find me on X: @liustack. What you built with it, which harness you are on, what should come next. New releases land there first, and a proper community space is on the way.
Highlights
🥇 The most capable vision plugin for DeepSeek Harness (dsh): one command, npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.18.3, and the text-only DeepSeek model behind dsh reads images through a native modlens_read_image tool. Updating is the same command again. The version is named rather than @latest on purpose: pnpm 11 holds back releases published in the last 24 hours and resolves the tag against what survives, so @latest would install whatever shipped a day ago (details).
Pasting an image works two ways. ① Just paste. On a text-only model the pasted image lands as a private temp file and its path enters the composer — the same interaction OpenCode and Pi ship — and the modlens_read_image tool takes it from there. ② Pick a (modlens vision) entry in the model selector (it remembers your choice, so once is enough), then paste: the thumbnail stays visible in your message, closer to the Codex app feel, and the image is converted to structured evidence at request time, answered by the same underlying route. The plugin auto-discovers every provider route carrying text-only DeepSeek or GLM models and adds a wrapped entry per route (a stock install gets DeepSeek-V4-Flash (modlens vision) and DeepSeek-V4-Pro (modlens vision); extra routes like opencode-go or zai get their own); the two families' own vision models are excluded automatically. Which paste route applies is the host's per-model call: only a model its metadata positively confirms text-only is taken over, anything unconfirmed is left alone, so vision models keep their native paste (details).
Paste an image and it reads it. No saving to a file and passing a path first.
- The lightest touch on the market. No hooks, no wrappers, no local proxy daemon, not a single line changed in any harness config: on the skill harnesses it is exactly one skill folder, on dsh exactly one plugin. Uninstalling is deleting a folder, and your agents are back to stock.
- Zero-config start. Reuses what Claude Code, Codex, OpenCode, or Pi already have set up: the multimodal models on your machine go straight to work. Nothing at all? Antigravity CLI is a free no-key channel, and a free Gemini key brings a read down to 5-10 seconds.
- Evidence, not imagination. Full transcription, reading-order layout regions, entity and relation lists. The model quotes specifics.
- Install once, use everywhere. Verified on real machines in Claude Code, Codex, Pi, and OpenCode.
Installation
Step 1, hand it to your AI. Send it this line:
Install and configure the modlens skill following https://github.com/liustack/modlens/blob/main/INSTALL.md, then run the health check and tell me the result.
The install starts by checking what your machine already has. An existing login in Claude Code, Codex, OpenCode, or Pi can be enough: modlens asks before reusing any of them, and the health check tells you where things stand.
Step 2, only if the health check comes back empty, set up a free engine. The recommended choice is a free Gemini API key (about three minutes at Google AI Studio, no credit card), which also makes every read 5-10 seconds. A free OpenAI-compatible key from another platform works too. To avoid any sign-up, install Antigravity CLI instead, then sign in:
curl -fsSL https://antigravity.google/cli/install.sh | bash
agy # sign in, then exit
The install also inventories vision reachable through your other local harness CLIs (Codex, OpenCode, Pi) and asks, per harness, whether modlens may reuse it. Granted logins join the engine pool as equals, and every reused read is labeled with whose quota it spent.
Usage
Once installed, just chat. Paste an image or drop a path, ask anything, and the skill triggers on its own: the image goes to a vision engine and the answer comes back grounded in what it read.
Vision engines: six built-in providers, four reusable CLIs, one failover chain
ModLens does not depend on any single vision service. Ten sources of vision in total: six built-in providers, any one of which is enough, plus four local agent CLIs whose logins can be reused. The built-ins:
| Provider | What it needs | Speed per read | Good for |
|---|---|---|---|
gemini-api | a free Gemini API key (3 minutes, no card) | 5-10s | the recommended default |
openai | any OpenAI-compatible endpoint (key + baseUrl + model) | 5-10s | qwen-vl, GLM, self-hosted gateways |
anthropic | an Anthropic API key | 5-10s | machines already holding one |
antigravity-cli | the free agy CLI, one browser sign-in, no key | 15-45s | zero-signup starts |
claude-cli | a signed-in Claude Code | 20-45s | riding your existing Claude subscription |
kimi-cli | a signed-in Kimi Code | 20-45s | riding your existing Kimi subscription, named explicitly |
Without a pinned provider, every configured engine forms one failover chain: the fast API providers try first, the agent CLIs back them up, the first good result wins, and meta.attempts records every attempt so a fallback is never silent.
openai is a universal socket, not just OpenAI
Any endpoint speaking the OpenAI chat-completions protocol with image input plugs straight in — that covers most of the vision-model world:
modlens config set openai.baseUrl https://dashscope.aliyuncs.com/compatible-mode/v1 # qwen-vl
modlens config set openai.apiKey <key>
modlens config set openai.model qwen3-vl-plus
The same three keys work for GLM's open platform, SiliconFlow, OpenRouter, a self-hosted vLLM/Ollama, or any gateway of your own. If your favorite vision model has an OpenAI-compatible API, ModLens can drive it.
Reusing what your machine already has
Two more sources of vision need zero new keys, each behind one explicit consent recorded in config:
- The harness you are talking in right now. Running inside Claude Code with a subscription signed in?
claude-clireads images through it out of the box. The install flow asks the same question for whichever harness you install into. - Every other agent CLI on the machine.
modlens doctordiscovers them, you grant per harness, and they join the same failover chain with no priority over your own keys. Every reused read is labeled inmeta.warningswith whose quota it spent, so nothing is ever silently billed:
| Reused CLI | What it needs | Grant with | Rides as |
|---|---|---|---|
| Codex | a signed-in Codex CLI with a vision model | config set reuse.codex true | agent lane, 15-45s |
| OpenCode | a vision model configured in OpenCode | config set reuse.opencode true | agent lane, 15-45s |
| Pi | model credentials held by Pi | config set reuse.pi true | an API key upgrades to the 5-10s inline lane, OAuth drives Pi itself |
| Grok | a signed-in Grok CLI (SuperGrok) | config set reuse.grok true | agent lane, 15-45s |
Picking and routing
Two knobs: modlens config set provider <name> states a preference (the chain still backs it up), -p <name> pins exactly one with no fallback. Machines behind a proxy set HTTPS_PROXY or modlens config set proxy <url> and the API providers route through it. Details: the CLI manual for defaults and flags, Configuration for every key, and Security for who fetches what on remote URLs.
See it work
Unedited runs, all driving a text-only DeepSeek-V4-Flash.
The newest one first: pasting a screenshot straight into DeepSeek Harness on the DeepSeek-V4-Flash (modlens vision) variant. The paste keeps its native thumbnail, the trajectory shows the image arriving "already transcribed by the modlens vision bridge", and the answer walks the UI element by element.

A tweet screenshot in the Codex desktop app. It reads the author, the caption, the photo itself (down to what both people are wearing), the timestamp, and every engagement number: 5.4M views, 1.6K replies, 5.7K reposts, 116K likes.

Three images pasted at once. The model reads them one by one, spots that they belong to one visual family, and describes each illustration's content and style.

The stress test: a scatter plot comparing 128 AI models. It reads both axes, the log scale, the per-provider color coding, the highlighted region, and every DeepSeek model called out with dashed markers. Dense charts are where vision bridges most often fail.

And the paste path, end to end, in a Claude Code terminal on DeepSeek. The pasted image arrives as a path rather than pixels, the skill triggers on its own, the guard confirms the model truly has no vision, and the slide's full content comes back: titles, layout, background, plus an honestly stated uncertainty about the truncated filename.

Documentation
| Doc | Read it when |
|---|---|
| Install guide | Installing the skill step by step (written for an agent) |
| CLI manual | The CLI the skill drives: flags, config, doctor |
| Troubleshooting | A command failed and the message needs decoding |
| Configuration | Setting a key, switching providers, fixing config |
| Output contract | Parsing the JSON or building on it |
| Harness setup | Wiring it into Codex, Claude Code, Pi, or OpenCode |
| Security | File permissions, image content as untrusted input |
| CHANGELOG | Finding what changed in a version |
Contributing
ModLens does not accept pull requests. The project is maintained by a single author who reviews every line, which is a deliberate choice for reliability. Two effective ways to contribute:
- Open an issue. Bugs, suggestions, confusing errors, unclear docs. Issues are read and shape what gets built next.
- Fork it. Under MIT your copy is fully yours to modify and publish.
Shameless plug
This project runs on LIUSTACK Skills: shaping before you build, coding while you build, dig when it breaks, snapshot when you hand off. Lighter than Superpowers, and stronger.
npx -y skills add liustack/vibemaster -g
⭐ If it helps, star ModLens and VibeMaster. Stars are how the next developer finds them.
Key ecosystem partners
The projects worth recommending in the DeepSeek Harness ecosystem.
- 🖥️ DeepSeek Harness Desktop — A desktop front end for DeepSeek Harness. Start and manage the Harness service on your own machine without installing Node.js or running a command. A plugin market, remote control from a phone, and IM channels are on its roadmap. Site 为 DeepSeek Harness 生态打造的现代化桌面端。不用配置 Node.js,也不用敲命令,就能启动和管理本机的 Harness 服务。后续还会支持插件市场、移动端远程控制和 IM Channels。官网
- 🛒 dsh-market — The plugin market inside DeepSeek Harness. Browse 800+ community plugins with category filters and screenshot previews, one-click install and update, and live theme switching. Most need no restart. DeepSeek Harness 的可视化插件市场。设置页里直接逛社区全部 800+ 插件:分类筛选、截图预览、一键安装与更新、主题即点即换,装完多数免重启。
Star History
Disclaimer
Provided as-is under the MIT License below. The author makes no warranty and gives no endorsement for any particular use, commercial use included. Your use of upstream engines (Antigravity CLI, the Gemini, OpenAI, and Anthropic APIs, and any OpenAI-compatible endpoint) is governed by their own terms and quotas, which you are responsible for.
License
MIT