@liustack/modlens 2.7.5 → 2.7.7

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 CHANGED
@@ -13,15 +13,16 @@ DeepSeek-V4-Flash gives you a lot of model for very little money: fast, strong,
13
13
 
14
14
  ModLens fixes this the lightest way possible: it never touches your config, never adds a local proxy, and is just a vision plug-in you can run as a CLI or install as an Agent Skill. What it hands back is not a one-line caption but structured visual evidence: text, layout, regions, entities, relations, visual clues. Five vision engines to pick from. The default one needs no key at all, and the fastest one runs on a free Gemini key whose image understanding embarrasses most flagships, Fable 5 included. How it works:
15
15
 
16
- ```text
17
- text-only model in your agent harness ──▶ modlens skill (auto-triggers on images)
18
- │
19
- ▼
20
- vision engine · Gemini 3.6 Flash (free)
21
- │
22
- ▼
23
- structured JSON evidence ──▶ model answers with sight
24
- ```
16
+ ![Paste an image into the terminal, a lens reads it, structured evidence comes back](https://raw.githubusercontent.com/liustack/modlens/main/assets/flow.jpg)
17
+
18
+ - **You just paste.** Every other bridge makes you save a file and report its path. ModLens pulls the pasted image back out of session storage.
19
+ - **Evidence, not an impression.** Full OCR text, layout regions in reading order, semantic entities, visual clues. Your model can quote specifics.
20
+ - **It says when it cannot read something.** Anything uncertain lands in `uncertainty` rather than being invented. Pixel coordinates and confidence scores, the two things models fabricate most, were dropped in v2.
21
+ - **No model swap, no config changes, no local proxy.** You picked your model for price and reasoning, not eyesight. That choice stays.
22
+ - **Starts with no key, gets faster with a free one.** agy needs no key at all, and a free AI Studio Gemini key takes three minutes and reads an image in 5-10 seconds.
23
+ - **Install once, works in four harnesses.** Verified in Claude Code, Codex, Pi, and OpenCode.
24
+
25
+ **Requirements**: Node 18+ (22.5+ for OpenCode paste recovery), macOS or Linux. Hit a problem? [Troubleshooting](docs/troubleshooting.md) lists every error this CLI prints, with causes and fixes.
25
26
 
26
27
  ## You can just paste the image
27
28
 
@@ -31,6 +32,19 @@ That is not laziness on their part. Pasting is handled end to end by the client:
31
32
 
32
33
  Verified on real machines across four harnesses: Claude Code pinpoints the exact session from its injected session id, Pi stores sessions the same way, OpenCode swaps in SQLite, and Codex's pasted images already carry a temp path so the path-tag route handles them. Before touching anything, `recover-paste` works out which harness it is running inside, by walking the process ancestry and checking environment fingerprints, and reads only that harness's storage, so another tool's stale sessions can't impersonate it.
33
34
 
35
+ Side by side:
36
+
37
+ | | Swap in a multimodal model | Vision MCP servers | ModLens |
38
+ | :-- | :-- | :-- | :-- |
39
+ | Your chosen model | has to change | stays | stays |
40
+ | An image pasted into the chat | visible if the model supports it | not reachable, their docs say save a file and report the path | handled directly |
41
+ | What you get back | the model's own reading | usually a description | OCR text, layout regions, semantics, visual clues |
42
+ | Where it cannot read | may invent | may invent | says so in `uncertainty` |
43
+ | Cost | multimodal model pricing | usually per API call | agy's free quota, or a free Gemini key |
44
+ | Setup | change config, change model | install a server, edit config | one CLI or one skill |
45
+
46
+ The honest weaknesses: agy's free tier is a weekly quota and heavy use hits the wall (a free Gemini key sidesteps that). Session storage layouts are each harness's internals with no compatibility promise, so if recovery ever breaks, dragging the file in still works everywhere.
47
+
34
48
  ## Quick start
35
49
 
36
50
  **1. Install the skill.** Just tell your agent (Claude Code, Codex, OpenClaw, Cursor, ...):
@@ -138,9 +152,12 @@ Two more subcommands: `modlens config <init|set|show>` manages providers and key
138
152
  modlens recover-paste # newest pasted image, path printed as JSON
139
153
  modlens recover-paste --count 3 # the three newest
140
154
  modlens recover-paste --session <id> # exact session (skills pass ${CLAUDE_SESSION_ID})
155
+ modlens recover-paste --harness pi # force one harness's format
141
156
  # --transcript <path> overrides everything; --cwd <dir> sets the project directory
142
157
  ```
143
158
 
159
+ Recovered images are written 0600 into a 0700 directory, so nobody else on a shared machine can read them. Locating a session checks the cwd recorded inside the transcript as well as the directory, because directory slugs collide (`/tmp/a.b` and `/tmp/a-b` produce the same one) and without that check you can be handed a neighbouring project's images.
160
+
144
161
  ## Providers and config
145
162
 
146
163
  ModLens ships five vision providers. `antigravity-cli` stays the default: zero keys, pure free quota.
package/README.zh-CN.md CHANGED
@@ -13,15 +13,16 @@ DeepSeek-V4-Flash 碗大又好吃,速度快,性能强,要说唯一的缺
13
13
 
14
14
  ModLens 用最轻的方式解决它:不动你的配置,不装本地代理,就是一个视觉外挂,CLI 和 skill 两种用法。它产出的不是一句话描述,是结构化的视觉证据:文字、版面、区块、实体、关系、视觉线索。视觉引擎有五个可选,默认那个零 key 就能跑,最快的那个用免费 Gemini key,识图能力连 Fable 5 都吊打。原理如下:
15
15
 
16
- ```text
17
- Agent Harness 中的纯文本模型 ──▶ modlens skill(遇到图片自动触发)
18
- │
19
- ▼
20
- 视觉引擎 · Gemini 3.6 Flash(免费)
21
- │
22
- ▼
23
- 结构化 JSON 证据 ──▶ 模型带着视力回答
24
- ```
16
+ ![把图片粘进终端,放大镜读图,结构化证据回到终端](https://raw.githubusercontent.com/liustack/modlens/main/assets/flow.jpg)
17
+
18
+ - **你直接粘贴就行。** 别的方案让你先存成文件再报路径,ModLens 从会话存储里把粘贴的图捞回来。
19
+ - **给的是证据,不是印象。** OCR 全文、按阅读顺序排好的版面区块、语义实体、视觉线索,模型能引用具体内容。
20
+ - **读不准就说读不准。** 拿不准的地方进 `uncertainty`,不编。像素坐标和置信度分数这两样模型最爱编的,v2 直接删了。
21
+ - **不换模型,不改配置,不装代理。** 你选 DeepSeek 图的是价格和推理,不是视力,这个选择不用动。
22
+ - **零 key 起步,想快就领个免费 key。** agy 不要 key,AI Studio 的免费 Gemini key 三分钟到手,识图 5-10 秒。
23
+ - **装一次,四家 harness 通用。** Claude Code、Codex、Pi、OpenCode 都验证过。
24
+
25
+ **环境要求**:Node 18+(OpenCode 的粘贴恢复需要 22.5+),macOS 或 Linux。 出问题看[故障排查](docs/troubleshooting.md),里面按报错原文列了每一条的成因和解法。
25
26
 
26
27
  ## 你可以直接粘贴图片
27
28
 
@@ -31,6 +32,19 @@ Agent Harness 中的纯文本模型 ──▶ modlens skill(遇到图片自动
31
32
 
32
33
  四家 harness 真机验证过:Claude Code 按注入的会话 ID 精确定位,Pi 的存储路数和它同构,OpenCode 换成了 SQLite,Codex 的粘贴图本来就带临时路径,走路径标签就行。动手之前 `recover-paste` 会先认清自己跑在哪一家(查进程祖先链,核对环境变量指纹),只读那一家的存储,别家的旧会话冒充不了。
33
34
 
35
+ 放在一起看更清楚:
36
+
37
+ | | 换个多模态模型 | 识图类 MCP server | ModLens |
38
+ | :-- | :-- | :-- | :-- |
39
+ | 你选的模型 | 得换掉 | 不用换 | 不用换 |
40
+ | 粘贴进对话的图 | 模型支持就能看 | 接不住,文档让你先存文件报路径 | 直接接住 |
41
+ | 拿到手的是什么 | 模型自己的理解 | 通常是一段描述 | OCR 全文、版面区块、语义、视觉线索 |
42
+ | 读不准的地方 | 可能编 | 可能编 | 进 `uncertainty`,明说读不准 |
43
+ | 花费 | 多模态模型的价格 | 多数按 API 计费 | agy 免费额度,或免费 Gemini key |
44
+ | 上手 | 改配置换模型 | 装 server、改配置 | 一个 CLI 或一个 skill |
45
+
46
+ 诚实说短板:agy 的免费额度是周配额,重度用会撞墙(换成免费 Gemini key 就绕开了)。会话存储格式是各家 harness 的内部实现,没有兼容承诺,哪天捞不动了,拖文件永远是保底。
47
+
34
48
  ## 快速开始
35
49
 
36
50
  **1. 装 skill。** 直接告诉你的 agent(Claude Code、Codex、OpenClaw、Cursor 等):
@@ -138,9 +152,12 @@ modlens -i <图片路径或 URL> [选项]
138
152
  modlens recover-paste # 捞最新一张,路径以 JSON 打印
139
153
  modlens recover-paste --count 3 # 捞最近三张
140
154
  modlens recover-paste --session <id> # 精确会话(skill 会传 ${CLAUDE_SESSION_ID})
155
+ modlens recover-paste --harness pi # 强制按某家宿主的格式解析
141
156
  # --transcript <path> 优先级最高,--cwd <dir> 指定项目目录
142
157
  ```
143
158
 
159
+ 恢复出来的图片写成 0600、放进 0700 目录,共享机器上别人读不到。定位会话时除了目录,还会核对会话记录里写着的真实工作目录,因为目录 slug 会碰撞(`/tmp/a.b` 和 `/tmp/a-b` 算出同一个),不核对就可能把隔壁项目的图交给你。
160
+
144
161
  ## Provider 与配置
145
162
 
146
163
  ModLens 内置五个视觉 provider,默认还是 `antigravity-cli`:零 key,纯免费额度。
package/dist/main.js CHANGED
@@ -800,14 +800,9 @@ function setConfigValue(dottedKey, value, configPath = CONFIG_PATH) {
800
800
  }
801
801
  }
802
802
  const CONFIG_TEMPLATE = {
803
- provider: "antigravity-cli",
804
- providers: {
805
- "antigravity-cli": { model: "gemini-3.6-flash-low" },
806
- "gemini-api": { apiKey: "", model: "gemini-3.6-flash" },
807
- openai: { baseUrl: "", apiKey: "", model: "" },
808
- anthropic: { apiKey: "", model: "claude-haiku-4-5-20251001" },
809
- "claude-cli": { model: "haiku" }
810
- }
803
+ // Empty means the built-in default provider.
804
+ provider: "",
805
+ providers: {}
811
806
  };
812
807
  function initConfigFile(configPath = CONFIG_PATH, force = false) {
813
808
  if (!force && fs.existsSync(configPath)) {
@@ -845,6 +840,7 @@ function maskKey(key) {
845
840
  const DEFAULT_TIMEOUT_MS = 18e4;
846
841
  const KILL_GRACE_MS = 3e4;
847
842
  const DRAIN_GRACE_MS = 500;
843
+ const SIGKILL_GRACE_MS = 2e3;
848
844
  async function analyzeImage(options) {
849
845
  const resolvedInput = resolveInput(options.input);
850
846
  if (resolvedInput.kind === "local") {
@@ -937,6 +933,12 @@ function runCommand(providerName, invocation, timeoutMs, describeFailure) {
937
933
  const timer = setTimeout(() => {
938
934
  timedOut = true;
939
935
  child.kill("SIGTERM");
936
+ settle(null);
937
+ setTimeout(() => {
938
+ if (!child.killed) {
939
+ child.kill("SIGKILL");
940
+ }
941
+ }, SIGKILL_GRACE_MS).unref();
940
942
  }, timeoutMs);
941
943
  const settle = (code) => {
942
944
  if (settled) {
@@ -945,6 +947,8 @@ function runCommand(providerName, invocation, timeoutMs, describeFailure) {
945
947
  settled = true;
946
948
  clearTimeout(timer);
947
949
  clearTimeout(drainTimer);
950
+ stdout += outDecoder.decode();
951
+ stderr += errDecoder.decode();
948
952
  child.stdout?.destroy();
949
953
  child.stderr?.destroy();
950
954
  child.unref();
@@ -1474,7 +1478,7 @@ function recoverPastedImages(options = {}) {
1474
1478
  return result;
1475
1479
  }
1476
1480
  const program = new Command();
1477
- program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("2.7.5");
1481
+ program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("2.7.7");
1478
1482
  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) => {
1479
1483
  try {
1480
1484
  const timeoutMs = Number.parseInt(options.timeout, 10);
@@ -1543,9 +1547,13 @@ config.command("init").description(`Create a starter config at ${CONFIG_PATH}`).
1543
1547
  try {
1544
1548
  initConfigFile(CONFIG_PATH, Boolean(options.force));
1545
1549
  process.stdout.write(
1546
- `Created ${CONFIG_PATH}
1547
- Fill in the apiKey fields you need, or use: modlens config set <provider>.apiKey <key>
1548
- `
1550
+ [
1551
+ `Created ${CONFIG_PATH}`,
1552
+ "Everything is optional. Two things you can set:",
1553
+ " modlens config set provider <name> which provider analyzes images",
1554
+ " modlens config set <provider>.<apiKey|baseUrl|model> <value> provider credentials",
1555
+ ""
1556
+ ].join("\n")
1549
1557
  );
1550
1558
  } catch (error) {
1551
1559
  process.stderr.write(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liustack/modlens",
3
- "version": "2.7.5",
3
+ "version": "2.7.7",
4
4
  "description": "Plug-in vision for text-only LLMs, powered by the free Antigravity CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -73,6 +73,7 @@ Harnesses rarely hand you a clean path. First identify which harness you are in,
73
73
  - The output is JSON with real file paths, ordered oldest to newest, so the LAST path is the user's most recent paste. Analyze that one first. Entries carry `filename` (the original attachment name) when the harness stored one; if the user's message or an error mentions a filename, match on it.
74
74
  - Run every command yourself: `recover-paste`, then `modlens -i <path>` on the recovered file, then answer from the JSON. Never ask the user to run modlens or to relay paths.
75
75
  - The output's `detected` field names the harness scope that was applied. If it is absent, detection failed and every store was scanned by newest-image timestamp: before describing anything, check that `harness` and `filename` match what you expect, force the scope with `--harness <claude-code|pi|opencode>` if they do not, and when in doubt ask the user for the file instead of describing the wrong image.
76
+ - Recovery is scoped to this project: the harness's own record of its working directory is checked, not just the directory name, so images from a neighbouring project are never handed over. Recovered files are private to the user (0600).
76
77
  - If recovery fails (session storage is each harness's internals and may change), ask the user to drag the image file into the terminal or type its path.
77
78
 
78
79
  **Any other harness, or nothing matches** (no path tag and `recover-paste` reports no transcripts): do not guess. Ask the user for the image file path, or suggest dragging the file into the terminal.
@@ -100,6 +101,8 @@ Structure is enforced by schema on antigravity-cli and claude-cli (`--json-schem
100
101
 
101
102
  ## Failure Handling
102
103
 
104
+ Every error this CLI prints is catalogued with its cause and fix in the project's `docs/troubleshooting.md`. Read the message first: most of them already name the fix.
105
+
103
106
  - `Provider CLI not found`: Antigravity CLI is not installed. Install it, or switch provider: `-p gemini-api`.
104
107
  - Missing key errors name the exact env var and `config set` command to run. Relay that to the user.
105
108
  - `does not match the vision schema` on the openai route: retry once, then switch to `-p gemini-api` or `-p anthropic` for enforced schemas.