@liustack/modlens 2.7.5 → 2.7.6
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 +3 -0
- package/README.zh-CN.md +3 -0
- package/dist/main.js +20 -12
- package/package.json +1 -1
- package/skills/modlens/SKILL.md +1 -0
package/README.md
CHANGED
|
@@ -138,9 +138,12 @@ Two more subcommands: `modlens config <init|set|show>` manages providers and key
|
|
|
138
138
|
modlens recover-paste # newest pasted image, path printed as JSON
|
|
139
139
|
modlens recover-paste --count 3 # the three newest
|
|
140
140
|
modlens recover-paste --session <id> # exact session (skills pass ${CLAUDE_SESSION_ID})
|
|
141
|
+
modlens recover-paste --harness pi # force one harness's format
|
|
141
142
|
# --transcript <path> overrides everything; --cwd <dir> sets the project directory
|
|
142
143
|
```
|
|
143
144
|
|
|
145
|
+
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.
|
|
146
|
+
|
|
144
147
|
## Providers and config
|
|
145
148
|
|
|
146
149
|
ModLens ships five vision providers. `antigravity-cli` stays the default: zero keys, pure free quota.
|
package/README.zh-CN.md
CHANGED
|
@@ -138,9 +138,12 @@ modlens -i <图片路径或 URL> [选项]
|
|
|
138
138
|
modlens recover-paste # 捞最新一张,路径以 JSON 打印
|
|
139
139
|
modlens recover-paste --count 3 # 捞最近三张
|
|
140
140
|
modlens recover-paste --session <id> # 精确会话(skill 会传 ${CLAUDE_SESSION_ID})
|
|
141
|
+
modlens recover-paste --harness pi # 强制按某家宿主的格式解析
|
|
141
142
|
# --transcript <path> 优先级最高,--cwd <dir> 指定项目目录
|
|
142
143
|
```
|
|
143
144
|
|
|
145
|
+
恢复出来的图片写成 0600、放进 0700 目录,共享机器上别人读不到。定位会话时除了目录,还会核对会话记录里写着的真实工作目录,因为目录 slug 会碰撞(`/tmp/a.b` 和 `/tmp/a-b` 算出同一个),不核对就可能把隔壁项目的图交给你。
|
|
146
|
+
|
|
144
147
|
## Provider 与配置
|
|
145
148
|
|
|
146
149
|
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
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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.
|
|
1481
|
+
program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("2.7.6");
|
|
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
|
-
|
|
1547
|
-
|
|
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
package/skills/modlens/SKILL.md
CHANGED
|
@@ -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.
|