import { createCli, type TrpcCliMeta } from "trpc-cli" import { initTRPC } from "@trpc/server" import { z } from "zod" import { Server } from "../server/server" import { AuthAnthropic } from "../auth/anthropic" import { UI } from "./ui" import { App } from "../app/app" import { Bus } from "../bus" import { Provider } from "../provider/provider" import { Session } from "../session" import { Share } from "../share/share" import { Message } from "../session/message" import { VERSION } from "./version" import { LSP } from "../lsp" import fs from "fs/promises" import path from "path" const t = initTRPC.meta().create() export const router = t.router({ generate: t.procedure .meta({ description: "Generate OpenAPI and event specs", }) .input(z.object({})) .mutation(async () => { const specs = await Server.openapi() const dir = "gen" await fs.rmdir(dir, { recursive: true }).catch(() => {}) await fs.mkdir(dir, { recursive: true }) await Bun.write( path.join(dir, "openapi.json"), JSON.stringify(specs, null, 2), ) return "Generated OpenAPI specs in gen/ directory" }), run: t.procedure .meta({ description: "Run OpenCode with a message", }) .input( z.object({ message: z.array(z.string()).default([]).describe("Message to send"), session: z.string().optional().describe("Session ID to continue"), }), ) .mutation( async ({ input }: { input: { message: string[]; session?: string } }) => { const message = input.message.join(" ") await App.provide( { cwd: process.cwd(), version: "0.0.0", }, async () => { await Share.init() const session = input.session ? await Session.get(input.session) : await Session.create() UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + "◍ OpenCode", VERSION) UI.empty() UI.println(UI.Style.TEXT_NORMAL_BOLD + "> ", message) UI.empty() UI.println( UI.Style.TEXT_INFO_BOLD + "~ https://dev.opencode.ai/s?id=" + session.id.slice(-8), ) UI.empty() function printEvent(color: string, type: string, title: string) { UI.println( color + `|`, UI.Style.TEXT_NORMAL + UI.Style.TEXT_DIM + ` ${type.padEnd(7, " ")}`, "", UI.Style.TEXT_NORMAL + title, ) } Bus.subscribe(Message.Event.PartUpdated, async (message) => { const part = message.properties.part if ( part.type === "tool-invocation" && part.toolInvocation.state === "result" ) { if (part.toolInvocation.toolName === "opencode_todowrite") return const args = part.toolInvocation.args as any const tool = part.toolInvocation.toolName if (tool === "opencode_edit") printEvent(UI.Style.TEXT_SUCCESS_BOLD, "Edit", args.filePath) if (tool === "opencode_bash") printEvent( UI.Style.TEXT_WARNING_BOLD, "Execute", args.command, ) if (tool === "opencode_read") printEvent(UI.Style.TEXT_INFO_BOLD, "Read", args.filePath) if (tool === "opencode_write") printEvent( UI.Style.TEXT_SUCCESS_BOLD, "Create", args.filePath, ) if (tool === "opencode_list") printEvent(UI.Style.TEXT_INFO_BOLD, "List", args.path) if (tool === "opencode_glob") printEvent( UI.Style.TEXT_INFO_BOLD, "Glob", args.pattern + (args.path ? " in " + args.path : ""), ) } if (part.type === "text") { if (part.text.includes("\n")) { UI.empty() UI.println(part.text) UI.empty() return } printEvent(UI.Style.TEXT_NORMAL_BOLD, "Text", part.text) } }) const { providerID, modelID } = await Provider.defaultModel() await Session.chat({ sessionID: session.id, providerID, modelID, parts: [ { type: "text", text: message, }, ], }) UI.empty() }, ) return "Session completed" }, ), scrap: t.procedure .meta({ description: "Test command for scraping files", }) .input( z.object({ file: z.string().describe("File to process"), }), ) .mutation(async ({ input }: { input: { file: string } }) => { await App.provide({ cwd: process.cwd(), version: VERSION }, async () => { await LSP.touchFile(input.file, true) await LSP.diagnostics() }) return `Processed file: ${input.file}` }), login: t.router({ anthropic: t.procedure .meta({ description: "Login to Anthropic", }) .input(z.object({})) .mutation(async () => { const { url, verifier } = await AuthAnthropic.authorize() UI.println("Login to Anthropic") UI.println("Open the following URL in your browser:") UI.println(url) UI.println("") const code = await UI.input("Paste the authorization code here: ") await AuthAnthropic.exchange(code, verifier) return "Successfully logged in to Anthropic" }), }), }) export function createOpenCodeCli() { return createCli({ router }) }