migrated custom tools to V2 plugin
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { $ } from "bun"
|
||||
import type { Info } from "@opencode/plugin/promise/tool"
|
||||
|
||||
const gitWorktreeCreate: Info = {
|
||||
name: "git-worktree-create",
|
||||
description: "Create a git worktree for a branch or PR",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
branch: { type: "string", description: "Branch name or PR number" },
|
||||
},
|
||||
required: ["branch"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
async execute(input) {
|
||||
const { branch } = input as { branch: string }
|
||||
const result = await $`git worktree add ../worktrees/${branch}`.text()
|
||||
return { content: result.trim() }
|
||||
},
|
||||
}
|
||||
|
||||
export default gitWorktreeCreate
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Plugin } from "@opencode/plugin"
|
||||
|
||||
import gitWorktreeCreate from "./git-worktree-create.js"
|
||||
import teaActionsRunsList from "./tea-actions-runs-list.js"
|
||||
import teaActionsRunsLogs from "./tea-actions-runs-logs.js"
|
||||
import teaComment from "./tea-comment.js"
|
||||
import teaIssueCreate from "./tea-issue-create.js"
|
||||
import teaIssueGet from "./tea-issue-get.js"
|
||||
import teaIssues from "./tea-issues.js"
|
||||
import teaPrCreate from "./tea-pr-create.js"
|
||||
import teaPrGet from "./tea-pr-get.js"
|
||||
import teaPulls from "./tea-pulls.js"
|
||||
|
||||
export default Plugin.define({
|
||||
id: "tools",
|
||||
async setup(ctx) {
|
||||
await ctx.tool.transform((editor) => {
|
||||
editor.add(gitWorktreeCreate)
|
||||
editor.add(teaActionsRunsList)
|
||||
editor.add(teaActionsRunsLogs)
|
||||
editor.add(teaComment)
|
||||
editor.add(teaIssueCreate)
|
||||
editor.add(teaIssueGet)
|
||||
editor.add(teaIssues)
|
||||
editor.add(teaPrCreate)
|
||||
editor.add(teaPrGet)
|
||||
editor.add(teaPulls)
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,37 @@
|
||||
import { $ } from "bun"
|
||||
import type { Info } from "@opencode/plugin/promise/tool"
|
||||
|
||||
const teaActionsRunsList: Info = {
|
||||
name: "tea-actions-runs-list",
|
||||
description: "List workflow runs for Gitea actions with optional filters",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: { type: "string", description: "Filter by status (success, failure, pending, queued, in_progress, skipped, canceled)" },
|
||||
branch: { type: "string", description: "Filter by branch name" },
|
||||
event: { type: "string", description: "Filter by event type (push, pull_request, etc.)" },
|
||||
limit: { type: "number", description: "Number of results to return (default: 30)" },
|
||||
since: { type: "string", description: "Show runs started after this time (e.g., '24h', '7d')" },
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
async execute(input) {
|
||||
const { status, branch, event, limit, since } = input as {
|
||||
status?: string
|
||||
branch?: string
|
||||
event?: string
|
||||
limit?: number
|
||||
since?: string
|
||||
}
|
||||
let cmd = [`tea`, `actions`, `runs`, `list`, `-o`, `json`]
|
||||
if (status) cmd = [...cmd, `--status`, status]
|
||||
if (branch) cmd = [...cmd, `--branch`, branch]
|
||||
if (event) cmd = [...cmd, `--event`, event]
|
||||
if (limit) cmd = [...cmd, `--limit`, String(limit)]
|
||||
if (since) cmd = [...cmd, `--since`, since]
|
||||
const result = await $`${cmd}`.text()
|
||||
return { content: result.trim() }
|
||||
},
|
||||
}
|
||||
|
||||
export default teaActionsRunsList
|
||||
@@ -0,0 +1,26 @@
|
||||
import { $ } from "bun"
|
||||
import type { Info } from "@opencode/plugin/promise/tool"
|
||||
|
||||
const teaActionsRunsLogs: Info = {
|
||||
name: "tea-actions-runs-logs",
|
||||
description: "Get logs for a specific Gitea actions workflow run to diagnose failures",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
runId: { type: "number", description: "Workflow run ID to get logs for" },
|
||||
job: { type: "string", description: "Specific job ID to view (if omitted, shows all jobs)" },
|
||||
},
|
||||
required: ["runId"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
async execute(input) {
|
||||
const { runId, job } = input as { runId: number; job?: string }
|
||||
let cmd = [`tea`, `actions`, `runs`, `logs`]
|
||||
if (job) cmd = [...cmd, `--job`, job]
|
||||
cmd = [...cmd, String(runId), `-o`, `simple`]
|
||||
const result = await $`${cmd}`.text()
|
||||
return { content: result.trim() }
|
||||
},
|
||||
}
|
||||
|
||||
export default teaActionsRunsLogs
|
||||
@@ -0,0 +1,23 @@
|
||||
import { $ } from "bun"
|
||||
import type { Info } from "@opencode/plugin/promise/tool"
|
||||
|
||||
const teaComment: Info = {
|
||||
name: "tea-comment",
|
||||
description: "Add a comment to a specific issue using tea CLI",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
issueNumber: { type: "number", description: "Issue number/index" },
|
||||
body: { type: "string", description: "Comment body" },
|
||||
},
|
||||
required: ["issueNumber", "body"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
async execute(input) {
|
||||
const { issueNumber, body } = input as { issueNumber: number; body: string }
|
||||
const result = await $`tea comment ${issueNumber} ${body}`.text()
|
||||
return { content: result.trim() }
|
||||
},
|
||||
}
|
||||
|
||||
export default teaComment
|
||||
@@ -0,0 +1,23 @@
|
||||
import { $ } from "bun"
|
||||
import type { Info } from "@opencode/plugin/promise/tool"
|
||||
|
||||
const teaIssueCreate: Info = {
|
||||
name: "tea-issue-create",
|
||||
description: "Create a new issue in the GitHub repository using tea CLI",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
title: { type: "string", description: "Issue title" },
|
||||
description: { type: "string", description: "Issue description (supports markdown)" },
|
||||
},
|
||||
required: ["title"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
async execute(input) {
|
||||
const { title, description } = input as { title: string; description?: string }
|
||||
const result = await $`tea issues create -t ${title} -d ${description}`.text()
|
||||
return { content: result.trim() }
|
||||
},
|
||||
}
|
||||
|
||||
export default teaIssueCreate
|
||||
@@ -0,0 +1,22 @@
|
||||
import { $ } from "bun"
|
||||
import type { Info } from "@opencode/plugin/promise/tool"
|
||||
|
||||
const teaIssueGet: Info = {
|
||||
name: "tea-issue-get",
|
||||
description: "Get a specific issue with all details and comments using tea CLI",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
issueNumber: { type: "number", description: "Issue number/index" },
|
||||
},
|
||||
required: ["issueNumber"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
async execute(input) {
|
||||
const { issueNumber } = input as { issueNumber: number }
|
||||
const result = await $`tea issues ${issueNumber} --comments --output json`.text()
|
||||
return { content: result.trim() }
|
||||
},
|
||||
}
|
||||
|
||||
export default teaIssueGet
|
||||
@@ -0,0 +1,18 @@
|
||||
import { $ } from "bun"
|
||||
import type { Info } from "@opencode/plugin/promise/tool"
|
||||
|
||||
const teaIssues: Info = {
|
||||
name: "tea-issues",
|
||||
description: "List all issues in the project using tea CLI",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
additionalProperties: false,
|
||||
},
|
||||
async execute() {
|
||||
const result = await $`tea issues list`.text()
|
||||
return { content: result.trim() }
|
||||
},
|
||||
}
|
||||
|
||||
export default teaIssues
|
||||
@@ -0,0 +1,40 @@
|
||||
import { $ } from "bun"
|
||||
import type { Info } from "@opencode/plugin/promise/tool"
|
||||
|
||||
const teaPrCreate: Info = {
|
||||
name: "tea-pr-create",
|
||||
description: "Create a pull request in the repository using tea CLI",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
title: { type: "string", description: "PR title" },
|
||||
head: { type: "string", description: "Branch name of the PR source (defaults to current branch)" },
|
||||
base: { type: "string", description: "Branch name of the PR target (defaults to repository default branch)" },
|
||||
description: { type: "string", description: "PR description (supports markdown)" },
|
||||
assignees: { type: "string", description: "Comma-separated list of usernames to assign" },
|
||||
labels: { type: "string", description: "Comma-separated list of labels to assign" },
|
||||
},
|
||||
required: ["title"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
async execute(input) {
|
||||
const { title, head, base, description, assignees, labels } = input as {
|
||||
title: string
|
||||
head?: string
|
||||
base?: string
|
||||
description?: string
|
||||
assignees?: string
|
||||
labels?: string
|
||||
}
|
||||
let cmd = [`tea`, `pulls`, `create`, `-t`, title]
|
||||
if (head) cmd = [...cmd, `--head`, head]
|
||||
if (base) cmd = [...cmd, `-b`, base]
|
||||
if (description) cmd = [...cmd, `-d`, description]
|
||||
if (assignees) cmd = [...cmd, `-a`, assignees]
|
||||
if (labels) cmd = [...cmd, `-L`, labels]
|
||||
const result = await $`${cmd}`.text()
|
||||
return { content: result.trim() }
|
||||
},
|
||||
}
|
||||
|
||||
export default teaPrCreate
|
||||
@@ -0,0 +1,22 @@
|
||||
import { $ } from "bun"
|
||||
import type { Info } from "@opencode/plugin/promise/tool"
|
||||
|
||||
const teaPrGet: Info = {
|
||||
name: "tea-pr-get",
|
||||
description: "Get a specific pull request with all details and comments using tea CLI",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
pullNumber: { type: "number", description: "Pull request number/index" },
|
||||
},
|
||||
required: ["pullNumber"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
async execute(input) {
|
||||
const { pullNumber } = input as { pullNumber: number }
|
||||
const result = await $`tea pulls ${pullNumber} --comments --output json`.text()
|
||||
return { content: result.trim() }
|
||||
},
|
||||
}
|
||||
|
||||
export default teaPrGet
|
||||
@@ -0,0 +1,21 @@
|
||||
import { $ } from "bun"
|
||||
import type { Info } from "@opencode/plugin/promise/tool"
|
||||
|
||||
const teaPulls: Info = {
|
||||
name: "tea-pulls",
|
||||
description: "List pull requests in the repository using tea CLI",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
state: { type: "string", description: "Filter by state (all|open|closed)" },
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
async execute(input) {
|
||||
const state = (input as { state?: string }).state || "open"
|
||||
const result = await $`tea pulls --state ${state} --output json`.text()
|
||||
return { content: result.trim() }
|
||||
},
|
||||
}
|
||||
|
||||
export default teaPulls
|
||||
@@ -1,13 +0,0 @@
|
||||
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"),
|
||||
},
|
||||
async execute(args) {
|
||||
const result = await $`git worktree add ../worktrees/${args.branch}`.text()
|
||||
return result.trim()
|
||||
},
|
||||
})
|
||||
@@ -1,23 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import { $ } from "bun"
|
||||
|
||||
export default tool({
|
||||
description: "List workflow runs for Gitea actions with optional filters",
|
||||
args: {
|
||||
status: tool.schema.string().optional().describe("Filter by status (success, failure, pending, queued, in_progress, skipped, canceled)"),
|
||||
branch: tool.schema.string().optional().describe("Filter by branch name"),
|
||||
event: tool.schema.string().optional().describe("Filter by event type (push, pull_request, etc.)"),
|
||||
limit: tool.schema.number().optional().describe("Number of results to return (default: 30)"),
|
||||
since: tool.schema.string().optional().describe("Show runs started after this time (e.g., '24h', '7d')"),
|
||||
},
|
||||
async execute(args) {
|
||||
let cmd = [`tea`, `actions`, `runs`, `list`, `-o`, `json`]
|
||||
if (args.status) cmd = [...cmd, `--status`, args.status]
|
||||
if (args.branch) cmd = [...cmd, `--branch`, args.branch]
|
||||
if (args.event) cmd = [...cmd, `--event`, args.event]
|
||||
if (args.limit) cmd = [...cmd, `--limit`, String(args.limit)]
|
||||
if (args.since) cmd = [...cmd, `--since`, args.since]
|
||||
const result = await $`${cmd}`.text()
|
||||
return result.trim()
|
||||
},
|
||||
})
|
||||
@@ -1,17 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import { $ } from "bun"
|
||||
|
||||
export default tool({
|
||||
description: "Get logs for a specific Gitea actions workflow run to diagnose failures",
|
||||
args: {
|
||||
runId: tool.schema.number().describe("Workflow run ID to get logs for"),
|
||||
job: tool.schema.string().optional().describe("Specific job ID to view (if omitted, shows all jobs)"),
|
||||
},
|
||||
async execute(args) {
|
||||
let cmd = [`tea`, `actions`, `runs`, `logs`]
|
||||
if (args.job) cmd = [...cmd, `--job`, args.job]
|
||||
cmd = [...cmd, String(args.runId), `-o`, `simple`]
|
||||
const result = await $`${cmd}`.text()
|
||||
return result.trim()
|
||||
},
|
||||
})
|
||||
@@ -1,14 +0,0 @@
|
||||
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,14 +0,0 @@
|
||||
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,13 +0,0 @@
|
||||
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,11 +0,0 @@
|
||||
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,24 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import { $ } from "bun"
|
||||
|
||||
export default tool({
|
||||
description: "Create a pull request in the repository using tea CLI",
|
||||
args: {
|
||||
title: tool.schema.string().describe("PR title"),
|
||||
head: tool.schema.string().optional().describe("Branch name of the PR source (defaults to current branch)"),
|
||||
base: tool.schema.string().optional().describe("Branch name of the PR target (defaults to repository default branch)"),
|
||||
description: tool.schema.string().optional().describe("PR description (supports markdown)"),
|
||||
assignees: tool.schema.string().optional().describe("Comma-separated list of usernames to assign"),
|
||||
labels: tool.schema.string().optional().describe("Comma-separated list of labels to assign"),
|
||||
},
|
||||
async execute(args) {
|
||||
let cmd = [`tea`, `pulls`, `create`, `-t`, args.title]
|
||||
if (args.head) cmd = [...cmd, `--head`, args.head]
|
||||
if (args.base) cmd = [...cmd, `-b`, args.base]
|
||||
if (args.description) cmd = [...cmd, `-d`, args.description]
|
||||
if (args.assignees) cmd = [...cmd, `-a`, args.assignees]
|
||||
if (args.labels) cmd = [...cmd, `-L`, args.labels]
|
||||
const result = await $`${cmd}`.text()
|
||||
return result.trim()
|
||||
},
|
||||
})
|
||||
@@ -1,13 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import { $ } from "bun"
|
||||
|
||||
export default tool({
|
||||
description: "Get a specific pull request with all details and comments using tea CLI",
|
||||
args: {
|
||||
pullNumber: tool.schema.number().describe("Pull request number/index"),
|
||||
},
|
||||
async execute(args) {
|
||||
const result = await $`tea pulls ${args.pullNumber} --comments --output json`.text()
|
||||
return result.trim()
|
||||
},
|
||||
})
|
||||
@@ -1,14 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import { $ } from "bun"
|
||||
|
||||
export default tool({
|
||||
description: "List pull requests in the repository using tea CLI",
|
||||
args: {
|
||||
state: tool.schema.string().optional().describe("Filter by state (all|open|closed)"),
|
||||
},
|
||||
async execute(args) {
|
||||
const state = args.state || "open"
|
||||
const result = await $`tea pulls --state ${state} --output json`.text()
|
||||
return result.trim()
|
||||
},
|
||||
})
|
||||
@@ -4,7 +4,7 @@ OPENCODE_DIR := $(HOME)/.config/opencode
|
||||
REPO_DIR := $(shell pwd)
|
||||
|
||||
# Items to symlink
|
||||
ITEMS := skills tools agents AGENTS.md
|
||||
ITEMS := skills plugins agents AGENTS.md
|
||||
|
||||
# LLM services to manage
|
||||
LLM_SERVICES := qwen qwen-4bit
|
||||
|
||||
@@ -11,7 +11,7 @@ OpenCode uses three component types:
|
||||
| Component | Location | Purpose | Example |
|
||||
|-----------|----------|---------|---------|
|
||||
| **Skills** | `.opencode/skills/` | Reference knowledge | `issue-writing` knows how to structure good issues |
|
||||
| **Tools** | `.opencode/tools/` | Custom functions | `spawn_issues` implements multiple issues in parallel |
|
||||
| **Tools** | `.opencode/plugins/` | Custom functions, provided by plugins | `tools` registers the `tea-*` Gitea CLI tools |
|
||||
| **Agents** | `.opencode/agents/` | Specialized subagents | `code-reviewer` handles PR reviews |
|
||||
|
||||
## Quick Start
|
||||
@@ -29,7 +29,7 @@ make install
|
||||
|
||||
This creates symlinks at:
|
||||
- `~/.config/opencode/skills/` → `.opencode/skills/`
|
||||
- `~/.config/opencode/tools/` → `.opencode/tools/`
|
||||
- `~/.config/opencode/plugins/` → `.opencode/plugins/`
|
||||
- `~/.config/opencode/agents/` → `.opencode/agents/`
|
||||
|
||||
### Uninstallation
|
||||
@@ -67,7 +67,7 @@ architecture/
|
||||
│
|
||||
├── .opencode/ # OpenCode configuration
|
||||
│ ├── skills/ # Reference knowledge (SKILL.md files)
|
||||
│ ├── tools/ # Custom tools (TypeScript/JS)
|
||||
│ ├── plugins/ # Plugins registering custom tools (TypeScript)
|
||||
│ └── agents/ # Specialized subagents (AGENT.md files)
|
||||
│
|
||||
├── Makefile # Symlink management
|
||||
@@ -96,24 +96,42 @@ Skills are auto-discovered by OpenCode and available via the `skill` tool.
|
||||
|
||||
### Tools
|
||||
|
||||
Create `.opencode/tools/<name>.ts`:
|
||||
Tools are registered by plugins under `.opencode/plugins/`. Create `.opencode/plugins/<name>/index.ts`:
|
||||
|
||||
```typescript
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import { Plugin } from "@opencode/plugin"
|
||||
|
||||
export default tool({
|
||||
import type { Info } from "@opencode/plugin/promise/tool"
|
||||
|
||||
const example: Info = {
|
||||
name: "example",
|
||||
description: "What this tool does",
|
||||
args: {
|
||||
param: tool.schema.string().describe("Parameter description"),
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
param: { type: "string", description: "Parameter description" },
|
||||
},
|
||||
async execute(args) {
|
||||
required: ["param"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
async execute(input) {
|
||||
const { param } = input as { param: string }
|
||||
// Your implementation
|
||||
return "result"
|
||||
return { content: `result for ${param}` }
|
||||
},
|
||||
}
|
||||
|
||||
export default Plugin.define({
|
||||
id: "example",
|
||||
async setup(ctx) {
|
||||
await ctx.tool.transform((editor) => {
|
||||
editor.add(example)
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Tools are auto-discovered and available to the LLM.
|
||||
Tool input schemas are plain JSON Schema, and execution returns structured content. Plugins under `.opencode/plugins/` are auto-discovered and their tools are available to the LLM.
|
||||
|
||||
### Agents
|
||||
|
||||
|
||||
Reference in New Issue
Block a user