Compare commits

..

7 Commits

7 changed files with 518 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
import { tool } from "@opencode-ai/plugin"
import { $ } from "bun"
export const whoami = tool({
description: "Get current logged-in user info for the Gitea instance",
args: {},
async execute(args, context) {
try {
const result = await $`tea whoami --output json`.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to get current user info",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})

View File

@@ -0,0 +1,110 @@
import { tool } from "@opencode-ai/plugin"
import { $ } from "bun"
export const issueDepsList = tool({
description: "List dependency relationships for an issue (what it blocks and what blocks it)",
args: {
issueNumber: tool.schema.number().describe("Issue number"),
},
async execute(args, context) {
try {
const result = await $`tea issue ${args.issueNumber} --output json --comments`.text()
const issue = JSON.parse(result)
const dependencies = {
issueNumber: issue.index,
title: issue.title,
blocks: [],
blockedBy: [],
}
if (issue.comments && Array.isArray(issue.comments)) {
for (const comment of issue.comments) {
const text = comment.body || ""
const depMatch = text.match(/blocks\s+#[0-9]+|blocked by\s+#[0-9]+/i)
if (depMatch) {
const relatedIssueMatch = text.match(/#[0-9]+/)
if (relatedIssueMatch) {
const relatedIssueNum = parseInt(relatedIssueMatch[0], 10)
const isBlocks = depMatch[0].toLowerCase().startsWith("blocks")
if (isBlocks) {
dependencies.blocks.push({
issueNumber: relatedIssueNum,
relationship: "blocks",
})
} else {
dependencies.blockedBy.push({
issueNumber: relatedIssueNum,
relationship: "blockedBy",
})
}
}
}
}
}
return dependencies
} catch (error: any) {
return {
error: "Failed to get issue dependencies",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})
export const issueDepsAdd = tool({
description: "Add a dependency relationship between issues (issue A blocks issue B, or issue A is blocked by issue B)",
args: {
issueNumber: tool.schema.number().describe("Primary issue number"),
relatedIssueNumber: tool.schema.number().describe("Related issue number"),
relationship: tool.schema.enum(["blocks", "blockedBy"]).describe("Relationship type: 'blocks' means issueNumber blocks relatedIssueNumber, 'blockedBy' means issueNumber is blocked by relatedIssueNumber"),
comment: tool.schema.string().optional().describe("Optional comment to add with the dependency"),
},
async execute(args, context) {
try {
let commentText = ""
if (args.relationship === "blocks") {
commentText = `@${args.relatedIssueNumber} blocks #${args.issueNumber}`
} else {
commentText = `@${args.issueNumber} is blocked by #${args.relatedIssueNumber}`
}
if (args.comment) {
commentText += `\n\n${args.comment}`
}
const result = await $`tea issue comment --output json --issue ${args.issueNumber} --body ${commentText}`.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to add issue dependency",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})
export const issueDepsRemove = tool({
description: "Remove a dependency relationship between issues by commenting on the issue",
args: {
issueNumber: tool.schema.number().describe("Primary issue number"),
relatedIssueNumber: tool.schema.number().describe("Related issue number to remove dependency from"),
},
async execute(args, context) {
try {
const commentText = `@${args.relatedIssueNumber} removing dependency relationship`
const result = await $`tea issue comment --output json --issue ${args.issueNumber} --body ${commentText}`.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to remove issue dependency",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})

View File

@@ -0,0 +1,160 @@
import { tool } from "@opencode-ai/plugin"
import { $ } from "bun"
export const issueList = tool({
description: "List issues with filters for state, labels, assignee, milestone, and author",
args: {
state: tool.schema.enum(["open", "closed", "all"]).default("open").describe("Filter by state"),
labels: tool.schema.string().optional().describe("Comma-separated list of labels to match"),
assignee: tool.schema.string().optional().describe("Filter by assignee username"),
milestone: tool.schema.string().optional().describe("Filter by milestone name"),
author: tool.schema.string().optional().describe("Filter by author username"),
limit: tool.schema.number().default(30).describe("Number of issues to return"),
keyword: tool.schema.string().optional().describe("Search keyword in title and body"),
},
async execute(args, context) {
try {
let cmd = $`tea issue list --output json --state ${args.state} --limit ${args.limit}`
if (args.labels) {
cmd = cmd`--labels ${args.labels}`
}
if (args.assignee) {
cmd = cmd`--assignee ${args.assignee}`
}
if (args.milestone) {
cmd = cmd`--milestones ${args.milestone}`
}
if (args.author) {
cmd = cmd`--author ${args.author}`
}
if (args.keyword) {
cmd = cmd`--keyword ${args.keyword}`
}
const result = await cmd.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to list issues",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})
export const issueCreate = tool({
description: "Create a new issue with title, body, labels, milestone, and assignees",
args: {
title: tool.schema.string().describe("Issue title"),
body: tool.schema.string().optional().describe("Issue description/body"),
labels: tool.schema.array(tool.schema.string()).optional().describe("Comma-separated list of label names"),
milestone: tool.schema.string().optional().describe("Milestone name"),
assignees: tool.schema.array(tool.schema.string()).optional().describe("Comma-separated list of assignee usernames"),
},
async execute(args, context) {
try {
let cmd = $`tea issue create --output json --title ${args.title}`
if (args.body) {
cmd = cmd`--body ${args.body}`
}
if (args.labels) {
cmd = cmd`--labels ${args.labels.join(",")}`
}
if (args.milestone) {
cmd = cmd`--milestone ${args.milestone}`
}
if (args.assignees) {
cmd = cmd`--assignee ${args.assignees.join(",")}`
}
const result = await cmd.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to create issue",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})
export const issueEdit = tool({
description: "Update an existing issue with partial updates to title, body, labels, milestone, or assignees",
args: {
issueNumber: tool.schema.number().describe("Issue number"),
title: tool.schema.string().optional().describe("New title"),
body: tool.schema.string().optional().describe("New description/body"),
labels: tool.schema.array(tool.schema.string()).optional().describe("New comma-separated list of label names"),
milestone: tool.schema.string().optional().describe("New milestone name"),
assignees: tool.schema.array(tool.schema.string()).optional().describe("New comma-separated list of assignee usernames"),
state: tool.schema.enum(["open", "closed"]).optional().describe("New state"),
},
async execute(args, context) {
try {
let cmd = $`tea issue edit --output json ${args.issueNumber}`
if (args.title) {
cmd = cmd`--title ${args.title}`
}
if (args.body) {
cmd = cmd`--body ${args.body}`
}
if (args.labels) {
cmd = cmd`--labels ${args.labels.join(",")}`
}
if (args.milestone) {
cmd = cmd`--milestone ${args.milestone}`
}
if (args.assignees) {
cmd = cmd`--assignee ${args.assignees.join(",")}`
}
if (args.state) {
cmd = cmd`--state ${args.state}`
}
const result = await cmd.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to edit issue",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})
export const issueDetails = tool({
description: "Get detailed information about a specific issue including dependencies and comments",
args: {
issueNumber: tool.schema.number().describe("Issue number"),
},
async execute(args, context) {
try {
const result = await $`tea issue ${args.issueNumber} --output json --comments`.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to get issue details",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})

View File

@@ -0,0 +1,19 @@
import { tool } from "@opencode-ai/plugin"
import { $ } from "bun"
export const labelList = tool({
description: "List available labels with their colors and descriptions",
args: {},
async execute(args, context) {
try {
const result = await $`tea label list --output json`.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to list labels",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})

View File

@@ -0,0 +1,52 @@
import { tool } from "@opencode-ai/plugin"
import { $ } from "bun"
export const milestoneList = tool({
description: "List milestones with their status and progress",
args: {
state: tool.schema.enum(["open", "closed", "all"]).default("open").describe("Filter by state"),
},
async execute(args, context) {
try {
const result = await $`tea milestone list --output json --state ${args.state}`.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to list milestones",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})
export const milestoneCreate = tool({
description: "Create a new milestone with title, description, and optional due date",
args: {
title: tool.schema.string().describe("Milestone title"),
description: tool.schema.string().optional().describe("Milestone description"),
dueDate: tool.schema.string().optional().describe("Due date (format: YYYY-MM-DD)"),
},
async execute(args, context) {
try {
let cmd = $`tea milestone create --output json --title ${args.title}`
if (args.description) {
cmd = cmd`--description ${args.description}`
}
if (args.dueDate) {
cmd = cmd`--due-date ${args.dueDate}`
}
const result = await cmd.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to create milestone",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})

View File

@@ -0,0 +1,21 @@
import { tool } from "@opencode-ai/plugin"
import { $ } from "bun"
export const notificationList = tool({
description: "List unread notifications including PR reviews, mentions, and other updates",
args: {
limit: tool.schema.number().default(30).describe("Number of notifications to return"),
},
async execute(args, context) {
try {
const result = await $`tea notification list --output json --limit ${args.limit}`.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to list notifications",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})

137
.opencode/tools/tea-pr.ts Normal file
View File

@@ -0,0 +1,137 @@
import { tool } from "@opencode-ai/plugin"
import { $ } from "bun"
export const prList = tool({
description: "List pull requests with their current state, useful for tracking work in progress or finding PRs to review",
args: {
state: tool.schema.enum(["open", "closed", "all"]).default("open").describe("Filter by state"),
author: tool.schema.string().optional().describe("Filter by author username"),
limit: tool.schema.number().default(30).describe("Number of PRs to return"),
},
async execute(args, context) {
try {
const cmd = args.author
? $`tea pr list --output json --state ${args.state} --author ${args.author} --limit ${args.limit}`
: $`tea pr list --output json --state ${args.state} --limit ${args.limit}`
const result = await cmd.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to list pull requests",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})
export const prCreate = tool({
description: "Create a new pull request with title, body, base branch, head branch, and optional labels and milestone",
args: {
title: tool.schema.string().describe("PR title"),
body: tool.schema.string().optional().describe("PR description/body"),
base: tool.schema.string().describe("Base branch name"),
head: tool.schema.string().describe("Head branch name"),
labels: tool.schema.array(tool.schema.string()).optional().describe("Comma-separated list of label names"),
milestone: tool.schema.string().optional().describe("Milestone name"),
},
async execute(args, context) {
try {
let cmd = $`tea pr create --output json --title ${args.title} --base ${args.base} --head ${args.head}`
if (args.body) {
cmd = cmd`--body ${args.body}`
}
if (args.labels) {
cmd = cmd`--labels ${args.labels.join(",")}`
}
if (args.milestone) {
cmd = cmd`--milestone ${args.milestone}`
}
const result = await cmd.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to create pull request",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})
export const prCheckout = tool({
description: "Checkout a pull request locally to work on it",
args: {
prNumber: tool.schema.number().describe("Pull request number"),
},
async execute(args, context) {
try {
const result = await $`tea pr checkout ${args.prNumber}`.text()
return {
success: true,
message: result,
}
} catch (error: any) {
return {
error: "Failed to checkout pull request",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})
export const prMerge = tool({
description: "Merge a pull request using rebase merge style",
args: {
prNumber: tool.schema.number().describe("Pull request number"),
title: tool.schema.string().optional().describe("Merge commit title"),
message: tool.schema.string().optional().describe("Merge commit message"),
},
async execute(args, context) {
try {
let cmd = $`tea pr merge --style rebase --output json ${args.prNumber}`
if (args.title) {
cmd = cmd`--title ${args.title}`
}
if (args.message) {
cmd = cmd`--message ${args.message}`
}
const result = await cmd.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to merge pull request",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})
export const prDetails = tool({
description: "Get detailed information about a specific pull request including review status and comments",
args: {
prNumber: tool.schema.number().describe("Pull request number"),
},
async execute(args, context) {
try {
const result = await $`tea pr ${args.prNumber} --output json --comments`.text()
return JSON.parse(result)
} catch (error: any) {
return {
error: "Failed to get pull request details",
message: error.message,
stderr: error.stderr?.toString(),
}
}
},
})