Compare commits
13 Commits
feature/te
...
c3f8f9c611
| Author | SHA1 | Date | |
|---|---|---|---|
|
c3f8f9c611
|
|||
|
5b4ab46471
|
|||
|
75daf31e1b
|
|||
|
28d7622956
|
|||
|
e692018422
|
|||
| da9fec976a | |||
| a15aac7f38 | |||
| 09c9527e93 | |||
| c1476c6a98 | |||
| 25573dabff | |||
| c60453ef41 | |||
| 9de03b1253 | |||
| ce06cca219 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -20,3 +20,7 @@ secrets/
|
|||||||
# opencode
|
# opencode
|
||||||
.opencode/opencode.json
|
.opencode/opencode.json
|
||||||
.opencode/package-lock.json
|
.opencode/package-lock.json
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
bun.lock
|
||||||
|
package-lock.json
|
||||||
15
.opencode/tools/git-worktree-create.ts
Normal file
15
.opencode/tools/git-worktree-create.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { tool } from "@opencode-ai/plugin"
|
||||||
|
import { $ } from "bun"
|
||||||
|
|
||||||
|
export default tool({
|
||||||
|
description: "Create a git worktree for a branch or PR",
|
||||||
|
args: {
|
||||||
|
branch: tool.schema.string().describe("Branch name or PR number"),
|
||||||
|
directory: tool.schema.string().describe("Worktree directory name (optional)"),
|
||||||
|
},
|
||||||
|
async execute(args) {
|
||||||
|
const worktreeDir = args.directory || args.branch
|
||||||
|
const result = await $`git worktree add ../worktrees/${worktreeDir} ${args.branch}`.text()
|
||||||
|
return result.trim()
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
import { tool } from "@opencode-ai/plugin"
|
|
||||||
import { $ } from "bun"
|
|
||||||
import { join, dirname } from "node:path"
|
|
||||||
|
|
||||||
async function getWorktreeLocation() {
|
|
||||||
const repoRoot = await $`git rev-parse --show-toplevel`.text()
|
|
||||||
const worktreesDir = join(dirname(repoRoot), "worktrees")
|
|
||||||
return { repoRoot, worktreesDir }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const worktreeCreate = tool({
|
|
||||||
description: "Create a new git worktree with specified name and branch",
|
|
||||||
args: {
|
|
||||||
name: tool.schema.string().describe("Worktree directory name (e.g., 'issue-42')"),
|
|
||||||
branch: tool.schema.string().describe("Git branch to attach (must exist or will be created from base)"),
|
|
||||||
base: tool.schema.string().optional().describe("Base branch for new branches (default: origin/main)"),
|
|
||||||
},
|
|
||||||
async execute(args, context) {
|
|
||||||
try {
|
|
||||||
const { worktreesDir } = await getWorktreeLocation()
|
|
||||||
const worktreePath = join(worktreesDir, args.name)
|
|
||||||
|
|
||||||
await $`mkdir -p ${worktreesDir}`.nothrow()
|
|
||||||
|
|
||||||
const existingBranch = await $`git branch --list ${args.branch}`.text()
|
|
||||||
const base = args.base || "origin/main"
|
|
||||||
|
|
||||||
if (existingBranch.trim()) {
|
|
||||||
await $`git worktree add ${worktreePath} ${args.branch}`
|
|
||||||
} else {
|
|
||||||
await $`git worktree add ${worktreePath} -b ${args.branch} ${base}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
path: worktreePath,
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Failed to create worktree",
|
|
||||||
message: error.message,
|
|
||||||
stderr: error.stderr?.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const worktreeList = tool({
|
|
||||||
description: "List all worktrees with their paths and branches",
|
|
||||||
args: {
|
|
||||||
path: tool.schema.string().optional().describe("Specific worktree path to list (lists all if omitted)"),
|
|
||||||
},
|
|
||||||
async execute(args, context) {
|
|
||||||
try {
|
|
||||||
const { repoRoot } = await getWorktreeLocation()
|
|
||||||
|
|
||||||
let cmd
|
|
||||||
if (args.path) {
|
|
||||||
cmd = $`git worktree list --porcelain`
|
|
||||||
} else {
|
|
||||||
cmd = $`git worktree list --porcelain`
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = await cmd.text()
|
|
||||||
|
|
||||||
const worktrees = output
|
|
||||||
.split("\n")
|
|
||||||
.filter((line) => line.startsWith("worktree "))
|
|
||||||
.map((line) => {
|
|
||||||
const path = line.replace("worktree ", "").trim()
|
|
||||||
return { path }
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
worktrees,
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Failed to list worktrees",
|
|
||||||
message: error.message,
|
|
||||||
stderr: error.stderr?.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const worktreeCheckout = tool({
|
|
||||||
description: "Checkout an existing worktree by name",
|
|
||||||
args: {
|
|
||||||
name: tool.schema.string().describe("Worktree name to checkout"),
|
|
||||||
},
|
|
||||||
async execute(args, context) {
|
|
||||||
try {
|
|
||||||
const { worktreesDir } = await getWorktreeLocation()
|
|
||||||
const worktreePath = join(worktreesDir, args.name)
|
|
||||||
|
|
||||||
if (!(await $`test -d ${worktreePath}`.nothrow().text())) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Worktree does not exist",
|
|
||||||
message: `Worktree '${args.name}' not found at ${worktreePath}`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
path: worktreePath,
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Failed to checkout worktree",
|
|
||||||
message: error.message,
|
|
||||||
stderr: error.stderr?.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const worktreeRemove = tool({
|
|
||||||
description: "Remove a worktree with dirty-check safety",
|
|
||||||
args: {
|
|
||||||
name: tool.schema.string().describe("Worktree name to remove"),
|
|
||||||
force: tool.schema.boolean().optional().describe("Force remove even if dirty"),
|
|
||||||
},
|
|
||||||
async execute(args, context) {
|
|
||||||
try {
|
|
||||||
const { worktreesDir } = await getWorktreeLocation()
|
|
||||||
const worktreePath = join(worktreesDir, args.name)
|
|
||||||
|
|
||||||
if (!(await $`test -d ${worktreePath}`.nothrow().text())) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Worktree does not exist",
|
|
||||||
message: `Worktree '${args.name}' not found at ${worktreePath}`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args.force) {
|
|
||||||
const status = await $`git -C ${worktreePath} status --porcelain`.nothrow().text()
|
|
||||||
if (status.trim()) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Worktree has uncommitted changes",
|
|
||||||
message: "Use --force to remove anyway",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await $`git worktree remove ${worktreePath} ${args.force ? "--force" : ""}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: `Worktree '${args.name}' removed`,
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Failed to remove worktree",
|
|
||||||
message: error.message,
|
|
||||||
stderr: error.stderr?.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const worktreeCleanup = tool({
|
|
||||||
description: "Remove stale worktrees (manually invoked)",
|
|
||||||
args: {
|
|
||||||
olderThan: tool.schema.number().optional().describe("Remove worktrees older than N days (default: 7)"),
|
|
||||||
force: tool.schema.boolean().optional().describe("Force cleanup without confirmation"),
|
|
||||||
},
|
|
||||||
async execute(args, context) {
|
|
||||||
try {
|
|
||||||
const { worktreesDir } = await getWorktreeLocation()
|
|
||||||
const olderThan = args.olderThan || 7
|
|
||||||
|
|
||||||
const output = await $`git worktree list --porcelain`.text()
|
|
||||||
|
|
||||||
const worktrees = output
|
|
||||||
.split("\n")
|
|
||||||
.filter((line) => line.startsWith("worktree "))
|
|
||||||
.map((line) => {
|
|
||||||
const path = line.replace("worktree ", "").trim()
|
|
||||||
return path
|
|
||||||
})
|
|
||||||
|
|
||||||
const now = new Date()
|
|
||||||
const staleWorktrees = []
|
|
||||||
|
|
||||||
for (const path of worktrees) {
|
|
||||||
if (!(await $`test -d ${path}`.nothrow().text())) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const stat = await $`stat -f %m ${path}`.nothrow().text()
|
|
||||||
const mtime = parseInt(stat.trim()) * 1000
|
|
||||||
const ageDays = (now.getTime() - mtime) / (1000 * 60 * 60 * 24)
|
|
||||||
|
|
||||||
if (ageDays > olderThan) {
|
|
||||||
staleWorktrees.push({ path, age: Math.round(ageDays) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (staleWorktrees.length === 0) {
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: "No stale worktrees found",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args.force) {
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
staleWorktrees,
|
|
||||||
message: `Found ${staleWorktrees.length} stale worktree(s). Use --force to remove.`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const { path } of staleWorktrees) {
|
|
||||||
await $`git worktree remove ${path} --force`.nothrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
removed: staleWorktrees,
|
|
||||||
message: `Removed ${staleWorktrees.length} stale worktree(s)`,
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Failed to cleanup worktrees",
|
|
||||||
message: error.message,
|
|
||||||
stderr: error.stderr?.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
14
.opencode/tools/tea-comment.ts
Normal file
14
.opencode/tools/tea-comment.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { tool } from "@opencode-ai/plugin"
|
||||||
|
import { $ } from "bun"
|
||||||
|
|
||||||
|
export default tool({
|
||||||
|
description: "Add a comment to a specific issue using tea CLI",
|
||||||
|
args: {
|
||||||
|
issueNumber: tool.schema.number().describe("Issue number/index"),
|
||||||
|
body: tool.schema.string().describe("Comment body"),
|
||||||
|
},
|
||||||
|
async execute(args) {
|
||||||
|
const result = await $`tea comment ${args.issueNumber} ${args.body}`.text()
|
||||||
|
return result.trim()
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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 result
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
error: "Failed to get current user info",
|
|
||||||
message: error.message,
|
|
||||||
stderr: error.stderr?.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
14
.opencode/tools/tea-issue-create.ts
Normal file
14
.opencode/tools/tea-issue-create.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { tool } from "@opencode-ai/plugin"
|
||||||
|
import { $ } from "bun"
|
||||||
|
|
||||||
|
export default tool({
|
||||||
|
description: "Create a new issue in the GitHub repository using tea CLI",
|
||||||
|
args: {
|
||||||
|
title: tool.schema.string().describe("Issue title"),
|
||||||
|
description: tool.schema.string().optional().describe("Issue description (supports markdown)"),
|
||||||
|
},
|
||||||
|
async execute(args) {
|
||||||
|
const result = await $`tea issues create -t ${args.title} -d ${args.description}`.text()
|
||||||
|
return result.trim()
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
import { tool } from "@opencode-ai/plugin";
|
|
||||||
import { $ } from "bun";
|
|
||||||
|
|
||||||
export const issueDepsList = tool({
|
|
||||||
description: "List dependency relationships for an issue",
|
|
||||||
args: {
|
|
||||||
issueNumber: tool.schema.number().describe("Issue number"),
|
|
||||||
},
|
|
||||||
async execute(args) {
|
|
||||||
try {
|
|
||||||
const result =
|
|
||||||
await $`tea issue ${args.issueNumber} --output json --comments`.text();
|
|
||||||
const issue = JSON.parse(result);
|
|
||||||
|
|
||||||
const dependencies: {
|
|
||||||
issueNumber: number;
|
|
||||||
title: string;
|
|
||||||
blocks: Array<{ issueNumber: number; relationship: "blocks" }>;
|
|
||||||
blockedBy: Array<{ issueNumber: number; relationship: "blockedBy" }>;
|
|
||||||
} = {
|
|
||||||
issueNumber: issue.index,
|
|
||||||
title: issue.title,
|
|
||||||
blocks: [],
|
|
||||||
blockedBy: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const comment of issue.comments ?? []) {
|
|
||||||
const text = comment.body ?? "";
|
|
||||||
|
|
||||||
const matches = text.matchAll(/\b(blocks|blocked by)\s+#(\d+)/gi);
|
|
||||||
|
|
||||||
for (const match of matches) {
|
|
||||||
const relationshipText = match[1].toLowerCase();
|
|
||||||
const relatedIssueNum = Number(match[2]);
|
|
||||||
|
|
||||||
if (relationshipText === "blocks") {
|
|
||||||
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",
|
|
||||||
args: {
|
|
||||||
issueNumber: tool.schema.number().describe("Primary issue number"),
|
|
||||||
relatedIssueNumber: tool.schema.number().describe("Related issue number"),
|
|
||||||
relationship: tool.schema
|
|
||||||
.enum(["blocks", "blockedBy"])
|
|
||||||
.describe("'blocks' means issueNumber blocks relatedIssueNumber"),
|
|
||||||
comment: tool.schema.string().optional().describe("Optional comment"),
|
|
||||||
},
|
|
||||||
async execute(args) {
|
|
||||||
try {
|
|
||||||
let commentText =
|
|
||||||
args.relationship === "blocks"
|
|
||||||
? `blocks #${args.relatedIssueNumber}`
|
|
||||||
: `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"),
|
|
||||||
},
|
|
||||||
async execute(args) {
|
|
||||||
try {
|
|
||||||
const commentText = `removing dependency relationship with #${args.relatedIssueNumber}`;
|
|
||||||
|
|
||||||
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(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
13
.opencode/tools/tea-issue-get.ts
Normal file
13
.opencode/tools/tea-issue-get.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { tool } from "@opencode-ai/plugin"
|
||||||
|
import { $ } from "bun"
|
||||||
|
|
||||||
|
export default tool({
|
||||||
|
description: "Get a specific issue with all details and comments using tea CLI",
|
||||||
|
args: {
|
||||||
|
issueNumber: tool.schema.number().describe("Issue number/index"),
|
||||||
|
},
|
||||||
|
async execute(args) {
|
||||||
|
const result = await $`tea issues ${args.issueNumber} --comments --output json`.text()
|
||||||
|
return result.trim()
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
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 ?? 30}`;
|
|
||||||
|
|
||||||
if (args.labels && args.labels.length > 0) {
|
|
||||||
cmd = cmd`--labels ${args.labels}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.assignee && args.assignee.length > 0) {
|
|
||||||
cmd = cmd`--assignee ${args.assignee}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.milestone && args.milestone.length > 0) {
|
|
||||||
cmd = cmd`--milestones ${args.milestone}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.author && args.author.length > 0) {
|
|
||||||
cmd = cmd`--author ${args.author}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.keyword && args.keyword.length > 0) {
|
|
||||||
cmd = cmd`--keyword ${args.keyword}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await cmd.text();
|
|
||||||
} 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(",")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await cmd.text();
|
|
||||||
} 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}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await cmd.text();
|
|
||||||
} 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 {
|
|
||||||
return await $`tea issue ${args.issueNumber} --output json --comments`.text();
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
error: "Failed to get issue details",
|
|
||||||
message: error.message,
|
|
||||||
stderr: error.stderr?.toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
11
.opencode/tools/tea-issues.ts
Normal file
11
.opencode/tools/tea-issues.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { tool } from "@opencode-ai/plugin"
|
||||||
|
import { $ } from "bun"
|
||||||
|
|
||||||
|
export default tool({
|
||||||
|
description: "List all issues in the project using tea CLI",
|
||||||
|
args: {},
|
||||||
|
async execute(args, context) {
|
||||||
|
const result = await $`tea issues list`.text()
|
||||||
|
return result.trim()
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
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(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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 result
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
error: "Failed to list labels",
|
|
||||||
message: error.message,
|
|
||||||
stderr: error.stderr?.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
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 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 result
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
error: "Failed to create milestone",
|
|
||||||
message: error.message,
|
|
||||||
stderr: error.stderr?.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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 result
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
error: "Failed to list notifications",
|
|
||||||
message: error.message,
|
|
||||||
stderr: error.stderr?.toString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
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"),
|
|
||||||
limit: tool.schema.number().optional().describe("Number of PRs to return"),
|
|
||||||
},
|
|
||||||
async execute(args, context) {
|
|
||||||
try {
|
|
||||||
const limit = args.limit || 30;
|
|
||||||
const cmd = $`tea pr list --output json --state ${args.state} --limit ${limit}`;
|
|
||||||
|
|
||||||
const result = await cmd.text();
|
|
||||||
return 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 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 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 result;
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
error: "Failed to get pull request details",
|
|
||||||
message: error.message,
|
|
||||||
stderr: error.stderr?.toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
12
package.json
Normal file
12
package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "architecture",
|
||||||
|
"module": "index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
tsconfig.json
Normal file
29
tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Environment setup & latest features
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "Preserve",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user