@liustack/modlens 2.3.2 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/main.js +23 -3
- package/package.json +1 -1
- package/skills/modlens/SKILL.md +13 -8
package/README.md
CHANGED
|
@@ -149,7 +149,7 @@ One catch: once text-only is declared, the Codex TUI **blocks Ctrl+V image paste
|
|
|
149
149
|
|
|
150
150
|
No setup needed: drag the image file into the terminal, or type its path, and the skill takes over.
|
|
151
151
|
|
|
152
|
-
Paste is trickier. If you run a text-only model behind `ANTHROPIC_BASE_URL`, Claude Code never writes pasted images to a regular temp file and has no modality switch, so a pasted image reaches the model as a pathless `[Unsupported Image]` placeholder (lenient gateways like DeepSeek's Anthropic endpoint) or breaks the request outright ([#62009](https://github.com/anthropics/claude-code/issues/62009)). But the bytes are not gone: Claude Code appends every user message, images included, to the local session transcript before the gateway ever sees it. That is what `modlens recover-paste` exploits: it pulls the most recent pasted images back out of the transcript and prints real file paths, ready for `modlens -i`. The skill runs this automatically when it spots the placeholder. Transcripts are per-session files; recovery picks the
|
|
152
|
+
Paste is trickier. If you run a text-only model behind `ANTHROPIC_BASE_URL`, Claude Code never writes pasted images to a regular temp file and has no modality switch, so a pasted image reaches the model as a pathless `[Unsupported Image]` placeholder (lenient gateways like DeepSeek's Anthropic endpoint) or breaks the request outright ([#62009](https://github.com/anthropics/claude-code/issues/62009)). But the bytes are not gone: Claude Code appends every user message, images included, to the local session transcript before the gateway ever sees it. That is what `modlens recover-paste` exploits: it pulls the most recent pasted images back out of the transcript and prints real file paths, ready for `modlens -i`. The skill runs this automatically when it spots the placeholder. Transcripts are per-session files. Skills can pass the exact session via `--session` (Claude Code substitutes `${CLAUDE_SESSION_ID}` into skill text since v2.1.9); without it, recovery picks the transcript holding the newest pasted image by message timestamp, so concurrent sessions in the same project do not confuse it either way. One honest caveat: the transcript layout is Claude Code internals with no compatibility promise; if recovery ever breaks, dragging the file still works everywhere.
|
|
153
153
|
|
|
154
154
|
## Why a bridge instead of a multimodal model?
|
|
155
155
|
|
package/README.zh-CN.md
CHANGED
|
@@ -149,7 +149,7 @@ Codex 只认 Responses API,DeepSeek 官方端点原生支持。先照着[官
|
|
|
149
149
|
|
|
150
150
|
不用任何配置:把图片文件拖进终端,或手打路径,skill 直接接手。
|
|
151
151
|
|
|
152
|
-
粘贴要多说两句。走 `ANTHROPIC_BASE_URL` 网关跑纯文本模型时,Claude Code 粘贴的图片从不写普通临时文件,也没有声明模型无视觉的开关,粘贴的图要么变成一个不带路径的 `[Unsupported Image]` 占位符到达模型(DeepSeek 的 Anthropic 兼容端点这类宽容网关),要么直接把请求搞挂([#62009](https://github.com/anthropics/claude-code/issues/62009))。但图片字节没有蒸发:Claude Code 在网关看到消息之前,就把每条用户消息(含图片)原样写进了本地会话记录。`modlens recover-paste` 干的就是这件事:从会话记录里把最近粘贴的图捞回来,落成真实文件路径,直接喂给 `modlens -i`。skill
|
|
152
|
+
粘贴要多说两句。走 `ANTHROPIC_BASE_URL` 网关跑纯文本模型时,Claude Code 粘贴的图片从不写普通临时文件,也没有声明模型无视觉的开关,粘贴的图要么变成一个不带路径的 `[Unsupported Image]` 占位符到达模型(DeepSeek 的 Anthropic 兼容端点这类宽容网关),要么直接把请求搞挂([#62009](https://github.com/anthropics/claude-code/issues/62009))。但图片字节没有蒸发:Claude Code 在网关看到消息之前,就把每条用户消息(含图片)原样写进了本地会话记录。`modlens recover-paste` 干的就是这件事:从会话记录里把最近粘贴的图捞回来,落成真实文件路径,直接喂给 `modlens -i`。skill 看到占位符会自动跑这一步。会话记录本来就是一个会话一个文件。skill 可以通过 `--session` 传入精确会话(Claude Code 从 v2.1.9 起会把 `${CLAUDE_SESSION_ID}` 替换进 skill 文本),不传时按消息时间戳挑「持有最新粘贴图」的那份,两条路都不怕同项目并发多开。一句老实话:会话记录格式是 Claude Code 的内部实现,没有兼容承诺,哪天捞不动了,拖文件永远是保底。
|
|
153
153
|
|
|
154
154
|
## 为什么外挂,而不是换多模态模型?
|
|
155
155
|
|
package/dist/main.js
CHANGED
|
@@ -943,8 +943,24 @@ function extractUserImages(transcriptPath) {
|
|
|
943
943
|
}
|
|
944
944
|
return images;
|
|
945
945
|
}
|
|
946
|
+
function transcriptForSession(cwd, sessionId) {
|
|
947
|
+
const file = path.join(
|
|
948
|
+
os.homedir(),
|
|
949
|
+
".claude",
|
|
950
|
+
"projects",
|
|
951
|
+
projectSlug(cwd),
|
|
952
|
+
`${sessionId}.jsonl`
|
|
953
|
+
);
|
|
954
|
+
if (!fs.existsSync(file)) {
|
|
955
|
+
throw new Error(
|
|
956
|
+
`No transcript for session ${sessionId} under this project (${file}). Check --cwd, or drop --session to auto-locate by newest pasted image.`
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
return file;
|
|
960
|
+
}
|
|
946
961
|
function recoverPastedImages(options = {}) {
|
|
947
|
-
const
|
|
962
|
+
const cwd = options.cwd ?? process.cwd();
|
|
963
|
+
const transcript = options.transcript ?? (options.session ? transcriptForSession(cwd, options.session) : locateTranscript(cwd));
|
|
948
964
|
const count = Math.max(1, options.count ?? 1);
|
|
949
965
|
const outDir = options.outDir ?? path.join(os.tmpdir(), "modlens-paste");
|
|
950
966
|
const all = extractUserImages(transcript);
|
|
@@ -966,7 +982,7 @@ function recoverPastedImages(options = {}) {
|
|
|
966
982
|
return { transcript, images };
|
|
967
983
|
}
|
|
968
984
|
const program = new Command();
|
|
969
|
-
program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("2.
|
|
985
|
+
program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("2.4.1");
|
|
970
986
|
program.command("analyze", { isDefault: true }).description("Analyze an image into structured JSON evidence (default command)").requiredOption("-i, --input <path|url>", "Input image path or https URL").option("-o, --output <path>", "Write result JSON to a file").option("-m, --model <name>", "Provider model name").option("-p, --provider <name>", `Vision provider (${listProviders().join(", ")})`).option("--prompt <text>", "Extra focus for this image").option("--timeout <ms>", "Provider timeout in milliseconds", "180000").option("--provider-bin <path>", "Provider binary path (default: agy)").option("--workdir <path>", "Working directory for the provider").action(async (options) => {
|
|
971
987
|
try {
|
|
972
988
|
const timeoutMs = Number.parseInt(options.timeout, 10);
|
|
@@ -1000,7 +1016,10 @@ program.command("analyze", { isDefault: true }).description("Analyze an image in
|
|
|
1000
1016
|
});
|
|
1001
1017
|
program.command("recover-paste").description(
|
|
1002
1018
|
"Recover images pasted into Claude Code from the session transcript (they never hit disk otherwise)"
|
|
1003
|
-
).option("--count <n>", "How many recent pasted images to recover", "1").option("--out-dir <path>", "Directory to write recovered images to").option(
|
|
1019
|
+
).option("--count <n>", "How many recent pasted images to recover", "1").option("--out-dir <path>", "Directory to write recovered images to").option(
|
|
1020
|
+
"--session <id>",
|
|
1021
|
+
"Claude Code session id for exact targeting (skills get it via ${CLAUDE_SESSION_ID})"
|
|
1022
|
+
).option("--transcript <path>", "Explicit transcript .jsonl (overrides --session)").option("--cwd <path>", "Project directory the image was pasted in", process.cwd()).action(async (options) => {
|
|
1004
1023
|
try {
|
|
1005
1024
|
const count = Number.parseInt(options.count, 10);
|
|
1006
1025
|
if (!Number.isFinite(count) || count <= 0) {
|
|
@@ -1010,6 +1029,7 @@ program.command("recover-paste").description(
|
|
|
1010
1029
|
count,
|
|
1011
1030
|
outDir: options.outDir,
|
|
1012
1031
|
transcript: options.transcript,
|
|
1032
|
+
session: options.session,
|
|
1013
1033
|
cwd: options.cwd
|
|
1014
1034
|
});
|
|
1015
1035
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
package/package.json
CHANGED
package/skills/modlens/SKILL.md
CHANGED
|
@@ -61,14 +61,19 @@ Speed expectations: `gemini-api` typically 5-10 seconds, `antigravity-cli` 15-40
|
|
|
61
61
|
|
|
62
62
|
## Finding the image path in the chat
|
|
63
63
|
|
|
64
|
-
Harnesses rarely hand you a clean path.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
Harnesses rarely hand you a clean path. First identify which harness you are in, then use its route. Never mix routes across harnesses.
|
|
65
|
+
|
|
66
|
+
**Codex** (you see a text tag like `<image name=[Image #1] path="/tmp/xxxx.png">`):
|
|
67
|
+
|
|
68
|
+
- Extract the `path` value from the tag and run modlens on it. Pasted images live in a temp file Codex already created; a stripped image keeps its path tag next to the placeholder. Do NOT use `recover-paste` here: it reads Claude Code session files, which do not exist for Codex.
|
|
69
|
+
|
|
70
|
+
**Claude Code** (no path tag anywhere; the placeholder looks like `[Unsupported Image]` or a bare `[Image #1]`, and `${CLAUDE_SESSION_ID}` below reads as a UUID):
|
|
71
|
+
|
|
72
|
+
- Claude Code never writes pasted images to a regular temp file, but it logs them into its local session transcript. Run `modlens recover-paste` (add `--count <n>` for several images): it recovers the pasted image bytes and prints real file paths as JSON. Feed that path to `modlens -i`.
|
|
73
|
+
- Session targeting: your session id is ${CLAUDE_SESSION_ID}. If that value reads as a UUID, pass it as `--session <uuid>` for exact targeting. If it reads as a literal placeholder, omit `--session`: the command auto-locates by scanning this project's transcripts for the newest pasted-image message, which is the session the user just pasted into, even with concurrent sessions. Run it from the project directory the conversation is happening in.
|
|
74
|
+
- If recovery fails (transcript format is Claude Code internals and may change), ask the user to drag the image file into the terminal or type its path.
|
|
75
|
+
|
|
76
|
+
**Any other harness, or nothing matches** (no path tag, `${CLAUDE_SESSION_ID}` still a literal placeholder, no Claude Code transcripts): do not guess and do not run `recover-paste`. Ask the user for the image file path, or suggest dragging the file into the terminal.
|
|
72
77
|
|
|
73
78
|
## Workflow
|
|
74
79
|
|