/** * Write tools — create/update operations */ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { Type } from "@sinclair/typebox"; import { GiteaClient } from "../../src/client.js"; import * as repos from "../../src/repos.js"; import * as issues from "../../src/issues.js"; import * as pulls from "../../src/pulls.js"; import * as webhooks from "../../src/webhooks.js"; import * as files from "../../src/files.js"; const client = new GiteaClient(); export default function (pi: ExtensionAPI) { // ── Branches ── pi.registerTool({ name: "gitea_create_branch", label: "Gitea: Create Branch", description: "Create a new branch from an existing ref.", parameters: Type.Object({ owner: Type.Optional(Type.String({ description: `Repo owner (default: ${client.defaultOwner})` })), repo: Type.Optional(Type.String({ description: `Repo name (default: ${client.defaultRepo})` })), branch: Type.String({ description: "Name for the new branch" }), ref: Type.Optional(Type.String({ description: "Source branch (default: main)" })), }), async execute(_id, params) { const { owner, repo } = client.resolve(params.owner, params.repo); const branch = await files.createBranch(client, owner, repo, { name: params.branch, oldRef: params.ref, }); return { content: [{ type: "text", text: `Branch created: ${branch.name}` }], details: { branch }, }; }, }); // ── Files ── pi.registerTool({ name: "gitea_get_file_content", label: "Gitea: Get File Content", description: "Get file content from a repository at a specific path.", parameters: Type.Object({ owner: Type.Optional(Type.String({ description: `Repo owner (default: ${client.defaultOwner})` })), repo: Type.Optional(Type.String({ description: `Repo name (default: ${client.defaultRepo})` })), path: Type.String({ description: "File path (e.g., 'src/index.ts')" }), ref: Type.Optional(Type.String({ description: "Commit SHA or branch (default: default branch)" })), }), async execute(_id, params) { const { owner, repo } = client.resolve(params.owner, params.repo); const file = await files.getFileContent(client, owner, repo, params.path, { ref: params.ref }); // Decode base64 content const decoded = file.encoding === "base64" ? Buffer.from(file.content, "base64").toString("utf-8") : file.content; return { content: [{ type: "text", text: `File: ${params.path} (ref: ${params.ref ?? "HEAD"}, sha: ${file.sha})\n\n${decoded}` }], details: { path: params.path, ref: params.ref, sha: file.sha, encoding: file.encoding }, }; }, }); pi.registerTool({ name: "gitea_update_file", label: "Gitea: Update File", description: "Update or create a file in a repository. Requires the file's current SHA for updates.", parameters: Type.Object({ owner: Type.Optional(Type.String({ description: `Repo owner (default: ${client.defaultOwner})` })), repo: Type.Optional(Type.String({ description: `Repo name (default: ${client.defaultRepo})` })), path: Type.String({ description: "File path" }), content: Type.String({ description: "File content" }), message: Type.String({ description: "Commit message" }), branch: Type.Optional(Type.String({ description: "Target branch (default: main)" })), sha: Type.Optional(Type.String({ description: "Current file SHA (required for updates)" })), }), async execute(_id, params) { const { owner, repo } = client.resolve(params.owner, params.repo); const result = await files.updateFile(client, owner, repo, params.path, { content: params.content, message: params.message, branch: params.branch, sha: params.sha, }); return { content: [{ type: "text", text: `File updated: ${params.path}` }], details: { commit: result.commit, content: result.content }, }; }, }); // ── Repos ── pi.registerTool({ name: "gitea_create_repo", label: "Gitea: Create Repository", description: "Create a new repository for the authenticated user.", parameters: Type.Object({ name: Type.String({ description: "Repository name" }), private: Type.Optional(Type.Boolean({ description: "Private repo (default: false)" })), description: Type.Optional(Type.String({ description: "Repository description" })), }), async execute(_id, params) { const repo = await repos.createRepo(client, { name: params.name, private: params.private, description: params.description, }); return { content: [{ type: "text", text: `✅ Created: ${repo.html_url}` }], details: { repo }, }; }, }); pi.registerTool({ name: "gitea_ensure_repo", label: "Gitea: Ensure Repository", description: "Get a repository if it exists, create it if not. Returns clone URL.", parameters: Type.Object({ owner: Type.Optional(Type.String({ description: `Repo owner (default: ${client.defaultOwner})` })), name: Type.String({ description: "Repository name" }), private: Type.Optional(Type.Boolean({ description: "Private if creating (default: false)" })), }), async execute(_id, params) { const owner = params.owner ?? client.defaultOwner; const { repo, created } = await repos.ensureRepo(client, owner, params.name, { private: params.private, }); return { content: [{ type: "text", text: `${created ? "Created" : "Exists"}: ${repo.clone_url}` }], details: { repo, created }, }; }, }); // ── Issues ── pi.registerTool({ name: "gitea_create_issue", label: "Gitea: Create Issue", description: "Create a new issue in a repository.", parameters: Type.Object({ owner: Type.Optional(Type.String({ description: `Repo owner (default: ${client.defaultOwner})` })), repo: Type.Optional(Type.String({ description: `Repo name (default: ${client.defaultRepo})` })), title: Type.String({ description: "Issue title" }), body: Type.Optional(Type.String({ description: "Issue body (Markdown)" })), }), async execute(_id, params) { const { owner, repo } = client.resolve(params.owner, params.repo); const issue = await issues.createIssue(client, owner, repo, { title: params.title, body: params.body, }); return { content: [{ type: "text", text: `✅ Issue #${issue.number}: ${issue.html_url}` }], details: { issue }, }; }, }); // ── Pull Requests ── pi.registerTool({ name: "gitea_create_pr", label: "Gitea: Create PR", description: "Create a pull request from a source branch to a target branch.", parameters: Type.Object({ owner: Type.Optional(Type.String({ description: `Repo owner (default: ${client.defaultOwner})` })), repo: Type.Optional(Type.String({ description: `Repo name (default: ${client.defaultRepo})` })), title: Type.String({ description: "PR title" }), head: Type.String({ description: "Source branch" }), base: Type.String({ description: "Target branch" }), body: Type.Optional(Type.String({ description: "PR description" })), }), async execute(_id, params) { const { owner, repo } = client.resolve(params.owner, params.repo); const pr = await pulls.createPullRequest(client, owner, repo, { title: params.title, head: params.head, base: params.base, body: params.body, }); return { content: [{ type: "text", text: `PR created: ${pr.html_url}` }], details: { pr }, }; }, }); pi.registerTool({ name: "gitea_merge_pr", label: "Gitea: Merge PR", description: "Merge a pull request into its base branch.", parameters: Type.Object({ owner: Type.Optional(Type.String({ description: `Repo owner (default: ${client.defaultOwner})` })), repo: Type.Optional(Type.String({ description: `Repo name (default: ${client.defaultRepo})` })), index: Type.Number({ description: "PR number" }), merge_method: Type.Optional( Type.Union([Type.Literal("merge"), Type.Literal("rebase"), Type.Literal("squash")], { description: "Merge method (default: merge)", }), ), }), async execute(_id, params) { const { owner, repo } = client.resolve(params.owner, params.repo); await pulls.mergePullRequest(client, owner, repo, params.index, { method: params.merge_method as any, }); return { content: [{ type: "text", text: `PR #${params.index} merged.` }], details: { index: params.index }, }; }, }); // ── Webhooks ── pi.registerTool({ name: "gitea_create_webhook", label: "Gitea: Create Webhook", description: "Create a webhook on a repository.", parameters: Type.Object({ owner: Type.Optional(Type.String({ description: `Repo owner (default: ${client.defaultOwner})` })), repo: Type.Optional(Type.String({ description: `Repo name (default: ${client.defaultRepo})` })), url: Type.String({ description: "URL where Gitea will send webhooks" }), token: Type.Optional(Type.String({ description: "Bearer token for webhook auth" })), events: Type.Optional( Type.Array(Type.String(), { description: "Events to listen for (default: issues, issue_comment, pull_request, push)" }), ), }), async execute(_id, params) { const { owner, repo } = client.resolve(params.owner, params.repo); const hook = await webhooks.createWebhook(client, owner, repo, { url: params.url, token: params.token, events: params.events, }); return { content: [{ type: "text", text: `Webhook created: ID ${hook.id}` }], details: { webhook: hook }, }; }, }); }