opencode-see-image 0.5.10 → 0.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/bun.lock +3 -0
- package/index.ts +22 -137
- package/package.json +3 -2
package/bun.lock
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"name": "opencode-see-image",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@opencode-ai/plugin": "^1.15.0",
|
|
9
|
+
"opencode-plugin-update-kit": "^0.1.0",
|
|
9
10
|
},
|
|
10
11
|
},
|
|
11
12
|
},
|
|
@@ -52,6 +53,8 @@
|
|
|
52
53
|
|
|
53
54
|
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
|
|
54
55
|
|
|
56
|
+
"opencode-plugin-update-kit": ["opencode-plugin-update-kit@0.1.0", "", {}, "sha512-2pZ8H6QnMGN2drK0g0vHSUY7jW4sP3XItUljdZlw1iVJIhcu1sQxzEmRD6oF+T7kWRXU6uJmN9Koq9IGuyibwg=="],
|
|
57
|
+
|
|
55
58
|
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
|
56
59
|
|
|
57
60
|
"pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="],
|
package/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin"
|
|
2
|
+
import { autoUpdate } from "opencode-plugin-update-kit"
|
|
2
3
|
import path from "path"
|
|
3
4
|
import os from "os"
|
|
4
5
|
import fs from "fs"
|
|
@@ -324,158 +325,42 @@ async function seeImageViaHTTP(
|
|
|
324
325
|
|
|
325
326
|
const SYSTEM_INSTRUCTIONS = `# See Image (vision bridge), opencode-see-image plugin
|
|
326
327
|
|
|
327
|
-
You have
|
|
328
|
+
You have a \`see_image\` tool. When the user attaches an image, opencode rejects it and you receive an error string containing the **filename**. You MUST call \`see_image\` to actually view it — do NOT just tell the user about the error.
|
|
328
329
|
|
|
329
|
-
## When
|
|
330
|
+
## When you MUST call \`see_image\`
|
|
330
331
|
|
|
331
|
-
|
|
332
|
-
1. You receive an error like: \`Cannot read "Screenshot ....png" (this model does not support image input)\`
|
|
333
|
-
2. The user references an image/screenshot they expect you to see ("see this", "look at this", "can you see this", ".png"/".jpg")
|
|
334
|
-
3. The user pastes an image path they want you to inspect
|
|
332
|
+
Call \`see_image\` immediately in ALL these cases — do not inform the user, do not ask for confirmation:
|
|
335
333
|
|
|
336
|
-
|
|
334
|
+
1. You receive an error containing \`Cannot read "Screenshot ..."\` or \`Cannot read "...png"\` or \`Cannot read "...jpg"\`
|
|
335
|
+
2. The error message says \`this model does not support image input\`
|
|
336
|
+
3. The error starts with \`ERROR:\` and mentions an image file
|
|
337
|
+
4. The user references an image/screenshot ("see this", "look at this", ".png", ".jpg")
|
|
338
|
+
5. The user pastes an image path
|
|
337
339
|
|
|
338
340
|
## How to use it
|
|
339
341
|
|
|
340
|
-
1.
|
|
341
|
-
2.
|
|
342
|
-
3.
|
|
342
|
+
1. Extract the filename from the error (it's in quotes in the error message, e.g. \`Screenshot 2026-06-19 at 02.18.53.png\`)
|
|
343
|
+
2. Call \`see_image\` with \`filePath\` set to that bare filename
|
|
344
|
+
3. Optionally pass a \`question\` if the user asked something specific
|
|
345
|
+
4. Answer using the returned description as if you saw the image. Be natural.
|
|
343
346
|
|
|
344
347
|
## Important
|
|
345
348
|
|
|
346
|
-
-
|
|
347
|
-
- If
|
|
348
|
-
-
|
|
349
|
+
- NEVER just repeat the error to the user. Call the tool.
|
|
350
|
+
- If \`see_image\` cannot find the file, tell the user the filename and ask for an absolute path.
|
|
351
|
+
- Do NOT use \`see_image\` for text files (\`.ts\`, \`.md\`, \`.json\`, etc.) — use \`read\` instead.
|
|
352
|
+
- Never guess image contents. If you haven't called \`see_image\`, you haven't seen the image.`
|
|
349
353
|
|
|
350
354
|
const PKG_NAME = "opencode-see-image"
|
|
351
|
-
const REGISTRY_LATEST = `https://registry.npmjs.org/${PKG_NAME}/latest`
|
|
352
|
-
|
|
353
|
-
function currentVersion(): string | null {
|
|
354
|
-
// Try import.meta.url first (works when not bundled)
|
|
355
|
-
try {
|
|
356
|
-
const here = new URL(".", import.meta.url)
|
|
357
|
-
const pkgPath = new URL("package.json", here)
|
|
358
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
359
|
-
if (pkg.version) return pkg.version
|
|
360
|
-
} catch {}
|
|
361
|
-
|
|
362
|
-
// Fallback: walk up from import.meta.url looking for package.json with our name
|
|
363
|
-
try {
|
|
364
|
-
let dir = new URL(".", import.meta.url)
|
|
365
|
-
for (let i = 0; i < 10; i++) {
|
|
366
|
-
const pkgPath = new URL("package.json", dir)
|
|
367
|
-
try {
|
|
368
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
369
|
-
if (pkg.name === PKG_NAME && pkg.version) return pkg.version
|
|
370
|
-
} catch {}
|
|
371
|
-
const parent = new URL("../", dir)
|
|
372
|
-
if (parent.href === dir.href) break
|
|
373
|
-
dir = parent
|
|
374
|
-
}
|
|
375
|
-
} catch {}
|
|
376
|
-
|
|
377
|
-
// Last resort: check the known opencode cache paths
|
|
378
|
-
try {
|
|
379
|
-
const cacheDir = path.join(os.homedir(), ".cache/opencode/packages")
|
|
380
|
-
if (fs.existsSync(cacheDir)) {
|
|
381
|
-
for (const sub of fs.readdirSync(cacheDir)) {
|
|
382
|
-
if (sub.startsWith(PKG_NAME)) {
|
|
383
|
-
const pkgPath = path.join(cacheDir, sub, "node_modules", PKG_NAME, "package.json")
|
|
384
|
-
try {
|
|
385
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
386
|
-
if (pkg.version) return pkg.version
|
|
387
|
-
} catch {}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
} catch {}
|
|
392
|
-
|
|
393
|
-
return null
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function semverGt(a: string, b: string): boolean {
|
|
397
|
-
const pa = a.split(".").map((n) => parseInt(n, 10) || 0)
|
|
398
|
-
const pb = b.split(".").map((n) => parseInt(n, 10) || 0)
|
|
399
|
-
for (let i = 0; i < 3; i++) {
|
|
400
|
-
const x = pa[i] ?? 0
|
|
401
|
-
const y = pb[i] ?? 0
|
|
402
|
-
if (x > y) return true
|
|
403
|
-
if (x < y) return false
|
|
404
|
-
}
|
|
405
|
-
return false
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
async function maybeAutoUpdate(
|
|
409
|
-
client: any,
|
|
410
|
-
$: any,
|
|
411
|
-
log: (msg: string, level?: string) => void,
|
|
412
|
-
) {
|
|
413
|
-
log(`starting update check`, "debug")
|
|
414
|
-
const current = currentVersion()
|
|
415
|
-
if (!current) {
|
|
416
|
-
log(`could not determine current version`, "warn")
|
|
417
|
-
return
|
|
418
|
-
}
|
|
419
|
-
log(`current version: ${current}`, "debug")
|
|
420
|
-
|
|
421
|
-
let latest: string
|
|
422
|
-
try {
|
|
423
|
-
const res = await fetch(REGISTRY_LATEST, {
|
|
424
|
-
headers: { accept: "application/json" },
|
|
425
|
-
})
|
|
426
|
-
if (!res.ok) return
|
|
427
|
-
const data: any = await res.json()
|
|
428
|
-
latest = data?.version
|
|
429
|
-
if (!latest) return
|
|
430
|
-
} catch {
|
|
431
|
-
return
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (!semverGt(latest, current)) return
|
|
435
|
-
|
|
436
|
-
log(`update available: ${current} -> ${latest}; updating`, "info")
|
|
437
|
-
|
|
438
|
-
const specifier = `${PKG_NAME}@${latest}`
|
|
439
|
-
const opencodeBin =
|
|
440
|
-
process.env.OPENCODE_BIN ||
|
|
441
|
-
path.join(os.homedir(), ".opencode/bin/opencode")
|
|
442
|
-
try {
|
|
443
|
-
await $`${opencodeBin} plugin ${specifier} --force --global`.quiet()
|
|
444
|
-
} catch (e: any) {
|
|
445
|
-
try {
|
|
446
|
-
await $`opencode plugin ${specifier} --force --global`.quiet()
|
|
447
|
-
} catch (e2: any) {
|
|
448
|
-
log(`plugin update failed: ${e2?.message ?? e2}`, "warn")
|
|
449
|
-
return
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
log(`update applied: ${current} -> ${latest}; restart opencode to load`, "info")
|
|
454
|
-
try {
|
|
455
|
-
await client?.tui?.showToast?.({ body: { message: `${PKG_NAME} updated to ${latest}, restart opencode to apply`, variant: "success", duration: 86_400_000 } })
|
|
456
|
-
} catch {
|
|
457
|
-
// toast is non-critical, log already captured
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
355
|
|
|
461
356
|
const SeeImagePlugin: Plugin = async (ctx) => {
|
|
462
357
|
const { client, $ } = ctx
|
|
463
358
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (process.env.NODE_ENV !== "production") {
|
|
470
|
-
console.log(`[${PKG_NAME}] ${level}: ${message}`)
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const version = currentVersion() || "unknown"
|
|
476
|
-
log(`plugin initialized v${version}`, "info")
|
|
477
|
-
maybeAutoUpdate(client, $, log).catch((e) => {
|
|
478
|
-
log(`auto-update error: ${e?.message ?? e}`, "warn")
|
|
359
|
+
autoUpdate({
|
|
360
|
+
pkgName: PKG_NAME,
|
|
361
|
+
client,
|
|
362
|
+
$,
|
|
363
|
+
importMeta: import.meta,
|
|
479
364
|
})
|
|
480
365
|
|
|
481
366
|
const seeImageTool = tool({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-see-image",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Give non-vision opencode models the ability to see images/screenshots by routing them to a vision-capable model (MiniMax M3 via opencode-go by default).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@opencode-ai/plugin": "^1.15.0"
|
|
25
|
+
"@opencode-ai/plugin": "^1.15.0",
|
|
26
|
+
"opencode-plugin-update-kit": "^0.1.0"
|
|
26
27
|
}
|
|
27
28
|
}
|