feat: add dedicated issue close/reopen and PR review tools

This commit is contained in:
2026-05-12 13:11:12 +02:00
parent bb2fd20992
commit e8fa47a57d
5 changed files with 546 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
import { tool } from "@opencode-ai/plugin";
import { $ } from "bun";
export const issueClose = tool({
description: "Close one or more issues",
args: {
issueNumbers: tool.schema
.array(tool.schema.number())
.describe("Issue number(s) to close"),
comment: tool.schema
.string()
.optional()
.describe("Optional comment explaining why the issue is being closed"),
},
async execute(args) {
try {
const issueListStr = args.issueNumbers.join(" ");
let cmd = $`tea issues close --output json ${issueListStr}`;
if (args.comment) {
cmd = cmd`--comment ${args.comment}`;
}
const result = await cmd.text();
return {
success: true,
message: `Successfully closed issue(s) #${args.issueNumbers.join(", ")}`,
issues: args.issueNumbers,
output: result,
};
} catch (error: any) {
return {
error: "Failed to close issue(s)",
message: error.message,
stderr: error.stderr?.toString(),
issues: args.issueNumbers,
};
}
},
});
export const issueReopen = tool({
description: "Reopen one or more closed issues",
args: {
issueNumbers: tool.schema
.array(tool.schema.number())
.describe("Issue number(s) to reopen"),
comment: tool.schema
.string()
.optional()
.describe("Optional comment explaining why the issue is being reopened"),
},
async execute(args) {
try {
const issueListStr = args.issueNumbers.join(" ");
let cmd = $`tea issues reopen --output json ${issueListStr}`;
if (args.comment) {
cmd = cmd`--comment ${args.comment}`;
}
const result = await cmd.text();
return {
success: true,
message: `Successfully reopened issue(s) #${args.issueNumbers.join(", ")}`,
issues: args.issueNumbers,
output: result,
};
} catch (error: any) {
return {
error: "Failed to reopen issue(s)",
message: error.message,
stderr: error.stderr?.toString(),
issues: args.issueNumbers,
};
}
},
});

View File

@@ -0,0 +1,155 @@
import { tool } from "@opencode-ai/plugin";
import { $ } from "bun";
export const issueUpdateTitle = tool({
description: "Update the title of an issue",
args: {
issueNumber: tool.schema.number().describe("Issue number"),
title: tool.schema.string().describe("New title"),
},
async execute(args) {
try {
const result = await $`tea issues edit --output json --title ${args.title} ${args.issueNumber}`.text();
return {
success: true,
message: `Successfully updated title for issue #${args.issueNumber}`,
issueNumber: args.issueNumber,
output: result,
};
} catch (error: any) {
return {
error: "Failed to update issue title",
message: error.message,
stderr: error.stderr?.toString(),
issueNumber: args.issueNumber,
};
}
},
});
export const issueUpdateDescription = tool({
description: "Update the description/body of an issue",
args: {
issueNumber: tool.schema.number().describe("Issue number"),
description: tool.schema.string().describe("New description/body"),
},
async execute(args) {
try {
const result = await $`tea issues edit --output json --description ${args.description} ${args.issueNumber}`.text();
return {
success: true,
message: `Successfully updated description for issue #${args.issueNumber}`,
issueNumber: args.issueNumber,
output: result,
};
} catch (error: any) {
return {
error: "Failed to update issue description",
message: error.message,
stderr: error.stderr?.toString(),
issueNumber: args.issueNumber,
};
}
},
});
export const issueAddLabels = tool({
description: "Add labels to one or more issues",
args: {
issueNumbers: tool.schema
.array(tool.schema.number())
.describe("Issue number(s) to add labels to"),
labels: tool.schema
.array(tool.schema.string())
.describe("Label names to add"),
},
async execute(args) {
try {
const issueListStr = args.issueNumbers.join(" ");
const labelsStr = args.labels.join(",");
const result = await $`tea issues edit --output json --add-labels ${labelsStr} ${issueListStr}`.text();
return {
success: true,
message: `Successfully added label(s) "${labelsStr}" to issue(s) #${args.issueNumbers.join(", ")}`,
issueNumbers: args.issueNumbers,
labels: args.labels,
output: result,
};
} catch (error: any) {
return {
error: "Failed to add labels to issue(s)",
message: error.message,
stderr: error.stderr?.toString(),
issueNumbers: args.issueNumbers,
labels: args.labels,
};
}
},
});
export const issueRemoveLabels = tool({
description: "Remove labels from one or more issues",
args: {
issueNumbers: tool.schema
.array(tool.schema.number())
.describe("Issue number(s) to remove labels from"),
labels: tool.schema
.array(tool.schema.string())
.describe("Label names to remove"),
},
async execute(args) {
try {
const issueListStr = args.issueNumbers.join(" ");
const labelsStr = args.labels.join(",");
const result = await $`tea issues edit --output json --remove-labels ${labelsStr} ${issueListStr}`.text();
return {
success: true,
message: `Successfully removed label(s) "${labelsStr}" from issue(s) #${args.issueNumbers.join(", ")}`,
issueNumbers: args.issueNumbers,
labels: args.labels,
output: result,
};
} catch (error: any) {
return {
error: "Failed to remove labels from issue(s)",
message: error.message,
stderr: error.stderr?.toString(),
issueNumbers: args.issueNumbers,
labels: args.labels,
};
}
},
});
export const issueAddComment = tool({
description: "Add a comment to an issue",
args: {
issueNumber: tool.schema.number().describe("Issue number"),
comment: tool.schema.string().describe("Comment text"),
},
async execute(args) {
try {
const result = await $`tea issue comment --output json --issue ${args.issueNumber} --body ${args.comment}`.text();
return {
success: true,
message: `Successfully added comment to issue #${args.issueNumber}`,
issueNumber: args.issueNumber,
output: result,
};
} catch (error: any) {
return {
error: "Failed to add comment to issue",
message: error.message,
stderr: error.stderr?.toString(),
issueNumber: args.issueNumber,
};
}
},
});

View File

@@ -0,0 +1,100 @@
import { tool } from "@opencode-ai/plugin";
import { $ } from "bun";
export const labelCreate = tool({
description: "Create a new label with name, color, and optional description",
args: {
name: tool.schema.string().describe("Label name"),
color: tool.schema.string().describe("Hex color code (e.g., #e99695)"),
description: tool.schema
.string()
.optional()
.describe("Label description"),
},
async execute(args) {
try {
let cmd = $`tea label create --output json --name ${args.name} --color ${args.color}`;
if (args.description) {
cmd = cmd`--description ${args.description}`;
}
const result = await cmd.text();
return result;
} catch (error: any) {
return {
error: "Failed to create label",
message: error.message,
stderr: error.stderr?.toString(),
};
}
},
});
export const labelUpdate = tool({
description: "Update an existing label's name, color, or description",
args: {
labelName: tool.schema.string().describe("Current label name"),
newName: tool.schema
.string()
.optional()
.describe("New label name"),
newColor: tool.schema
.string()
.optional()
.describe("New hex color code"),
newDescription: tool.schema
.string()
.optional()
.describe("New description"),
},
async execute(args) {
try {
let cmd = $`tea label update --output json --name ${args.labelName}`;
if (args.newName) {
cmd = cmd`--new-name ${args.newName}`;
}
if (args.newColor) {
cmd = cmd`--new-color ${args.newColor}`;
}
if (args.newDescription) {
cmd = cmd`--new-description ${args.newDescription}`;
}
const result = await cmd.text();
return result;
} catch (error: any) {
return {
error: "Failed to update label",
message: error.message,
stderr: error.stderr?.toString(),
};
}
},
});
export const labelDelete = tool({
description: "Delete a label from the repository",
args: {
labelName: tool.schema.string().describe("Label name to delete"),
},
async execute(args) {
try {
const result = await $`tea label delete --output json --name ${args.labelName}`.text();
return {
success: true,
message: `Successfully deleted label "${args.labelName}"`,
output: result,
};
} catch (error: any) {
return {
error: "Failed to delete label",
message: error.message,
stderr: error.stderr?.toString(),
};
}
},
});

View File

@@ -0,0 +1,99 @@
import { tool } from "@opencode-ai/plugin";
import { $ } from "bun";
export const milestoneUpdate = tool({
description: "Update an existing milestone with new title, description, due date, or state",
args: {
milestoneNumber: tool.schema.number().describe("Milestone number"),
title: tool.schema
.string()
.optional()
.describe("New milestone title"),
description: tool.schema
.string()
.optional()
.describe("New milestone description"),
dueDate: tool.schema
.string()
.optional()
.describe("New due date (format: YYYY-MM-DD)"),
state: tool.schema
.enum(["open", "closed"])
.optional()
.describe("New milestone state"),
},
async execute(args) {
try {
let cmd = $`tea milestone edit --output json ${args.milestoneNumber}`;
if (args.title) {
cmd = cmd`--title ${args.title}`;
}
if (args.description) {
cmd = cmd`--description ${args.description}`;
}
if (args.dueDate) {
cmd = cmd`--due-date ${args.dueDate}`;
}
if (args.state) {
cmd = cmd`--state ${args.state}`;
}
const result = await cmd.text();
return {
success: true,
message: `Successfully updated milestone #${args.milestoneNumber}`,
milestoneNumber: args.milestoneNumber,
output: result,
};
} catch (error: any) {
return {
error: "Failed to update milestone",
message: error.message,
stderr: error.stderr?.toString(),
milestoneNumber: args.milestoneNumber,
};
}
},
});
export const milestoneDelete = tool({
description: "Delete a milestone from the repository",
args: {
milestoneNumber: tool.schema.number().describe("Milestone number to delete"),
confirm: tool.schema
.boolean()
.optional()
.describe("Confirmation flag (set to true to confirm deletion)"),
},
async execute(args) {
try {
if (args.confirm !== true) {
return {
error: "Deletion not confirmed",
message: "Please set confirm: true to confirm deletion",
};
}
const result = await $`tea milestone delete --output json ${args.milestoneNumber}`.text();
return {
success: true,
message: `Successfully deleted milestone #${args.milestoneNumber}`,
milestoneNumber: args.milestoneNumber,
output: result,
};
} catch (error: any) {
return {
error: "Failed to delete milestone",
message: error.message,
stderr: error.stderr?.toString(),
milestoneNumber: args.milestoneNumber,
};
}
},
});

View File

@@ -0,0 +1,110 @@
import { tool } from "@opencode-ai/plugin";
import { $ } from "bun";
export const prApprove = tool({
description: "Approve a pull request with an optional comment",
args: {
prNumber: tool.schema.number().describe("Pull request number"),
comment: tool.schema
.string()
.optional()
.describe("Optional review comment"),
},
async execute(args) {
try {
let cmd = $`tea pr review --output json --approve ${args.prNumber}`;
if (args.comment) {
cmd = cmd`--message ${args.comment}`;
}
const result = await cmd.text();
return {
success: true,
message: `Successfully approved PR #${args.prNumber}`,
prNumber: args.prNumber,
output: result,
};
} catch (error: any) {
return {
error: "Failed to approve PR",
message: error.message,
stderr: error.stderr?.toString(),
prNumber: args.prNumber,
};
}
},
});
export const prRequestChanges = tool({
description: "Request changes on a pull request with a comment",
args: {
prNumber: tool.schema.number().describe("Pull request number"),
comment: tool.schema.string().describe("Review comment explaining requested changes"),
},
async execute(args) {
try {
const cmd = $`tea pr review --output json --request-changes ${args.prNumber} --message ${args.comment}`;
const result = await cmd.text();
return {
success: true,
message: `Successfully requested changes on PR #${args.prNumber}`,
prNumber: args.prNumber,
output: result,
};
} catch (error: any) {
return {
error: "Failed to request changes on PR",
message: error.message,
stderr: error.stderr?.toString(),
prNumber: args.prNumber,
};
}
},
});
export const prRequestChangesWithFeedback = tool({
description: "Request changes on a pull request with detailed feedback",
args: {
prNumber: tool.schema.number().describe("Pull request number"),
feedback: tool.schema.string().describe("Detailed feedback explaining requested changes"),
sections: tool.schema
.array(tool.schema.string())
.optional()
.describe("Specific sections requiring changes (e.g., ['authentication', 'tests'])"),
},
async execute(args) {
try {
let feedbackText = args.feedback;
if (args.sections && args.sections.length > 0) {
feedbackText += "\n\nKey areas requiring attention:\n";
for (const section of args.sections) {
feedbackText += `- ${section}\n`;
}
}
const cmd = $`tea pr review --output json --request-changes ${args.prNumber} --message ${feedbackText}`;
const result = await cmd.text();
return {
success: true,
message: `Successfully requested changes on PR #${args.prNumber}`,
prNumber: args.prNumber,
feedback: feedbackText,
output: result,
};
} catch (error: any) {
return {
error: "Failed to request changes on PR",
message: error.message,
stderr: error.stderr?.toString(),
prNumber: args.prNumber,
};
}
},
});