@liustack/modlens 2.6.0 → 2.6.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/dist/main.js +20 -11
- package/package.json +1 -1
- package/skills/modlens/SKILL.md +3 -1
package/dist/main.js
CHANGED
|
@@ -991,10 +991,10 @@ function opencodeQuery(dbPath, cwd, sessionId) {
|
|
|
991
991
|
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
992
992
|
try {
|
|
993
993
|
const resolved = path.resolve(cwd);
|
|
994
|
-
const sessionFilter = sessionId ? `AND (session.id = ? OR session.slug = ?)` : `AND session.directory =
|
|
995
|
-
const params = sessionId ? [sessionId, sessionId] : [resolved];
|
|
994
|
+
const sessionFilter = sessionId ? `AND (session.id = ? OR session.slug = ?)` : `AND (session.directory = ? OR session.directory LIKE ? || '/%' OR ? LIKE session.directory || '/%')`;
|
|
995
|
+
const params = sessionId ? [sessionId, sessionId] : [resolved, resolved, resolved];
|
|
996
996
|
const rows = db.prepare(
|
|
997
|
-
`SELECT part.data AS data, part.time_created AS time_created
|
|
997
|
+
`SELECT part.data AS data, part.time_created AS time_created, part.session_id AS session_id
|
|
998
998
|
FROM part
|
|
999
999
|
JOIN message ON message.id = part.message_id
|
|
1000
1000
|
JOIN session ON session.id = part.session_id
|
|
@@ -1018,7 +1018,7 @@ function opencodeImagesFromRows(rows) {
|
|
|
1018
1018
|
}
|
|
1019
1019
|
const match = /^data:[^;]+;base64,(.+)$/.exec(part.url ?? "");
|
|
1020
1020
|
if (match) {
|
|
1021
|
-
images.push({ mediaType: part.mime, data: match[1] });
|
|
1021
|
+
images.push({ mediaType: part.mime, data: match[1], filename: part.filename });
|
|
1022
1022
|
}
|
|
1023
1023
|
} catch {
|
|
1024
1024
|
}
|
|
@@ -1033,18 +1033,19 @@ const opencodeAdapter = {
|
|
|
1033
1033
|
if (!fs.existsSync(dbPath)) {
|
|
1034
1034
|
return null;
|
|
1035
1035
|
}
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
if (images.length === 0) {
|
|
1036
|
+
const withImages = opencodeQuery(dbPath, cwd).map((row) => ({ row, images: opencodeImagesFromRows([row]) })).filter((entry) => entry.images.length > 0);
|
|
1037
|
+
if (withImages.length === 0) {
|
|
1039
1038
|
return null;
|
|
1040
1039
|
}
|
|
1040
|
+
const newest = withImages[withImages.length - 1];
|
|
1041
|
+
const scoped = withImages.filter((entry) => entry.row.session_id === newest.row.session_id);
|
|
1041
1042
|
return {
|
|
1042
1043
|
ref: {
|
|
1043
1044
|
harness: "opencode",
|
|
1044
1045
|
location: dbPath,
|
|
1045
|
-
extract: () =>
|
|
1046
|
+
extract: () => scoped.flatMap((entry) => entry.images)
|
|
1046
1047
|
},
|
|
1047
|
-
timestamp:
|
|
1048
|
+
timestamp: newest.row.time_created
|
|
1048
1049
|
};
|
|
1049
1050
|
},
|
|
1050
1051
|
findSession: (cwd, sessionId) => {
|
|
@@ -1131,12 +1132,20 @@ function recoverPastedImages(options = {}) {
|
|
|
1131
1132
|
const ext = EXT_BY_MIME[image.mediaType] ?? "png";
|
|
1132
1133
|
const filePath = path.join(outDir, `paste-${hash}.${ext}`);
|
|
1133
1134
|
fs.writeFileSync(filePath, buffer);
|
|
1134
|
-
|
|
1135
|
+
const recovered = {
|
|
1136
|
+
path: filePath,
|
|
1137
|
+
mediaType: image.mediaType,
|
|
1138
|
+
bytes: buffer.length
|
|
1139
|
+
};
|
|
1140
|
+
if (image.filename) {
|
|
1141
|
+
recovered.filename = image.filename;
|
|
1142
|
+
}
|
|
1143
|
+
return recovered;
|
|
1135
1144
|
});
|
|
1136
1145
|
return { harness: source.harness, transcript: source.location, images };
|
|
1137
1146
|
}
|
|
1138
1147
|
const program = new Command();
|
|
1139
|
-
program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("2.6.
|
|
1148
|
+
program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("2.6.1");
|
|
1140
1149
|
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) => {
|
|
1141
1150
|
try {
|
|
1142
1151
|
const timeoutMs = Number.parseInt(options.timeout, 10);
|
package/package.json
CHANGED
package/skills/modlens/SKILL.md
CHANGED
|
@@ -69,7 +69,9 @@ Harnesses rarely hand you a clean path. First identify which harness you are in,
|
|
|
69
69
|
|
|
70
70
|
**Claude Code, Pi, or OpenCode** (no path tag anywhere; the image reads as `[Unsupported Image]`, a bare `[Image #1]`, or an attachment you simply cannot see):
|
|
71
71
|
|
|
72
|
-
- None of these harnesses writes pasted images to a regular temp file, but all of them persist user messages locally before any gateway strips them: Claude Code and Pi in session JSONL files (`~/.claude/projects/`, `~/.pi/agent/sessions/`), OpenCode in a SQLite database (`~/.local/share/opencode/opencode.db`, read via node:sqlite, needs Node 22.5+). `recover-paste` scans all three and auto-detects which harness owns the newest pasted image for this project. Run `modlens recover-paste` (add `--count <n>` for several images): it recovers the pasted image bytes and prints real file paths as JSON.
|
|
72
|
+
- None of these harnesses writes pasted images to a regular temp file, but all of them persist user messages locally before any gateway strips them: Claude Code and Pi in session JSONL files (`~/.claude/projects/`, `~/.pi/agent/sessions/`), OpenCode in a SQLite database (`~/.local/share/opencode/opencode.db`, read via node:sqlite, needs Node 22.5+). `recover-paste` scans all three and auto-detects which harness owns the newest pasted image for this project. Run `modlens recover-paste` (add `--count <n>` for several images): it recovers the pasted image bytes and prints real file paths as JSON, 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.
|
|
73
|
+
- 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.
|
|
74
|
+
- Sanity-check the `harness` field in the output against the harness you are actually running in. If they differ (say it reports `claude-code` while you are in OpenCode), the images are probably stale pastes from another tool's sessions in the same project: check `filename` against what the user sent, and when in doubt ask for the file instead of describing the wrong image.
|
|
73
75
|
- Session targeting: your session id is ${CLAUDE_SESSION_ID}. If that value reads as a UUID, pass it as `--session <uuid>` for exact targeting (on OpenCode, the session id or slug works too). If it reads as a literal placeholder, omit `--session`: the command auto-locates by scanning this project's sessions for the newest pasted-image message, which is the session the user just pasted into, even with concurrent sessions. Run it from the project directory the conversation is happening in.
|
|
74
76
|
- 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.
|
|
75
77
|
|