opencode-image 0.1.2 → 2.0.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/README.md +25 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +148 -133
- package/dist/options.d.ts +3 -0
- package/package.json +14 -7
package/README.md
CHANGED
|
@@ -7,9 +7,31 @@
|
|
|
7
7
|
- 兼容任何 OpenAI 风格的 `/images/generations` 与 `/images/edits` 接口
|
|
8
8
|
- 跨平台缩放(基于 [sharp](https://sharp.pixelplumbing.com)),不依赖 `sips` 等系统命令
|
|
9
9
|
|
|
10
|
+
## 版本与兼容(重要)
|
|
11
|
+
|
|
12
|
+
| 版本 | 目标 opencode | 插件 API | npm 安装 |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| **2.x**(当前线,`next` tag) | opencode **2.0+** | `@opencode/plugin@2`(`Plugin.define` + `ctx.tool` / `ctx.session`) | `opencode-image@next` |
|
|
15
|
+
| 1.x(`latest` tag) | opencode **1.x** | `@opencode-ai/plugin` | `opencode-image@latest` |
|
|
16
|
+
|
|
17
|
+
- v2 的插件运行时**只认** `export default { id, effect | setup }`,v1 形态的插件在 v2 里加载不了,所以这是**大版本切换**。
|
|
18
|
+
- 本仓库 `publishConfig.tag` 固定为 `next`,直接 `npm publish` **不会**顶掉 `latest`,因此 **v1 老用户不会被自动升级到 2.x**。
|
|
19
|
+
- v1 线要出新补丁:`bun run release:legacy`(打 patch 到 `latest`)。
|
|
20
|
+
|
|
10
21
|
## 安装
|
|
11
22
|
|
|
12
|
-
|
|
23
|
+
opencode **2.0+**(v2 插件 API):
|
|
24
|
+
|
|
25
|
+
```jsonc
|
|
26
|
+
{
|
|
27
|
+
"$schema": "https://opencode.ai/config.json",
|
|
28
|
+
"plugin": [
|
|
29
|
+
["opencode-image@next", { "apiKey": "sk-..." }]
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
opencode **1.x**(保持老版本):
|
|
13
35
|
|
|
14
36
|
```jsonc
|
|
15
37
|
{
|
|
@@ -20,7 +42,8 @@
|
|
|
20
42
|
}
|
|
21
43
|
```
|
|
22
44
|
|
|
23
|
-
opencode 启动时会自动从 npm 安装插件到 `~/.cache/opencode/packages/opencode-image
|
|
45
|
+
opencode 启动时会自动从 npm 安装插件到 `~/.cache/opencode/packages/opencode-image.../node_modules/`。
|
|
46
|
+
|
|
24
47
|
|
|
25
48
|
## 配置项
|
|
26
49
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare const OpenCodeImagePlugin: Plugin;
|
|
1
|
+
import { Plugin } from "@opencode/plugin";
|
|
2
|
+
export declare const OpenCodeImagePlugin: Plugin.Plugin;
|
|
3
3
|
export default OpenCodeImagePlugin;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import { Plugin } from "@opencode/plugin";
|
|
3
|
+
import { Schema } from "effect";
|
|
3
4
|
import * as fs5 from "node:fs";
|
|
4
5
|
import * as path5 from "node:path";
|
|
5
6
|
|
|
@@ -295,6 +296,7 @@ function resolveOptions(options) {
|
|
|
295
296
|
serverPort: opts.serverPort ?? DEFAULT_SERVER_PORT,
|
|
296
297
|
serverIdleMs: opts.serverIdleMs ?? DEFAULT_SERVER_IDLE_MS,
|
|
297
298
|
updateCheck: opts.updateCheck ?? true,
|
|
299
|
+
injectPrompt: opts.injectPrompt ?? true,
|
|
298
300
|
cacheDir: opts.cacheDir ?? DEFAULT_CACHE_DIR,
|
|
299
301
|
cacheLimit: opts.cacheLimit ?? DEFAULT_CACHE_LIMIT,
|
|
300
302
|
timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
@@ -406,9 +408,9 @@ class ImageServer {
|
|
|
406
408
|
return Buffer.from(relative2.split(path4.sep).join("/"), "utf8").toString("base64url");
|
|
407
409
|
}
|
|
408
410
|
}
|
|
409
|
-
for (const [
|
|
411
|
+
for (const [id, registered] of this.extra) {
|
|
410
412
|
if (registered === file)
|
|
411
|
-
return
|
|
413
|
+
return id;
|
|
412
414
|
}
|
|
413
415
|
const id = randomUUID().replace(/-/g, "").slice(0, 16);
|
|
414
416
|
this.extra.set(id, file);
|
|
@@ -460,9 +462,9 @@ class ImageServer {
|
|
|
460
462
|
const port = this.configuredPort || derivePort(path4.resolve(opts.outDir));
|
|
461
463
|
const server = http.createServer((req, res) => void this.handle(req, res));
|
|
462
464
|
server.unref();
|
|
463
|
-
const failure = await new Promise((
|
|
464
|
-
server.once("error",
|
|
465
|
-
server.listen(port, "127.0.0.1", () =>
|
|
465
|
+
const failure = await new Promise((resolve) => {
|
|
466
|
+
server.once("error", resolve);
|
|
467
|
+
server.listen(port, "127.0.0.1", () => resolve(undefined));
|
|
466
468
|
});
|
|
467
469
|
if (!failure) {
|
|
468
470
|
this.server = server;
|
|
@@ -514,15 +516,15 @@ class ImageServer {
|
|
|
514
516
|
const root = this.root;
|
|
515
517
|
if (!root)
|
|
516
518
|
return;
|
|
517
|
-
let
|
|
519
|
+
let relative;
|
|
518
520
|
try {
|
|
519
|
-
|
|
521
|
+
relative = Buffer.from(id, "base64url").toString("utf8");
|
|
520
522
|
} catch {
|
|
521
523
|
return;
|
|
522
524
|
}
|
|
523
|
-
if (!
|
|
525
|
+
if (!relative || relative.includes("\x00") || path4.isAbsolute(relative))
|
|
524
526
|
return;
|
|
525
|
-
const target = path4.resolve(root,
|
|
527
|
+
const target = path4.resolve(root, relative);
|
|
526
528
|
if (target === root || !target.startsWith(root + path4.sep))
|
|
527
529
|
return;
|
|
528
530
|
try {
|
|
@@ -573,9 +575,25 @@ class ImageServer {
|
|
|
573
575
|
}
|
|
574
576
|
|
|
575
577
|
// src/index.ts
|
|
576
|
-
var
|
|
577
|
-
|
|
578
|
-
|
|
578
|
+
var PLUGIN_ID = "opencode-image";
|
|
579
|
+
var GENERATE_DESCRIPTION = "Generate an image from a text prompt. This is the only supported way to create images in this session: " + "the endpoint and API key are already configured inside this plugin, so NEVER call the image API yourself " + "(no curl, no scripts) and never read the API key out of opencode.json. " + "The first line of the output is a Markdown image line (plus a separate 查看原图 link when displayMaxSize > 0); " + "you MUST copy those lines verbatim into your reply text, otherwise the user cannot see the picture. " + "If the output contains a line starting with [请转述给用户], relay that line to the user as well. " + "To refine a result, call image_edit with the returned path instead of generating a new image.";
|
|
580
|
+
var EDIT_DESCRIPTION = "Edit an existing image with a text instruction (image-to-image). Prefer this over generating a new image " + "when the user wants to adjust a picture: pass the exact path returned by a previous image_generate or " + "image_edit call to keep refining the same one. " + "Same rules: never call the image API yourself and never read the API key; copy the Markdown image line " + "(and the 查看原图 link, if present) from the output verbatim into your reply, and relay any line starting " + "with [请转述给用户].";
|
|
581
|
+
var CACHE_DESCRIPTION = "Inspect or clear the preview-thumbnail cache used for rendering images in the conversation. " + 'Use action="status" to report the cache directory, file count, total size and age of the oldest entry; ' + 'action="clean" to delete every cached preview (the generated images in outDir are NOT touched). ' + "This returns text only — do not turn it into a Markdown image line.";
|
|
582
|
+
var SYSTEM_RULE = "Image generation/editing: always use the `image_generate` / `image_edit` tools provided by the " + "opencode-image plugin — the endpoint and API key are already configured there. Never call the image API " + "directly, never read the API key from config, and do not use the imagegen skill or its CLI. Copy the " + "Markdown image line from that tool's output verbatim into your reply so the user can see the picture.";
|
|
583
|
+
var GenerateInput = Schema.Struct({
|
|
584
|
+
prompt: Schema.String,
|
|
585
|
+
filename: Schema.optional(Schema.String)
|
|
586
|
+
});
|
|
587
|
+
var EditInput = Schema.Struct({
|
|
588
|
+
image: Schema.String,
|
|
589
|
+
prompt: Schema.String,
|
|
590
|
+
filename: Schema.optional(Schema.String)
|
|
591
|
+
});
|
|
592
|
+
var CacheInput = Schema.Struct({
|
|
593
|
+
action: Schema.Literals(["status", "clean"])
|
|
594
|
+
});
|
|
595
|
+
function resolveDestination(filename, options, directory) {
|
|
596
|
+
const dir = resolveOutDir(directory, options.outDir);
|
|
579
597
|
if (filename) {
|
|
580
598
|
const target = path5.isAbsolute(filename) ? filename : path5.join(dir, filename);
|
|
581
599
|
return `${target.replace(/\.[A-Za-z0-9]+$/, "")}.png`;
|
|
@@ -585,123 +603,119 @@ function resolveDestination(filename, outDir, ctx) {
|
|
|
585
603
|
return path5.join(dir, `image-${stamp}-${suffix}.png`);
|
|
586
604
|
}
|
|
587
605
|
function asAttachment(file) {
|
|
588
|
-
return
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
];
|
|
606
|
+
return {
|
|
607
|
+
type: "file",
|
|
608
|
+
mime: "image/png",
|
|
609
|
+
name: path5.basename(file),
|
|
610
|
+
uri: `data:image/png;base64,${fs5.readFileSync(file).toString("base64")}`
|
|
611
|
+
};
|
|
596
612
|
}
|
|
597
|
-
var OpenCodeImagePlugin =
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
613
|
+
var OpenCodeImagePlugin = Plugin.define({
|
|
614
|
+
id: PLUGIN_ID,
|
|
615
|
+
setup: async (ctx) => {
|
|
616
|
+
const options = resolveOptions(ctx.options);
|
|
617
|
+
const directory = ctx.location.directory;
|
|
618
|
+
const outDir = resolveOutDir(directory, options.outDir);
|
|
619
|
+
const cache = new PreviewCache(options.cacheDir, options.cacheLimit);
|
|
620
|
+
const server = new ImageServer(options.serverIdleMs, options.serverPort, cache);
|
|
621
|
+
const selfVersion = readOwnVersion();
|
|
622
|
+
let updateHint;
|
|
623
|
+
let updateChecked = false;
|
|
624
|
+
const upgradeNotice = async () => {
|
|
625
|
+
if (!options.updateCheck || updateChecked || !selfVersion)
|
|
626
|
+
return updateHint;
|
|
627
|
+
updateChecked = true;
|
|
628
|
+
updateHint = await checkForUpdate(selfVersion);
|
|
606
629
|
return updateHint;
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (options.displayMaxSize > 0)
|
|
619
|
-
lines.push(`[查看原图](${server.url(file, 0)})`);
|
|
620
|
-
display = lines.join(`
|
|
630
|
+
};
|
|
631
|
+
const render = async (label, prompt, file, progress, usage) => {
|
|
632
|
+
const { width, height } = await imageDimensions(file);
|
|
633
|
+
let display;
|
|
634
|
+
try {
|
|
635
|
+
await server.start({ outDir });
|
|
636
|
+
const alt = prompt.slice(0, 60);
|
|
637
|
+
const lines = [`})`];
|
|
638
|
+
if (options.displayMaxSize > 0)
|
|
639
|
+
lines.push(`[查看原图](${server.url(file, 0)})`);
|
|
640
|
+
display = lines.join(`
|
|
621
641
|
`);
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
`),
|
|
641
|
-
|
|
642
|
-
|
|
642
|
+
} catch (error) {
|
|
643
|
+
display = `[opencode-image: local image server unavailable, cannot render inline: ${String(error)}]`;
|
|
644
|
+
}
|
|
645
|
+
const notice = await upgradeNotice();
|
|
646
|
+
const output = [
|
|
647
|
+
display,
|
|
648
|
+
`${label} saved to ${file} (${width}x${height})`,
|
|
649
|
+
`Refine it by calling image_edit with image="${file}" and a prompt describing the change.`,
|
|
650
|
+
`IMPORTANT: copy the Markdown image line above verbatim into your reply text, otherwise the user cannot see the picture.`
|
|
651
|
+
];
|
|
652
|
+
if (notice)
|
|
653
|
+
output.push(notice);
|
|
654
|
+
const cacheNotice = server.consumeCacheNotice();
|
|
655
|
+
if (cacheNotice)
|
|
656
|
+
output.push(cacheNotice);
|
|
657
|
+
progress(`${label}: ${prompt.slice(0, 60)}`);
|
|
658
|
+
return {
|
|
659
|
+
content: [{ type: "text", text: output.join(`
|
|
660
|
+
`) }, asAttachment(file)],
|
|
661
|
+
metadata: { file, prompt, usage }
|
|
662
|
+
};
|
|
643
663
|
};
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
image_cache: tool({
|
|
695
|
-
description: "Inspect or clear the preview-thumbnail cache used for rendering images in the conversation. " + 'Use action="status" to report the cache directory, file count, total size and age of the oldest entry; ' + 'action="clean" to delete every cached preview (the generated images in outDir are NOT touched). ' + "This returns text only — do not turn it into a Markdown image line.",
|
|
696
|
-
args: {
|
|
697
|
-
action: z.enum(["status", "clean"]).describe("status = 查看缓存;clean = 清空缓存")
|
|
698
|
-
},
|
|
664
|
+
await ctx.tool.transform((tools) => {
|
|
665
|
+
tools.add({
|
|
666
|
+
name: "image_generate",
|
|
667
|
+
description: GENERATE_DESCRIPTION,
|
|
668
|
+
input: GenerateInput,
|
|
669
|
+
options: { codemode: false },
|
|
670
|
+
execute: async (args, toolCtx) => {
|
|
671
|
+
const title = `Generating image: ${args.prompt.slice(0, 60)}`;
|
|
672
|
+
await toolCtx.progress({ title });
|
|
673
|
+
const { signal, clear } = withTimeout(toolCtx.signal, options.timeoutMs);
|
|
674
|
+
try {
|
|
675
|
+
const remote = await generateImage(options, args.prompt, signal);
|
|
676
|
+
const raw = await fetchImageBytes(remote, signal);
|
|
677
|
+
const destination = resolveDestination(args.filename, options, directory);
|
|
678
|
+
await persistImage(raw, destination, options.maxSize);
|
|
679
|
+
return await render("Image generated", args.prompt, destination, (m) => void toolCtx.progress({ title: m }), remote.usage);
|
|
680
|
+
} finally {
|
|
681
|
+
clear();
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
tools.add({
|
|
686
|
+
name: "image_edit",
|
|
687
|
+
description: EDIT_DESCRIPTION,
|
|
688
|
+
input: EditInput,
|
|
689
|
+
options: { codemode: false },
|
|
690
|
+
execute: async (args, toolCtx) => {
|
|
691
|
+
const source = path5.isAbsolute(args.image) ? args.image : path5.resolve(directory, args.image);
|
|
692
|
+
if (!fs5.existsSync(source))
|
|
693
|
+
throw new Error(`image_edit: source image not found: ${source}`);
|
|
694
|
+
const title = `Editing image: ${args.prompt.slice(0, 60)}`;
|
|
695
|
+
await toolCtx.progress({ title });
|
|
696
|
+
const { signal, clear } = withTimeout(toolCtx.signal, options.timeoutMs);
|
|
697
|
+
try {
|
|
698
|
+
const upload = await toPngBuffer(source, options.maxSize);
|
|
699
|
+
const remote = await editImage(options, upload, `${path5.basename(source, path5.extname(source))}.png`, args.prompt, signal);
|
|
700
|
+
const raw = await fetchImageBytes(remote, signal);
|
|
701
|
+
const destination = resolveDestination(args.filename, options, directory);
|
|
702
|
+
await persistImage(raw, destination, options.maxSize);
|
|
703
|
+
return await render("Image edited", args.prompt, destination, (m) => void toolCtx.progress({ title: m }), remote.usage);
|
|
704
|
+
} finally {
|
|
705
|
+
clear();
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
tools.add({
|
|
710
|
+
name: "image_cache",
|
|
711
|
+
description: CACHE_DESCRIPTION,
|
|
712
|
+
input: CacheInput,
|
|
713
|
+
options: { codemode: false },
|
|
699
714
|
execute: async (args) => {
|
|
700
715
|
if (args.action === "clean") {
|
|
701
716
|
const removed = server.cleanCache();
|
|
702
717
|
return {
|
|
703
|
-
|
|
704
|
-
output: [
|
|
718
|
+
content: [
|
|
705
719
|
`已清理 ${removed.count} 个预览文件,共 ${humanBytes(removed.bytes)}。`,
|
|
706
720
|
`缓存目录:${removed.dir}`,
|
|
707
721
|
`注意:这不影响 outDir 里的原图,会话里的图片仍能正常显示(需要时会重新生成缩略图)。`
|
|
@@ -712,8 +726,7 @@ var OpenCodeImagePlugin = async (_input, rawOptions) => {
|
|
|
712
726
|
}
|
|
713
727
|
const stats = server.cacheStats();
|
|
714
728
|
return {
|
|
715
|
-
|
|
716
|
-
output: [
|
|
729
|
+
content: [
|
|
717
730
|
`缓存目录:${stats.dir}`,
|
|
718
731
|
`文件数量:${stats.count}`,
|
|
719
732
|
`占用大小:${humanBytes(stats.bytes)}`,
|
|
@@ -724,15 +737,17 @@ var OpenCodeImagePlugin = async (_input, rawOptions) => {
|
|
|
724
737
|
metadata: stats
|
|
725
738
|
};
|
|
726
739
|
}
|
|
727
|
-
})
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
if (options.injectPrompt)
|
|
743
|
+
await ctx.session.hook("context", (event) => {
|
|
744
|
+
event.system.push({ type: "text", text: SYSTEM_RULE });
|
|
745
|
+
});
|
|
746
|
+
return () => server.stop();
|
|
747
|
+
}
|
|
748
|
+
});
|
|
734
749
|
var src_default = OpenCodeImagePlugin;
|
|
735
750
|
export {
|
|
736
|
-
|
|
737
|
-
|
|
751
|
+
OpenCodeImagePlugin,
|
|
752
|
+
src_default as default
|
|
738
753
|
};
|
package/dist/options.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export type PluginOptions = {
|
|
|
30
30
|
serverIdleMs?: number;
|
|
31
31
|
/** 首次出图时查一次 npm 最新版,落后则在工具输出末尾附升级提示。默认开启 */
|
|
32
32
|
updateCheck?: boolean;
|
|
33
|
+
/** 往系统提示里注入一条"用哪个工具生图"的规则。默认开启 */
|
|
34
|
+
injectPrompt?: boolean;
|
|
33
35
|
/** 对话里展示用的缩略图缓存目录,默认 <系统临时目录>/opencode-image */
|
|
34
36
|
cacheDir?: string;
|
|
35
37
|
/** 缩略图缓存最多保留多少个文件,超出按最久未访问淘汰;0 表示不限制 */
|
|
@@ -56,6 +58,7 @@ export type ResolvedOptions = {
|
|
|
56
58
|
serverPort: number;
|
|
57
59
|
serverIdleMs: number;
|
|
58
60
|
updateCheck: boolean;
|
|
61
|
+
injectPrompt: boolean;
|
|
59
62
|
cacheDir: string;
|
|
60
63
|
cacheLimit: number;
|
|
61
64
|
timeoutMs: number;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-image",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Image generation and editing tools for opencode, with inline rendering in the conversation.",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Image generation and editing tools for opencode (v2 plugin API), with inline rendering in the conversation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"registry": "https://registry.npmjs.org/",
|
|
26
|
-
"access": "public"
|
|
26
|
+
"access": "public",
|
|
27
|
+
"tag": "next"
|
|
27
28
|
},
|
|
28
29
|
"exports": {
|
|
29
30
|
".": {
|
|
@@ -39,25 +40,31 @@
|
|
|
39
40
|
],
|
|
40
41
|
"scripts": {
|
|
41
42
|
"build": "bun run build:js && bun run build:types",
|
|
42
|
-
"build:js": "bun build src/index.ts --target=node --format=esm --external sharp --external @opencode
|
|
43
|
+
"build:js": "bun build src/index.ts --target=node --format=esm --external sharp --external @opencode/plugin --external effect --outfile=dist/index.js",
|
|
43
44
|
"build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
44
45
|
"typecheck": "tsc --noEmit",
|
|
45
46
|
"test": "bun test",
|
|
46
47
|
"prepublishOnly": "bun run build && bun test",
|
|
47
|
-
"release": "npm
|
|
48
|
+
"release": "npm publish && rm -rf ~/.cache/opencode/packages/opencode-image*",
|
|
49
|
+
"release:legacy": "npm version patch && npm publish --tag latest"
|
|
48
50
|
},
|
|
49
51
|
"engines": {
|
|
50
52
|
"node": ">=18"
|
|
51
53
|
},
|
|
52
54
|
"dependencies": {
|
|
53
|
-
"@opencode-ai/plugin": "^1.18.30",
|
|
54
55
|
"sharp": "^0.35.4"
|
|
55
56
|
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"@opencode/plugin": ">=2.0.27",
|
|
59
|
+
"effect": "^4.0.0"
|
|
60
|
+
},
|
|
56
61
|
"devDependencies": {
|
|
62
|
+
"@opencode/plugin": "2.0.27",
|
|
57
63
|
"@types/node": "^24.12.2",
|
|
64
|
+
"effect": "4.0.1",
|
|
58
65
|
"typescript": "^5.8.2"
|
|
59
66
|
},
|
|
60
67
|
"bin": {
|
|
61
|
-
"opencode-image": "
|
|
68
|
+
"opencode-image": "bin/cli.js"
|
|
62
69
|
}
|
|
63
70
|
}
|