@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 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 rows = opencodeQuery(dbPath, cwd);
1037
- const images = opencodeImagesFromRows(rows);
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: () => opencodeImagesFromRows(opencodeQuery(dbPath, cwd))
1046
+ extract: () => scoped.flatMap((entry) => entry.images)
1046
1047
  },
1047
- timestamp: rows[rows.length - 1].time_created
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
- return { path: filePath, mediaType: image.mediaType, bytes: buffer.length };
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.0");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liustack/modlens",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "Plug-in vision for text-only LLMs, powered by the free Antigravity CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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. Feed that path to `modlens -i`.
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