Give DeepSeek Eyes: A Zero-Dependency Skill for Screenshots in Claude Code, Codex, and Pi
Using DeepSeek in Claude Code / Codex / Pi — How to Make It See Images?
DeepSeek writes code beautifully, and connecting it to Claude Code, Codex, and Pi is becoming increasingly common. But there's one thing you'll repeatedly stumble over: the official API is text-only. You throw in a screenshot of an error, a UI draft, or a photo of interface documentation, and the model either says it can't see it outright or fabricates something based on the filename.
I later built a very small skill: glm-vision. The idea isn't complicated — leave the image recognition to Zhipu GLM, while reasoning and coding remain with DeepSeek. The repository is here: voidman2017/glm-vision
This post explains the problem and the approach clearly: why the official API can't see images, how the skill's architecture is broken down, how to install it, and where its boundaries lie.
The Problem Isn't the Agent, It's the Model Interface
Claude, GPT, and Gemini can natively consume image blocks. In the managed API documentation for DeepSeek V4 Flash / Pro, the input type is text. No matter how smart the coding agent is, if a request carries an image, the upstream will reject it, or the image will be discarded before it reaches the model.
This leads to a very disjointed experience:
- With Claude,
@screenshot.pnglets it critique the interface freely. - After switching to DeepSeek, the same image becomes "I cannot view images."
Another path in the community is a vision proxy: intercept requests locally, convert images to text, and then forward them to DeepSeek. That path can achieve "paste to view," but it requires changing base_url, keeping a port open persistently, and may stack on top of a Claude reverse proxy you're already running. I wanted something lighter first:
- Don't change DeepSeek's authentication or network path.
- Don't install extra Python dependencies.
- Works with Claude Code, Codex, Pi, and OpenCode.
- Zhipu Flash vision models are free; get everyday screenshots working first.
So that left only a skill + one command.
What It Actually Does
When the agent sees an image path, it shouldn't "look" itself, but instead run:
python3 scripts/see.py shot.png -q "What interface is this? Read out the error and key buttons."
see.py encodes the local image into data:image/...;base64,..., hits Zhipu's OpenAI-compatible interface /chat/completions, and hands the description from stdout back to DeepSeek. DeepSeek then decides how to modify the code and how to reply to you.
The whole chain can be drawn as:
flowchart TD
A["You @ an image"] --> B["SKILL.md constrains agent<br/>run see.py first, forbid pixel hallucination"]
B --> C[see.py]
C --> C1["Read ~/.config/glm-vision/env"]
C1 --> C2["Retry same model<br/>429 / 1305 / 5xx"]
C2 --> C3["Then switch to next model in queue"]
C3 --> D[GLM outputs text description]
D --> E["DeepSeek continues coding / explaining error"]
There are two roles, don't mix them:
flowchart LR
subgraph eye[Eye]
G[Zhipu GLM-4.6V-Flash etc.]
end
subgraph brain[Brain]
D[DeepSeek]
end
G -->|Text description| D
D -->|Modify code / explain error| U[You]
| Role | Who Plays It | What It Does |
|---|---|---|
| Eye | Zhipu GLM-4.6V-Flash etc. | Read images, OCR, describe what's on the interface |
| Brain | DeepSeek | Reason based on text description, modify code, provide solutions |
This is not native multimodality. What DeepSeek receives is "notes taken by someone else after looking," not visual tokens. The upside is the main model doesn't need to change; the cost is that details missed in the description cannot be recovered later. So -q must include the current task, not a vague "describe this image."
The Architecture Has Just Three Parts
The repository is small, deliberately kept as three separated parts.
flowchart TB
subgraph skill[For the agent]
S[SKILL.md]
end
subgraph cli[Actually sends requests]
P[scripts/see.py]
end
subgraph cfg[Not in git]
E["~/.config/glm-vision/env"]
end
S -->|Constraint: run script first, forbid hallucination| P
E -->|API Key / Model queue / Retries| P
P -->|image_url + description| G[Zhipu /chat/completions]
The sequence for a complete call:
sequenceDiagram
actor User as You
participant Agent as DeepSeek Agent
participant Skill as SKILL.md
participant See as see.py
participant GLM as Zhipu GLM
User->>Agent: @error.png What is this error?
Agent->>Skill: Match image recognition scenario
Skill-->>Agent: Must run see.py first
Agent->>See: see.py error.png -q user's original words
See->>See: Read env, convert to data URL
loop Same model retry
See->>GLM: chat/completions + image_url
alt 429 / 1305 / 5xx and retries remain
GLM-->>See: Rate limited
else Success
GLM-->>See: Text description
end
end
alt Still fails and queue not empty
See->>GLM: Switch to next model and retry
GLM-->>See: Text description
end
See-->>Agent: stdout description
Agent-->>User: Explain and modify code based on description
1. SKILL.md: The manual for the agent
It doesn't handle HTTP. It only specifies:
- When the script must be called (
@image, path is png/jpg, user says recognize image/view screenshot) - How to write the command, how
-qshould carry the user's original words - The script already handles retries and fallback, forbid wrapping another layer of
sleep && retry - Treat stdout as fact;
info:/warn:in stderr only indicates which model was used - If there's no file path, ask the user to save first; don't pretend to see the clipboard
Human-readable installation instructions go in README.md, not inside the skill package, to avoid stuffing setup steps into the context every time an image is recognized.
2. scripts/see.py: The CLI that does the real work
Only uses Python standard library: urllib, base64, argparse. No need for pip install openai.
It does several specific things:
- Converts local PNG / JPEG / GIF / WebP into a data URL (max 5MB)
- Sends it in OpenAI
image_urlformat toVISION_BASE_URL/chat/completions - Uses
VISION_LANGto add a "Please answer in Chinese/English" instruction to the vision model, separate from the system environment variableLANG, to prevent macOS'sen_US.UTF-8from overriding the config - Retries the same model first, then switches to the next
Default queue:
flowchart LR
A[glm-4.6v-flash] --> B[glm-4.1v-thinking-flash] --> C[glm-4v-flash]
glm-4.6v-flash is officially free and supports local base64, so it's placed first in the queue. The older glm-4v-flash has a history of "not supporting base64" and can only consume public URLs, so it's placed last; if the error encountered is a format error rather than rate limiting, the script will not continue to fall back, to avoid hitting all models with the same bad request.
The retry strategy is also straightforward:
flowchart TD
S["Send request to current model"] --> R{Success?}
R -->|Yes| OK[Output description]
R -->|No| T{Retryable?<br/>429 / 1305 / 5xx}
T -->|Yes and retries remain| W["Wait 2s / 4s / 8s"] --> S
T -->|Yes but retries exhausted| N{Next model available?}
T -->|No format/auth etc.| X[Stop fallback and report error]
N -->|Yes| M[Switch to next model] --> S
N -->|No| X
Each model gets at most 1 + VISION_RETRIES attempts; waits double by VISION_RETRY_DELAY. Retryable: HTTP 429 / 500 / 502 / 503 / 504, and Zhipu 1302, 1305.
Free Flash models easily hit 429 during peak hours. If you switch models after a single failure, the primary model is barely used; if you stubbornly stick to one model, the whole conversation round gets stuck. So it's "politely ask twice more first, then switch."
3. ~/.config/glm-vision/env: Keep the key separate
The key does not go into git. The agent and the script read the same config:
VISION_API_KEY=your_zhipu_key
VISION_BASE_URL=https://open.bigmodel.cn/api/paas/v4
VISION_MODELS=glm-4.6v-flash,glm-4.1v-thinking-flash,glm-4v-flash
VISION_RETRIES=2
VISION_RETRY_DELAY=2
VISION_LANG=zh
Lookup order:
flowchart TD
A{"$VISION_ENV_FILE set and file exists?"} -->|Yes| U[Use that file]
A -->|No| B{"~/.config/glm-vision/env exists?"}
B -->|Yes| U2[Use this env]
B -->|No| C{"~/.config/agent-vision-toolkit/env exists?"}
C -->|Yes| U3[Compatible with toolkit env]
C -->|No| E[Missing VISION_API_KEY]
Environment variables in the process take priority over the file.
How to Use
Best recommendation: In the AI era, naturally use magic to defeat magic. Directly provide the repository address to the AI and let it help execute the installation. Of course, manual installation is also possible.
Installation
Clone first, then link to the agent you're using (no need to install for all four):
git clone https://github.com/voidman2017/glm-vision.git
cd glm-vision
ln -sfn "$(pwd)" ~/.claude/skills/glm-vision
ln -sfn "$(pwd)" ~/.codex/skills/glm-vision
ln -sfn "$(pwd)" ~/.pi/agent/skills/glm-vision
Corresponding directories:
| Agent | Skills Directory |
|---|---|
| Claude Code | ~/.claude/skills/glm-vision |
| Codex | ~/.codex/skills/glm-vision |
| Pi | ~/.pi/agent/skills/glm-vision |
| OpenCode | ~/.config/opencode/skills/glm-vision |
Windows can use directory junctions. If you don't want symlinks, use cp -R. Restart the agent after installation; many CLIs only scan skills at startup.
Then configure the Key:
mkdir -p ~/.config/glm-vision
cp assets/env.example ~/.config/glm-vision/env
chmod 600 ~/.config/glm-vision/env
# Fill in VISION_API_KEY; for domestic use, VISION_LANG=zh is recommended
After real-name verification on the Zhipu open platform, you can call the Flash vision model without needing to top up first.
Verify in the Terminal First
python3 scripts/see.py --help
python3 scripts/see.py ~/Desktop/error.png -q "Read the full error text and the bottom buttons"
On success, stderr will show something like info: using model glm-4.6v-flash; stdout is the description for DeepSeek to read.
Common parameters:
# OCR, transcribe text in reading order
python3 scripts/see.py dialog.png --ocr
# Compare two images
python3 scripts/see.py before.png after.png -q "What are the differences in layout and copy?"
# Specify a queue this time, or increase retries
python3 scripts/see.py shot.png --model glm-4.1v-thinking-flash
python3 scripts/see.py shot.png --retries 3 --retry-delay 2
How to Use Inside the Agent
Switch to DeepSeek, then:
@error.png What does this error mean? Where is the relevant code?
When the skill takes effect, the model will first run see.py, then answer based on the description. If you just paste an image into the input box without any path, the skill cannot help — the pixels never landed on disk. Save first, then @.
How to Choose vs. a "Vision Proxy"
| glm-vision | Local Vision Proxy (e.g., agent-vision-toolkit) | |
|---|---|---|
| Paste to view | No, requires a path | Can do |
| Need to change DeepSeek's base_url | No | Usually yes, may also layer a local port |
| Installation footprint | skill + one env file | Proxy process, auto-start on boot, coordinate with existing reverse proxies |
| Suitable for | Daily @ screenshots, OCR, UI comparison |
Seamless pasting across three ends, built-in view_image |
For my daily use, the skill is enough. People who already have a Codex / Claude reverse proxy and rely heavily on pasting are better suited to the proxy. Both can share the same VISION_* configuration.
A Few Pitfalls Encountered
Free models get rate limited. Zhipu 1305 "current access volume too high" is very common. So the script defaults to trying the same model 3 times, then switching to glm-4.1v-thinking-flash. When all three Flash models are congested, add the paid glm-4.6v to the end of VISION_MODELS as a fallback.
Old Flash models may not accept base64. Images in coding agents are almost always local files, without public URLs. The model at the front of the queue must support data URLs.
This is lossy compression. A line of small text that GLM misses, DeepSeek cannot "take another look" at, unless you have the script run again with a more specific -q. Passing in the user's original words is far more useful than "please describe in detail."
A skill is not magic. It changes the agent's behavioral constraints, not the model's input modality. No path, no image.
Summary
DeepSeek does not currently provide an official image recognition API. Rather than waiting for an interface, it's better to separate "seeing" and "thinking": GLM looks at images, DeepSeek writes code. glm-vision packages this into an installable skill, a single command with no third-party dependencies, plus retries and fallback, so that DeepSeek inside Claude Code / Codex / Pi can at least take a screenshot seriously.
Repository: https://github.com/voidman2017/glm-vision
MIT, welcome stars, issues, and PRs.
Top 2 from juejin.cn, machine-translated. The original thread is authoritative.
I've learned a lot, keep it up [fighting]
Not bad, thumbs up for you 👍