@liustack/modlens 3.1.1 → 3.2.0
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.2.0 - 2026-08-07
|
|
4
|
+
|
|
5
|
+
- A remote image URL with no explicit `-p` now runs on `gemini-api` whenever a Gemini key is configured, even if the default provider is an agent. The inline path downloads the image itself, behind the private-address guards, the magic-byte image check, and the 25 MB cap; an agent fetching the URL on its own passes through none of those. Without a Gemini key the run stays on the configured default, a local image never reroutes, and an explicit `-p` always wins.
|
|
6
|
+
|
|
3
7
|
## 3.1.1 - 2026-08-07
|
|
4
8
|
|
|
5
9
|
Fixes from a deep acceptance review (external audit, reproduced and verified here).
|
package/dist/main.js
CHANGED
|
@@ -28161,13 +28161,25 @@ const DEFAULT_TIMEOUT_MS = 18e4;
|
|
|
28161
28161
|
const KILL_GRACE_MS = 3e4;
|
|
28162
28162
|
const DRAIN_GRACE_MS = 500;
|
|
28163
28163
|
const SIGKILL_GRACE_MS = 2e3;
|
|
28164
|
+
function chooseProviderName(requested, config2, kind, env = process.env) {
|
|
28165
|
+
const name = requested || defaultProviderName(config2);
|
|
28166
|
+
if (requested || kind !== "remote") {
|
|
28167
|
+
return name;
|
|
28168
|
+
}
|
|
28169
|
+
if (!resolveProvider(name).isolateWorkdir) {
|
|
28170
|
+
return name;
|
|
28171
|
+
}
|
|
28172
|
+
return resolveProviderSettings("gemini-api", config2, env).apiKey ? "gemini-api" : name;
|
|
28173
|
+
}
|
|
28164
28174
|
async function analyzeImage(options) {
|
|
28165
28175
|
const resolvedInput = resolveInput(options.input);
|
|
28166
28176
|
if (resolvedInput.kind === "local") {
|
|
28167
28177
|
validateInputFile(resolvedInput.source);
|
|
28168
28178
|
}
|
|
28169
28179
|
const config2 = options.config ?? loadConfigFile();
|
|
28170
|
-
const provider = resolveProvider(
|
|
28180
|
+
const provider = resolveProvider(
|
|
28181
|
+
chooseProviderName(options.provider, config2, resolvedInput.kind)
|
|
28182
|
+
);
|
|
28171
28183
|
const settings = resolveProviderSettings(provider.name, config2);
|
|
28172
28184
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
28173
28185
|
const model = options.model || settings.model || provider.defaultModel;
|
|
@@ -29104,7 +29116,7 @@ function recoverPastedImages(options = {}) {
|
|
|
29104
29116
|
return result;
|
|
29105
29117
|
}
|
|
29106
29118
|
const program = new Command();
|
|
29107
|
-
program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("3.
|
|
29119
|
+
program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("3.2.0");
|
|
29108
29120
|
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) => {
|
|
29109
29121
|
try {
|
|
29110
29122
|
const timeoutMs = Number.parseInt(options.timeout, 10);
|
package/docs/security.md
CHANGED
|
@@ -22,7 +22,7 @@ The `claude-cli` provider runs with `--allowedTools Read` only, so it can read l
|
|
|
22
22
|
|
|
23
23
|
Both subprocess providers also run in a throwaway directory created fresh per call and removed afterward. For a local image it holds a private copy of that one image and nothing else, and it is a real copy, never a hardlink, so a provider writing to its temp path cannot touch the original. For a remote image the directory is empty and the agent downloads into it. Without this, text inside an image could steer a broadly-permissioned agent into reading files next to the original, or whatever project the caller happened to be in. Passing `--workdir` opts out and runs where you point it.
|
|
24
24
|
|
|
25
|
-
This is exposure reduction, not an OS sandbox: the agent can still read absolute paths, reach the network, and spawn processes. Treat it as a narrower default, not a security boundary. For images you do not trust, prefer an inline API provider (`-p gemini-api`), which hands the bytes to an HTTP endpoint and runs no local agent.
|
|
25
|
+
This is exposure reduction, not an OS sandbox: the agent can still read absolute paths, reach the network, and spawn processes. Treat it as a narrower default, not a security boundary. For images you do not trust, prefer an inline API provider (`-p gemini-api`), which hands the bytes to an HTTP endpoint and runs no local agent. Remote URLs already default there when a Gemini key is configured: the inline path downloads the image itself, behind the private-address guards, the magic-byte image check, and the size cap, none of which apply when an agent fetches the URL on its own. An explicit `-p` overrides the reroute.
|
|
26
26
|
|
|
27
27
|
## Image content is untrusted input
|
|
28
28
|
|
package/package.json
CHANGED
package/skills/modlens/SKILL.md
CHANGED
|
@@ -33,11 +33,11 @@ The launcher finds a working way to run modlens and forwards your arguments to i
|
|
|
33
33
|
|
|
34
34
|
### If you cannot run the launcher script
|
|
35
35
|
|
|
36
|
-
Some harnesses forbid running scripts. Reason through the same order by hand and run the first line that works (the pinned version is 3.
|
|
36
|
+
Some harnesses forbid running scripts. Reason through the same order by hand and run the first line that works (the pinned version is 3.2.0):
|
|
37
37
|
|
|
38
|
-
1. A `modlens` on `PATH` whose major version is 3 and is at least 3.
|
|
39
|
-
2. Otherwise, if `npx` exists: `npx --yes --package @liustack/modlens@3.
|
|
40
|
-
3. Otherwise, if `bunx` exists: `bunx --bun @liustack/modlens@3.
|
|
38
|
+
1. A `modlens` on `PATH` whose major version is 3 and is at least 3.2.0: `modlens <args>`.
|
|
39
|
+
2. Otherwise, if `npx` exists: `npx --yes --package @liustack/modlens@3.2.0 modlens <args>`.
|
|
40
|
+
3. Otherwise, if `bunx` exists: `bunx --bun @liustack/modlens@3.2.0 <args>`.
|
|
41
41
|
4. Otherwise none of these runtimes is here. 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.
|
|
42
42
|
|
|
43
43
|
`references/runtime.md` documents the version pin, the compatibility rule, and the diagnostic fields.
|
|
@@ -56,6 +56,8 @@ modlens config show
|
|
|
56
56
|
|
|
57
57
|
`modlens config init` writes a starter config to `~/.modlens/config.json` when none exists. Full setup recipes per provider: `references/configure.md`.
|
|
58
58
|
|
|
59
|
+
One routing rule to know: a remote image URL with no explicit `-p` runs on `gemini-api` whenever a Gemini key is configured, even if the default provider is an agent. The inline path downloads the image itself, behind the private-address guards, the magic-byte image check, and the size cap; an agent fetching the URL on its own passes through none of those. Without a Gemini key the run stays on the configured default, and an explicit `-p` always wins.
|
|
60
|
+
|
|
59
61
|
## Command
|
|
60
62
|
|
|
61
63
|
In the examples below, `modlens` means the command run through the launcher above (`bash <skill-dir>/scripts/run.sh ...`, or the PowerShell form on Windows).
|
|
@@ -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.
|
|
27
|
+
$Pinned = '3.2.0'
|
|
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.
|
|
25
|
+
PINNED="3.2.0"
|
|
26
26
|
# -------------------------------------------------------------------------------
|
|
27
27
|
|
|
28
28
|
NATIVE_NOTE="no native artifact is published for this tool yet; phase A ships npm launch paths only"
|