@liustack/modlens 3.12.0 → 3.12.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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.12.1 - 2026-08-14
4
+
5
+ - **claude-cli reads the envelope's `structured_output` first ([#22](https://github.com/liustack/modlens/issues/22)).** Newer claude CLI builds ship the schema-parsed object beside the `result` string, and the parser only hard-parsed the string, so an unescaped newline in the OCR text failed the whole read while the good object sat unread — intermittently, since it depended on what the model emitted. The parse order is now `structured_output`, then fence-tolerant extraction of the result string, then the error, matching the antigravity provider. Thanks to @lin-nanxing for the precise diagnosis, down to the code lines.
6
+ - **A `read_image` name collision no longer kills the whole dsh plugin ([#21](https://github.com/liustack/modlens/issues/21)).** Hosts with a durable attachment store mount dsh's own native `read_image` (from `dsh-tool-fs`), the duplicate registration threw, and the whole plugin fiber failed — vision wrapper included. The registration now falls back to `modlens_read_image` on a name collision (valuable exactly there: the native tool is gated on the model declaring image input and vanishes for text-only models, so the renamed bridge is the only image path left), the name is configurable via the plugin row's `toolName`, and any other registration error degrades loudly instead of taking the plugin down. Thanks to @abyss-stars for the root-cause analysis and the interim patch.
7
+
3
8
  ## 3.12.0 - 2026-08-14
4
9
 
5
10
  - **The API providers work behind a proxy ([#20](https://github.com/liustack/modlens/issues/20)).** Node's fetch ignores `HTTP_PROXY`/`HTTPS_PROXY` entirely, so machines that reach the internet through a proxy could not use `gemini-api` at all, and the failure surfaced as a bare `fetch failed`. The three inline API providers now honor the standard environment variables (`NO_PROXY` included, via undici's `EnvHttpProxyAgent`), with an explicit setting as the escape hatch: `modlens config set proxy <url>` for all API providers, `<provider>.proxy` to scope it to one. A connect-level failure now names the unreachable host and points at both knobs instead of saying `fetch failed`. Scope is deliberate and documented: the proxy applies to API requests only, while the remote-image download path keeps its direct, IP-pinned connection, because its SSRF guards validate the exact address being contacted and a proxy would blind them. Thanks to @soloyu for a report that arrived with the diagnosis, the fix direction, and the security boundary already thought through.
package/dist/main.js CHANGED
@@ -28031,14 +28031,12 @@ function parseClaudeCliOutput(stdout) {
28031
28031
  `Claude CLI reported ${envelope.subtype ?? "an error"}: ${truncate(envelope.result ?? "")}`
28032
28032
  );
28033
28033
  }
28034
- if (typeof envelope.result !== "string" || !envelope.result.trim()) {
28034
+ if (envelope.structured_output === void 0 && (typeof envelope.result !== "string" || !envelope.result.trim())) {
28035
28035
  throw new Error("Claude CLI output contains no result. Check login state (run: claude).");
28036
28036
  }
28037
- let result;
28038
- try {
28039
- result = JSON.parse(envelope.result);
28040
- } catch {
28041
- throw new Error(`Claude CLI returned non-JSON result: ${truncate(envelope.result)}`);
28037
+ const result = envelope.structured_output ?? (typeof envelope.result === "string" ? extractJson(envelope.result) : null);
28038
+ if (result === null || result === void 0) {
28039
+ throw new Error(`Claude CLI returned non-JSON result: ${truncate(envelope.result ?? "")}`);
28042
28040
  }
28043
28041
  return {
28044
28042
  result,
@@ -30702,7 +30700,7 @@ function parsePositiveInt(raw, flag) {
30702
30700
  }
30703
30701
  return Number.parseInt(raw, 10);
30704
30702
  }
30705
- program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("3.12.0");
30703
+ program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("3.12.1");
30706
30704
  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").option(
30707
30705
  "--extra-body <json>",
30708
30706
  `JSON merged into the API request body, e.g. '{"thinking":{"type":"disabled"}}'`
package/dsh/index.js CHANGED
@@ -47,8 +47,16 @@ export function apply(ctx, config = {}) {
47
47
  // the developer-preview registry accepts these and out-of-tree resolution
48
48
  // of @deepseek-ai/dsh-tools is not yet reliable), so this plugin owns its
49
49
  // own argument validation inside execute.
50
- ctx.tools.register({
51
- name: 'read_image',
50
+ //
51
+ // The name can collide: hosts with a durable attachment store mount their
52
+ // own native read_image (dsh-tool-fs), and a duplicate registration throws,
53
+ // which used to fail the whole plugin fiber (issue #21). The collision
54
+ // falls back to a prefixed name — valuable exactly there, since the native
55
+ // tool is gated on the model declaring image input and vanishes for
56
+ // text-only models — and any other registration error degrades loudly
57
+ // instead of taking the vision wrapper down with it.
58
+ const readImageTool = (toolName) => ({
59
+ name: toolName,
52
60
  description:
53
61
  'Read an image through the modlens vision bridge. Use whenever a message references an image the current model cannot see: a local file path or an http(s) URL to a screenshot, photo, chart, diagram, or document scan. Returns structured evidence with every word transcribed (ocr.full_text), layout regions in reading order, semantics, and an uncertainty list; quote the evidence instead of guessing. Requires a configured modlens engine (run `npx @liustack/modlens doctor` in a terminal to check).',
54
62
  parameters: {
@@ -106,6 +114,24 @@ export function apply(ctx, config = {}) {
106
114
  return parsed.result
107
115
  },
108
116
  })
117
+ const preferred = config.toolName || 'read_image'
118
+ try {
119
+ ctx.tools.register(readImageTool(preferred))
120
+ } catch (error) {
121
+ const fallback = 'modlens_read_image'
122
+ if (preferred !== fallback && /already|duplicate/i.test(String(error))) {
123
+ try {
124
+ ctx.tools.register(readImageTool(fallback))
125
+ console.error(
126
+ `[modlens] tool name "${preferred}" is taken by the host; registered as "${fallback}" instead`,
127
+ )
128
+ } catch (retryError) {
129
+ console.error(`[modlens] read_image registration skipped: ${retryError}`)
130
+ }
131
+ } else {
132
+ console.error(`[modlens] read_image registration skipped: ${error}`)
133
+ }
134
+ }
109
135
  }
110
136
 
111
137
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liustack/modlens",
3
- "version": "3.12.0",
3
+ "version": "3.12.1",
4
4
  "description": "Plug-in vision for text-only LLMs, powered by the free Antigravity CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,11 +20,11 @@ powershell -ExecutionPolicy Bypass -File <skill-dir>\scripts\run.ps1 <args>
20
20
 
21
21
  It resolves a working runtime (PATH `modlens`, then `npx`, then `bunx`) and forwards your arguments unchanged. Exit 78 means no runtime: relay the `nextSteps` from its stderr JSON instead of retrying.
22
22
 
23
- If your harness forbids running scripts, reason through the same order by hand and run the first line that works (the pinned version is 3.12.0):
23
+ If your harness forbids running scripts, reason through the same order by hand and run the first line that works (the pinned version is 3.12.1):
24
24
 
25
- 1. A `modlens` on `PATH` whose major version is 3 and is at least 3.12.0: `modlens <args>`.
26
- 2. Otherwise, if `npx` exists: `npx --yes --package @liustack/modlens@3.12.0 modlens <args>`.
27
- 3. Otherwise, if `bunx` exists: `bunx --bun @liustack/modlens@3.12.0 <args>`.
25
+ 1. A `modlens` on `PATH` whose major version is 3 and is at least 3.12.1: `modlens <args>`.
26
+ 2. Otherwise, if `npx` exists: `npx --yes --package @liustack/modlens@3.12.1 modlens <args>`.
27
+ 3. Otherwise, if `bunx` exists: `bunx --bun @liustack/modlens@3.12.1 <args>`.
28
28
  4. Otherwise tell the user no JavaScript runtime was found and that installing Node 22.13+ (https://nodejs.org) or Bun (https://bun.sh) is the next step. Do not claim modlens itself failed.
29
29
 
30
30
  `references/runtime.md` documents the pin and the diagnostic fields.
@@ -8,7 +8,7 @@ shell syntax.
8
8
 
9
9
  ## Pinned version
10
10
 
11
- - Pinned CLI version: 3.12.0
11
+ - Pinned CLI version: 3.12.1
12
12
  - npm package: `@liustack/modlens`
13
13
  - CLI binary name: `modlens`
14
14
 
@@ -24,7 +24,7 @@ $ErrorActionPreference = 'Stop'
24
24
  # package.json version, and the release script rewrites it on every bump.
25
25
  $Package = '@liustack/modlens'
26
26
  $Bin = 'modlens'
27
- $Pinned = '3.12.0'
27
+ $Pinned = '3.12.1'
28
28
  # -------------------------------------------------------------------------------
29
29
 
30
30
  $NativeNote = 'no native artifact is published for this tool yet; phase A ships npm launch paths only'
@@ -22,7 +22,7 @@ set -eu
22
22
  # package.json version, and the release script rewrites it on every bump.
23
23
  PKG="@liustack/modlens"
24
24
  BIN="modlens"
25
- PINNED="3.12.0"
25
+ PINNED="3.12.1"
26
26
  # -------------------------------------------------------------------------------
27
27
 
28
28
  NATIVE_NOTE="no native artifact is published for this tool yet; phase A ships npm launch paths only"