@liustack/modlens 3.16.0 → 3.16.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,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.16.1 - 2026-08-14
4
+
5
+ - **OpenChamber (OpenCode's desktop UI) is detected, and Windows detection got quieter and sharper ([#30](https://github.com/liustack/modlens/issues/30)).** Three stacked Windows gaps from one runtime-confirmed report. The env-fingerprint fallback never checked the markers opencode servers inject (`OPENCODE`, `OPENCODE_PID`, `OPENCODE_BINARY`), so OpenChamber read as "none detected" and `recover-paste` never auto-ran, while the pasted bytes sat recoverable in the opencode database the whole time; the fingerprint now resolves to `opencode`, placed before Claude Code's so nested setups pick the innermost input box. The `ps` ancestry probe now runs only off Windows: MSYS machines carry a `ps` that exists but rejects `-Ao`, and a failed child's stderr printed into every `doctor` run (the docs always said Windows skips ancestry, now the code agrees). And `findOnPath` tries the PATHEXT extensions before the bare name, so the POSIX `sh` shim npm installs next to `opencode.cmd` no longer shadows the executable into a `spawnSync ENOENT`. Thanks to @IA20201 for a report with the evidence already attached: observed env markers, doctor output, and the exact database row.
6
+
3
7
  ## 3.16.0 - 2026-08-14
4
8
 
5
9
  - **dsh: whether a paste is taken over is now the host's call, made from real model metadata.** The browser half used to guess with a name regex, which read every vision model it did not recognize (Qwen2.5-VL, GPT-4o, ...) as text-only and hijacked its native paste. And turning `pasteToPath` off only removed the host route while the client kept capturing pastes into a 404. The client now asks `GET /modlens/paste?model=<selector label>` and the host answers from the provider registry's declared `inputModalities`, with every unknown answered conservatively: the label carries no provider id, so EVERY model whose name or id appears in it must be confirmed text-only. One image-capable match anywhere vetoes, an unreadable provider catalog vetoes (the vision twin could live there), and missing modality metadata counts as unknown, never as text-only. Verdicts are re-asked on every composer focus and every paste, the host empties its own cache on every provider-topology change (a same-named vision route mounting mid-session is seen within one round-trip), a 60-second hard age bound backstops both, and a route that vanishes mid-session costs only the pastes inside the one round-trip it takes the failure to come back, after which the client forgets its verdicts and stands down. Until a model is positively confirmed text-only, pastes stay native. The paste route's magic-byte table now matches the CLI's signature for signature (a plain `ftypmp42` video is refused instead of saved as `paste.heic`, real heic/heif brands pass), and the browser half gained its first test suite, loaded straight from `dsh/client.js`.
package/dist/main.js CHANGED
@@ -1630,7 +1630,7 @@ const PROVIDER_DESCRIPTORS = [
1630
1630
  ];
1631
1631
  function findOnPath(bin, env) {
1632
1632
  const dirs = (env.PATH ?? "").split(path.delimiter).filter(Boolean);
1633
- const suffixes = process.platform === "win32" ? ["", ...(env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean)] : [""];
1633
+ const suffixes = process.platform === "win32" ? [...(env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean), ""] : [""];
1634
1634
  for (const dir of dirs) {
1635
1635
  for (const suffix of suffixes) {
1636
1636
  const full = path.join(dir, bin + suffix);
@@ -2745,27 +2745,40 @@ function detectHarnessDetailed() {
2745
2745
  if (override) {
2746
2746
  return { harness: override === "none" ? null : override, source: "override" };
2747
2747
  }
2748
- try {
2749
- const ps = childProcess.execFileSync("ps", ["-Ao", "pid=,ppid=,command="], {
2750
- encoding: "utf-8",
2751
- maxBuffer: 16 * 1024 * 1024
2752
- });
2753
- const found = harnessFromPsTable(ps, process.pid);
2754
- if (found) {
2755
- return { harness: found, source: "ancestry" };
2748
+ if (process.platform !== "win32") {
2749
+ try {
2750
+ const ps = childProcess.execFileSync("ps", ["-Ao", "pid=,ppid=,command="], {
2751
+ encoding: "utf-8",
2752
+ maxBuffer: 16 * 1024 * 1024,
2753
+ stdio: ["ignore", "pipe", "pipe"]
2754
+ });
2755
+ const found = harnessFromPsTable(ps, process.pid);
2756
+ if (found) {
2757
+ return { harness: found, source: "ancestry" };
2758
+ }
2759
+ } catch {
2756
2760
  }
2757
- } catch {
2758
2761
  }
2759
- if (process.env.PI_CODING_AGENT) {
2760
- return { harness: "pi", source: "env" };
2762
+ const fromEnv = harnessFromEnv(process.env);
2763
+ if (fromEnv) {
2764
+ return { harness: fromEnv, source: "env" };
2761
2765
  }
2762
- if (process.env.CODEX_THREAD_ID || process.env.CODEX_SANDBOX) {
2763
- return { harness: "codex", source: "env" };
2766
+ return { harness: null, source: "none" };
2767
+ }
2768
+ function harnessFromEnv(env) {
2769
+ if (env.PI_CODING_AGENT) {
2770
+ return "pi";
2764
2771
  }
2765
- if (process.env.CLAUDECODE || process.env.CLAUDE_CODE_SESSION_ID) {
2766
- return { harness: "claude-code", source: "env" };
2772
+ if (env.CODEX_THREAD_ID || env.CODEX_SANDBOX) {
2773
+ return "codex";
2767
2774
  }
2768
- return { harness: null, source: "none" };
2775
+ if (env.OPENCODE || env.OPENCODE_PID || env.OPENCODE_BINARY) {
2776
+ return "opencode";
2777
+ }
2778
+ if (env.CLAUDECODE || env.CLAUDE_CODE_SESSION_ID) {
2779
+ return "claude-code";
2780
+ }
2781
+ return null;
2769
2782
  }
2770
2783
  function detectHarness() {
2771
2784
  return detectHarnessDetailed().harness;
@@ -3781,7 +3794,7 @@ function parsePositiveInt(raw, flag) {
3781
3794
  }
3782
3795
  return Number.parseInt(raw, 10);
3783
3796
  }
3784
- program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("3.16.0");
3797
+ program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("3.16.1");
3785
3798
  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(
3786
3799
  "--extra-body <json>",
3787
3800
  `JSON merged into the API request body, e.g. '{"thinking":{"type":"disabled"}}'`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liustack/modlens",
3
- "version": "3.16.0",
3
+ "version": "3.16.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.16.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.16.1):
24
24
 
25
- 1. A `modlens` on `PATH` whose major version is 3 and is at least 3.16.0: `modlens <args>`.
26
- 2. Otherwise, if `npx` exists: `npx --yes --package @liustack/modlens@3.16.0 modlens <args>`.
27
- 3. Otherwise, if `bunx` exists: `bunx --bun @liustack/modlens@3.16.0 <args>`.
25
+ 1. A `modlens` on `PATH` whose major version is 3 and is at least 3.16.1: `modlens <args>`.
26
+ 2. Otherwise, if `npx` exists: `npx --yes --package @liustack/modlens@3.16.1 modlens <args>`.
27
+ 3. Otherwise, if `bunx` exists: `bunx --bun @liustack/modlens@3.16.1 <args>`.
28
28
  4. Otherwise tell the user no JavaScript runtime was found and that installing Node 22.19+ (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.16.0
11
+ - Pinned CLI version: 3.16.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.16.0'
27
+ $Pinned = '3.16.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.16.0"
25
+ PINNED="3.16.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"