opencode-github-release 0.5.0 → 0.6.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 CHANGED
@@ -8,7 +8,15 @@ An [OpenCode](https://opencode.ai) plugin that creates git tags and publishes Gi
8
8
 
9
9
  ## Installation
10
10
 
11
- Add to your `opencode.json`:
11
+ For OpenCode V2, add to your `opencode.json`:
12
+
13
+ ```json
14
+ {
15
+ "plugins": ["opencode-github-release"]
16
+ }
17
+ ```
18
+
19
+ For OpenCode V1 (1.18.29 or newer), use its V1 configuration key instead:
12
20
 
13
21
  ```json
14
22
  {
@@ -93,12 +101,14 @@ There are commit skills such as (https://github.com/PedroHBO/opencode-config-ski
93
101
  - [GitHub CLI](https://cli.github.com/) (`gh`) authenticated to your account
94
102
  - The current directory must be a git repository with a remote named `origin`
95
103
 
104
+ Git commands run in the session's working directory. Release creation pushes commits and tags to `origin` and publishes through `gh`; verify the proposed version before proceeding.
105
+
96
106
  ## Development
97
107
 
98
108
  ```bash
99
109
  npm install
100
110
  npm run typecheck
101
- npm run build
111
+ npm test
102
112
  npm pack --dry-run
103
113
  ```
104
114
 
@@ -0,0 +1,195 @@
1
+ // src/release.ts
2
+ import { execFile } from "child_process";
3
+ import { existsSync } from "fs";
4
+ import { join } from "path";
5
+ import { promisify } from "util";
6
+ var exec = promisify(execFile);
7
+ function runner(directory, signal) {
8
+ return async (command, args) => {
9
+ const { stdout } = await exec(command, args, {
10
+ cwd: directory,
11
+ signal,
12
+ maxBuffer: 10 * 1024 * 1024
13
+ });
14
+ return stdout.trim();
15
+ };
16
+ }
17
+ async function optional(run, command, args, fallback = "") {
18
+ try {
19
+ return await run(command, args);
20
+ } catch {
21
+ return fallback;
22
+ }
23
+ }
24
+ function bumpVersion(current, bump) {
25
+ const prefix = current.startsWith("v") ? "v" : "";
26
+ const cleaned = current.replace(/^v/, "");
27
+ const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/);
28
+ if (!match) throw new Error(`Cannot parse semver from "${current}"`);
29
+ let major = parseInt(match[1], 10);
30
+ let minor = parseInt(match[2], 10);
31
+ let patch = parseInt(match[3], 10);
32
+ if (bump === "major") {
33
+ major++;
34
+ minor = 0;
35
+ patch = 0;
36
+ }
37
+ if (bump === "minor") {
38
+ minor++;
39
+ patch = 0;
40
+ }
41
+ if (bump === "patch") {
42
+ patch++;
43
+ }
44
+ return `${prefix}${major}.${minor}.${patch}`;
45
+ }
46
+ function classifyCommit(subject, body) {
47
+ const breaking = /^\w+(\([^)]*\))?!:/.test(subject) || /^BREAKING(?: CHANGE|-CHANGE):[ \t]+\S/m.test(body);
48
+ const match = subject.match(/^(\w+)(\([^)]*\))?(!)?\s*:/);
49
+ const type = match?.[1]?.toLowerCase();
50
+ if (type === "feat") return { type: "feat", breaking };
51
+ if (type === "fix") return { type: "fix", breaking };
52
+ return { type: "other", breaking };
53
+ }
54
+ async function latestTag(run) {
55
+ await optional(run, "git", ["fetch", "--tags", "--force"]);
56
+ return optional(run, "git", ["describe", "--tags", "--abbrev=0"], "v0.0.0");
57
+ }
58
+ async function suggestBump(run, progress) {
59
+ await progress("Fetching tags\u2026");
60
+ const tag = await latestTag(run);
61
+ const logText = await optional(run, "git", ["log", `${tag}..HEAD`, "-z", "--format=%h%x00%s%x00%b"]);
62
+ if (!logText) {
63
+ return { title: "No new commits", output: `No new commits since ${tag}. No release needed.` };
64
+ }
65
+ const fields = logText.split("\0");
66
+ fields.pop();
67
+ const entries = [];
68
+ for (let i = 0; i < fields.length; i += 3) {
69
+ const [hash, subject, body] = fields.slice(i, i + 3);
70
+ entries.push({ hash, subject, ...classifyCommit(subject, body) });
71
+ }
72
+ let suggestedBump = "patch";
73
+ for (const entry of entries) {
74
+ if (entry.breaking) {
75
+ suggestedBump = "major";
76
+ break;
77
+ }
78
+ if (entry.type === "feat") {
79
+ suggestedBump = "minor";
80
+ }
81
+ }
82
+ const output = entries.map((e) => {
83
+ const tag2 = e.breaking ? "[BREAKING]" : e.type === "feat" ? "[feat]" : e.type === "fix" ? "[fix]" : " ";
84
+ return ` ${tag2} ${e.hash} ${e.subject}`;
85
+ }).join("\n");
86
+ return {
87
+ title: "Bump suggestion",
88
+ output: [
89
+ `Latest tag: ${tag}`,
90
+ `Commits: ${entries.length}`,
91
+ "",
92
+ output,
93
+ "",
94
+ `Suggested bump: ${suggestedBump} -> ${bumpVersion(tag, suggestedBump)}`
95
+ ].join("\n")
96
+ };
97
+ }
98
+ async function createRelease(args, directory, run, progress) {
99
+ const { bump, version, notes, force } = args;
100
+ if (!bump && !version) {
101
+ throw new Error("Provide either `bump` (patch/minor/major) or an explicit `version` string");
102
+ }
103
+ await progress("Checking working tree\u2026");
104
+ const status = await run("git", ["status", "--porcelain"]);
105
+ if (status && !force) {
106
+ const count = status.split("\n").length;
107
+ return {
108
+ title: "Uncommitted files",
109
+ output: `${count} uncommitted file(s) detected. Call create_release with force: true to proceed anyway, or commit/stash first.`
110
+ };
111
+ }
112
+ await progress("Fetching tags\u2026");
113
+ const tag = await latestTag(run);
114
+ const repoUsesV = tag.startsWith("v");
115
+ const hasExistingTags = await run("git", ["tag", "-l"]) !== "";
116
+ let newTag;
117
+ if (version) {
118
+ const versionHasV = version.startsWith("v");
119
+ if (hasExistingTags && versionHasV !== repoUsesV) {
120
+ const suggestion = versionHasV ? version.replace(/^v/, "") : `v${version}`;
121
+ return {
122
+ title: "Version prefix mismatch",
123
+ output: [
124
+ `Existing releases use ${repoUsesV ? 'the "v" prefix' : 'no "v" prefix'} (e.g. "${tag}"),`,
125
+ `but you provided "${version}" which ${versionHasV ? "has" : "does not have"} a "v" prefix.`,
126
+ "",
127
+ `Would you like to use "${suggestion}" instead?`,
128
+ "If so, call create_release again with the corrected version."
129
+ ].join("\n")
130
+ };
131
+ }
132
+ newTag = version;
133
+ } else {
134
+ newTag = bumpVersion(tag, bump);
135
+ }
136
+ await progress(`Bumping to ${newTag}\u2026`);
137
+ const branch = await run("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
138
+ let unpushedBefore = [];
139
+ if (branch !== "HEAD") {
140
+ const before = await optional(run, "git", ["log", `origin/${branch}..HEAD`, "--oneline"]);
141
+ if (before) {
142
+ unpushedBefore = before.split("\n").map((line) => {
143
+ const hash = line.split(/\s+/)[0];
144
+ return { hash, subject: line.slice(hash.length).trim() };
145
+ });
146
+ }
147
+ }
148
+ if (existsSync(join(directory, "package.json"))) {
149
+ const bareVersion = newTag.replace(/^v/, "");
150
+ await run("npm", ["version", bareVersion, "--no-git-tag-version"]);
151
+ await optional(run, "git", ["add", "package.json", "package-lock.json"]);
152
+ await run("git", ["commit", "-m", `chore(release): bump version to ${newTag}`]);
153
+ }
154
+ if (branch !== "HEAD") {
155
+ await progress("Pushing commits\u2026");
156
+ await run("git", ["push", "origin", branch]);
157
+ }
158
+ await progress("Tagging release\u2026");
159
+ await run("git", ["tag", "-a", newTag, "-m", notes || `Release ${newTag}`]);
160
+ await run("git", ["push", "origin", newTag]);
161
+ await progress("Creating GitHub release\u2026");
162
+ if (notes) {
163
+ await run("gh", ["release", "create", newTag, "--title", newTag, "--notes", notes]);
164
+ } else {
165
+ await run("gh", ["release", "create", newTag, "--title", newTag, "--generate-notes"]);
166
+ }
167
+ let result = `Created and published ${newTag} (bumped from ${tag})`;
168
+ if (branch !== "HEAD" && unpushedBefore.length > 0) {
169
+ const remaining = await optional(run, "git", ["log", `origin/${branch}..HEAD`, "--oneline"]);
170
+ const remainingCount = remaining ? remaining.split("\n").length : 0;
171
+ const pushedCount = unpushedBefore.length - remainingCount;
172
+ if (pushedCount > 0) {
173
+ const plural = pushedCount === 1 ? "" : "s";
174
+ result += `
175
+ Pushed ${pushedCount} commit${plural} to ${branch}:`;
176
+ for (let i = 0; i < pushedCount; i++) {
177
+ result += `
178
+ ${unpushedBefore[i].hash} ${unpushedBefore[i].subject}`;
179
+ }
180
+ }
181
+ if (remainingCount > 0) {
182
+ const plural = remainingCount === 1 ? "" : "s";
183
+ const verb = remainingCount === 1 ? "is" : "are";
184
+ result += `
185
+ Note: ${remainingCount} commit${plural} in this release ${verb} not yet pushed to origin/${branch}.`;
186
+ }
187
+ }
188
+ return { title: newTag, output: result };
189
+ }
190
+
191
+ export {
192
+ runner,
193
+ suggestBump,
194
+ createRelease
195
+ };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,10 @@
1
+ import { Plugin as Plugin$1 } from '@opencode/plugin';
1
2
  import { Plugin } from '@opencode-ai/plugin';
2
3
 
3
- declare const plugin: Plugin;
4
+ declare const _default: {
5
+ server: Plugin;
6
+ id: string;
7
+ setup: (context: Plugin$1.Context) => Promise<Plugin$1.Cleanup | void> | Plugin$1.Cleanup | void;
8
+ };
4
9
 
5
- export { plugin as default };
10
+ export { _default as default };
package/dist/index.js CHANGED
@@ -1,196 +1,84 @@
1
+ import {
2
+ createRelease,
3
+ runner,
4
+ suggestBump
5
+ } from "./chunk-G33QYB47.js";
6
+
1
7
  // src/index.ts
8
+ import { Plugin } from "@opencode/plugin";
2
9
  import { tool } from "@opencode-ai/plugin";
3
- function bumpVersion(current, bump) {
4
- const prefix = current.startsWith("v") ? "v" : "";
5
- const cleaned = current.replace(/^v/, "");
6
- const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/);
7
- if (!match) throw new Error(`Cannot parse semver from "${current}"`);
8
- let major = parseInt(match[1], 10);
9
- let minor = parseInt(match[2], 10);
10
- let patch = parseInt(match[3], 10);
11
- if (bump === "major") {
12
- major++;
13
- minor = 0;
14
- patch = 0;
10
+ var suggestDescription = "Analyze git history since the latest tag and suggest a semantic version bump. Call this tool when the user asks to create a release but does NOT specify patch/minor/major or an explicit version string. After receiving the suggestion, present it to the user and ask for confirmation before calling create_release.";
11
+ var releaseDescription = 'Create a git tag and publish a GitHub release with semantic versioning. Provide either `bump` to auto-compute the next version from the latest tag, or an explicit `version` string (e.g. "2.0.0" or "v2.0.0"). If the user only asks to "create a release" without specifying a bump or version, call suggest_bump first instead of this tool.';
12
+ var v1 = async () => ({
13
+ tool: {
14
+ suggest_bump: tool({
15
+ description: suggestDescription,
16
+ args: {},
17
+ async execute(_args, context) {
18
+ return suggestBump(runner(context.directory, context.abort), (title) => context.metadata({ title }));
19
+ }
20
+ }),
21
+ create_release: tool({
22
+ description: releaseDescription,
23
+ args: {
24
+ bump: tool.schema.enum(["patch", "minor", "major"]).optional(),
25
+ version: tool.schema.string().optional(),
26
+ notes: tool.schema.string().optional(),
27
+ force: tool.schema.boolean().optional()
28
+ },
29
+ async execute(args, context) {
30
+ return createRelease(args, context.directory, runner(context.directory, context.abort), (title) => context.metadata({ title }));
31
+ }
32
+ })
15
33
  }
16
- if (bump === "minor") {
17
- minor++;
18
- patch = 0;
19
- }
20
- if (bump === "patch") {
21
- patch++;
22
- }
23
- return `${prefix}${major}.${minor}.${patch}`;
24
- }
25
- function classifyCommit(subject) {
26
- const breaking = /^BREAKING CHANGE:/im.test(subject) || /^\w+(\([^)]*\))?!:/m.test(subject);
27
- const match = subject.match(/^(\w+)(\([^)]*\))?(!)?\s*:/);
28
- const type = match?.[1]?.toLowerCase();
29
- if (type === "feat") return { type: "feat", breaking };
30
- if (type === "fix") return { type: "fix", breaking };
31
- return { type: "other", breaking };
32
- }
33
- var plugin = async ({ $ }) => {
34
- return {
35
- tool: {
36
- suggest_bump: tool({
37
- description: "Analyze git history since the latest tag and suggest a semantic version bump. Call this tool when the user asks to create a release but does NOT specify patch/minor/major or an explicit version string. After receiving the suggestion, present it to the user and ask for confirmation before calling create_release.",
38
- args: {},
39
- async execute(_args, context) {
40
- context.metadata({ title: "Fetching tags\u2026" });
41
- await $`git fetch --tags --force 2>/dev/null || true`.quiet();
42
- const tagResult = await $`git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0"`.text();
43
- const latestTag = tagResult.trim();
44
- const logText = await $`git log ${latestTag}..HEAD --oneline 2>/dev/null || true`.text();
45
- if (!logText) {
46
- return {
47
- title: "No new commits",
48
- output: `No new commits since ${latestTag}. No release needed.`
49
- };
50
- }
51
- const lines = logText.split("\n");
52
- const entries = lines.map((line) => {
53
- const hash = line.split(/\s+/)[0];
54
- const subject = line.slice(hash.length).trim();
55
- const { type, breaking } = classifyCommit(subject);
56
- return { hash, subject, type, breaking };
57
- });
58
- let suggestedBump = "patch";
59
- for (const entry of entries) {
60
- if (entry.breaking) {
61
- suggestedBump = "major";
62
- break;
63
- }
64
- if (entry.type === "feat") {
65
- suggestedBump = "minor";
66
- }
67
- }
68
- const output = entries.map((e) => {
69
- const tag = e.breaking ? "[BREAKING]" : e.type === "feat" ? "[feat]" : e.type === "fix" ? "[fix]" : " ";
70
- return ` ${tag} ${e.hash} ${e.subject}`;
71
- }).join("\n");
72
- return {
73
- title: "Bump suggestion",
74
- output: [
75
- `Latest tag: ${latestTag}`,
76
- `Commits: ${entries.length}`,
77
- "",
78
- output,
79
- "",
80
- `Suggested bump: ${suggestedBump} -> ${bumpVersion(latestTag, suggestedBump)}`
81
- ].join("\n")
82
- };
83
- }
84
- }),
85
- create_release: tool({
86
- description: 'Create a git tag and publish a GitHub release with semantic versioning. Provide either `bump` to auto-compute the next version from the latest tag, or an explicit `version` string (e.g. "2.0.0" or "v2.0.0"). If the user only asks to "create a release" without specifying a bump or version, call suggest_bump first instead of this tool.',
87
- args: {
88
- bump: tool.schema.enum(["patch", "minor", "major"]).optional(),
89
- version: tool.schema.string().optional(),
90
- notes: tool.schema.string().optional(),
91
- force: tool.schema.boolean().optional()
92
- },
93
- async execute(args, context) {
94
- const { bump, version, notes, force } = args;
95
- if (!bump && !version) {
96
- throw new Error("Provide either `bump` (patch/minor/major) or an explicit `version` string");
97
- }
98
- context.metadata({ title: "Checking working tree\u2026" });
99
- const status = (await $`git status --porcelain`.text()).trim();
100
- if (status && !force) {
101
- const count = status.split("\n").length;
102
- return {
103
- title: "Uncommitted files",
104
- output: `${count} uncommitted file(s) detected. Call create_release with force: true to proceed anyway, or commit/stash first.`
105
- };
106
- }
107
- context.metadata({ title: "Fetching tags\u2026" });
108
- await $`git fetch --tags --force 2>/dev/null || true`.quiet();
109
- const tagResult = await $`git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0"`.text();
110
- const latestTag = tagResult.trim();
111
- const repoUsesV = latestTag.startsWith("v");
112
- const hasExistingTags = parseInt((await $`git tag -l 2>/dev/null | wc -l`.text()).trim(), 10) > 0;
113
- let newTag;
114
- if (version) {
115
- const versionHasV = version.startsWith("v");
116
- if (hasExistingTags && versionHasV !== repoUsesV) {
117
- const suggestion = versionHasV ? version.replace(/^v/, "") : `v${version}`;
118
- return {
119
- title: "Version prefix mismatch",
120
- output: [
121
- `Existing releases use ${repoUsesV ? 'the "v" prefix' : 'no "v" prefix'} (e.g. "${latestTag}"),`,
122
- `but you provided "${version}" which ${versionHasV ? "has" : "does not have"} a "v" prefix.`,
123
- "",
124
- `Would you like to use "${suggestion}" instead?`,
125
- "If so, call create_release again with the corrected version."
126
- ].join("\n")
127
- };
128
- }
129
- newTag = version;
130
- } else {
131
- newTag = bumpVersion(latestTag, bump);
132
- }
133
- context.metadata({ title: `Bumping to ${newTag}\u2026` });
134
- const branch = (await $`git rev-parse --abbrev-ref HEAD`.text()).trim();
135
- let unpushedBefore = [];
136
- if (branch !== "HEAD") {
137
- const before = (await $`git log origin/${branch}..HEAD --oneline 2>/dev/null || true`.text()).trim();
138
- if (before) {
139
- unpushedBefore = before.split("\n").map((line) => {
140
- const hash = line.split(/\s+/)[0];
141
- return { hash, subject: line.slice(hash.length).trim() };
142
- });
143
- }
144
- }
145
- const hasPkg = (await $`test -f package.json && echo "yes" || echo "no"`.text()).trim() === "yes";
146
- if (hasPkg) {
147
- const bareVersion = newTag.replace(/^v/, "");
148
- await $`npm version ${bareVersion} --no-git-tag-version`.quiet();
149
- await $`git add package.json package-lock.json 2>/dev/null || true`.quiet();
150
- await $`git commit -m ${`chore(release): bump version to ${newTag}`}`.quiet();
151
- }
152
- if (branch !== "HEAD") {
153
- context.metadata({ title: "Pushing commits\u2026" });
154
- await $`git push origin ${branch}`.quiet();
155
- }
156
- context.metadata({ title: "Tagging release\u2026" });
157
- const message = notes || `Release ${newTag}`;
158
- await $`git tag -a ${newTag} -m ${message}`.quiet();
159
- await $`git push origin ${newTag}`.quiet();
160
- context.metadata({ title: "Creating GitHub release\u2026" });
161
- if (notes) {
162
- await $`gh release create ${newTag} --title ${newTag} --notes ${notes}`.quiet();
163
- } else {
164
- await $`gh release create ${newTag} --title ${newTag} --generate-notes`.quiet();
34
+ });
35
+ var index_default = {
36
+ ...Plugin.define({
37
+ id: "opencode-github-release",
38
+ async setup(ctx) {
39
+ await ctx.tool.transform((editor) => {
40
+ editor.add({
41
+ name: "suggest_bump",
42
+ description: suggestDescription,
43
+ input: { type: "object", properties: {}, additionalProperties: false },
44
+ async execute(_input, context) {
45
+ const session = await ctx.session.get({ sessionID: context.sessionID });
46
+ const result = await suggestBump(
47
+ runner(session.location.directory, context.signal),
48
+ (title) => context.progress({ status: title })
49
+ );
50
+ return { content: result.output, metadata: { title: result.title } };
165
51
  }
166
- let result = `Created and published ${newTag} (bumped from ${latestTag})`;
167
- if (branch !== "HEAD" && unpushedBefore.length > 0) {
168
- const remaining = (await $`git log origin/${branch}..HEAD --oneline 2>/dev/null || true`.text()).trim();
169
- const remainingCount = remaining ? remaining.split("\n").length : 0;
170
- const pushedCount = unpushedBefore.length - remainingCount;
171
- if (pushedCount > 0) {
172
- const plural = pushedCount === 1 ? "" : "s";
173
- result += `
174
- Pushed ${pushedCount} commit${plural} to ${branch}:`;
175
- for (let i = 0; i < pushedCount; i++) {
176
- result += `
177
- ${unpushedBefore[i].hash} ${unpushedBefore[i].subject}`;
178
- }
179
- }
180
- if (remainingCount > 0) {
181
- const plural = remainingCount === 1 ? "" : "s";
182
- const verb = remainingCount === 1 ? "is" : "are";
183
- result += `
184
- Note: ${remainingCount} commit${plural} in this release ${verb} not yet pushed to origin/${branch}.`;
185
- }
52
+ });
53
+ editor.add({
54
+ name: "create_release",
55
+ description: releaseDescription,
56
+ input: {
57
+ type: "object",
58
+ properties: {
59
+ bump: { type: "string", enum: ["patch", "minor", "major"] },
60
+ version: { type: "string" },
61
+ notes: { type: "string" },
62
+ force: { type: "boolean" }
63
+ },
64
+ additionalProperties: false
65
+ },
66
+ async execute(input, context) {
67
+ const session = await ctx.session.get({ sessionID: context.sessionID });
68
+ const result = await createRelease(
69
+ input,
70
+ session.location.directory,
71
+ runner(session.location.directory, context.signal),
72
+ (title) => context.progress({ status: title })
73
+ );
74
+ return { content: result.output, metadata: { title: result.title } };
186
75
  }
187
- return { title: newTag, output: result };
188
- }
189
- })
76
+ });
77
+ });
190
78
  }
191
- };
79
+ }),
80
+ server: v1
192
81
  };
193
- var index_default = plugin;
194
82
  export {
195
83
  index_default as default
196
84
  };
@@ -0,0 +1,18 @@
1
+ type Bump = "patch" | "minor" | "major";
2
+ type Result = {
3
+ title: string;
4
+ output: string;
5
+ };
6
+ type Runner = (command: string, args: string[]) => Promise<string>;
7
+ type Progress = (title: string) => Promise<void> | void;
8
+ declare function runner(directory: string, signal?: AbortSignal): Runner;
9
+ declare function suggestBump(run: Runner, progress: Progress): Promise<Result>;
10
+ type ReleaseArgs = {
11
+ bump?: Bump;
12
+ version?: string;
13
+ notes?: string;
14
+ force?: boolean;
15
+ };
16
+ declare function createRelease(args: ReleaseArgs, directory: string, run: Runner, progress: Progress): Promise<Result>;
17
+
18
+ export { type Bump, type Progress, type ReleaseArgs, type Result, type Runner, createRelease, runner, suggestBump };
@@ -0,0 +1,10 @@
1
+ import {
2
+ createRelease,
3
+ runner,
4
+ suggestBump
5
+ } from "./chunk-G33QYB47.js";
6
+ export {
7
+ createRelease,
8
+ runner,
9
+ suggestBump
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-github-release",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "OpenCode plugin to create git tags and publish GitHub releases with semantic versioning.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,10 +21,11 @@
21
21
  "LICENSE"
22
22
  ],
23
23
  "scripts": {
24
- "build": "tsup src/index.ts --format esm --dts --clean",
25
- "dev": "tsup src/index.ts --format esm --dts --watch",
24
+ "build": "tsup src/index.ts src/release.ts --format esm --dts --clean",
25
+ "dev": "tsup src/index.ts src/release.ts --format esm --dts --watch",
26
26
  "typecheck": "tsc --noEmit",
27
- "check": "npm run typecheck && npm run build && npm pack --dry-run",
27
+ "test": "npm run build && node --test test/*.test.js",
28
+ "check": "npm run typecheck && npm run test && npm pack --dry-run",
28
29
  "prepublishOnly": "npm run check"
29
30
  },
30
31
  "keywords": [
@@ -38,7 +39,8 @@
38
39
  "access": "public"
39
40
  },
40
41
  "dependencies": {
41
- "@opencode-ai/plugin": "^1.14.41"
42
+ "@opencode-ai/plugin": "^1.18.29",
43
+ "@opencode/plugin": "^2.0.24"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@types/node": "^25.0.10",