refactor(cli): convert run command to effectCmd (#25519)

This commit is contained in:
Kit Langton
2026-05-02 22:35:20 -04:00
committed by GitHub
parent 2829943ad1
commit 7409dcc6bd
3 changed files with 25 additions and 20 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
import type { CommandModule } from "yargs" import type { CommandModule } from "yargs"
type WithDoubleDash<T> = T & { "--"?: string[] } export type WithDoubleDash<T> = T & { "--"?: string[] }
export function cmd<T, U>(input: CommandModule<T, WithDoubleDash<U>>) { export function cmd<T, U>(input: CommandModule<T, WithDoubleDash<U>>) {
return input return input
+21 -16
View File
@@ -1,10 +1,10 @@
import type { Argv } from "yargs" import type { Argv } from "yargs"
import path from "path" import path from "path"
import { pathToFileURL } from "url" import { pathToFileURL } from "url"
import { Effect } from "effect"
import { UI } from "../ui" import { UI } from "../ui"
import { cmd } from "./cmd" import { effectCmd } from "../effect-cmd"
import { Flag } from "@opencode-ai/core/flag/flag" import { Flag } from "@opencode-ai/core/flag/flag"
import { bootstrap } from "../bootstrap"
import { EOL } from "os" import { EOL } from "os"
import { Filesystem } from "@/util/filesystem" import { Filesystem } from "@/util/filesystem"
import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2" import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
@@ -203,11 +203,17 @@ function normalizePath(input?: string) {
return input return input
} }
export const RunCommand = cmd({ export const RunCommand = effectCmd({
command: "run [message..]", command: "run [message..]",
describe: "run opencode with a message", describe: "run opencode with a message",
builder: (yargs: Argv) => { // --attach connects to a remote server (no local instance needed); the
return yargs // default path runs an in-process server and needs the project instance.
instance: (args) => !args.attach,
// For --dir without --attach, load instance for the resolved target dir.
// The handler also chdirs (preserving the legacy order: chdir → file resolution).
directory: (args) => (args.dir && !args.attach ? path.resolve(process.cwd(), args.dir) : process.cwd()),
builder: (yargs: Argv) =>
yargs
.positional("message", { .positional("message", {
describe: "message to send", describe: "message to send",
type: "string", type: "string",
@@ -291,9 +297,9 @@ export const RunCommand = cmd({
type: "boolean", type: "boolean",
describe: "auto-approve permissions that are not explicitly denied (dangerous!)", describe: "auto-approve permissions that are not explicitly denied (dangerous!)",
default: false, default: false,
}) }),
}, handler: Effect.fn("Cli.run")(function* (args) {
handler: async (args) => { yield* Effect.promise(async () => {
let message = [...args.message, ...(args["--"] || [])] let message = [...args.message, ...(args["--"] || [])]
.map((arg) => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg)) .map((arg) => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg))
.join(" ") .join(" ")
@@ -661,13 +667,12 @@ export const RunCommand = cmd({
return await execute(sdk) return await execute(sdk)
} }
await bootstrap(process.cwd(), async () => { const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => { const request = new Request(input, init)
const request = new Request(input, init) return Server.Default().app.fetch(request)
return Server.Default().app.fetch(request) }) as typeof globalThis.fetch
}) as typeof globalThis.fetch const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", fetch: fetchFn })
const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", fetch: fetchFn }) await execute(sdk)
await execute(sdk)
}) })
}, }),
}) })
+3 -3
View File
@@ -3,7 +3,7 @@ import { Effect, Schema } from "effect"
import { AppRuntime, type AppServices } from "@/effect/app-runtime" import { AppRuntime, type AppServices } from "@/effect/app-runtime"
import { InstanceStore } from "@/project/instance-store" import { InstanceStore } from "@/project/instance-store"
import { InstanceRef } from "@/effect/instance-ref" import { InstanceRef } from "@/effect/instance-ref"
import { cmd } from "./cmd/cmd" import { cmd, type WithDoubleDash } from "./cmd/cmd"
/** /**
* User-visible command failure. Throw via `fail("...")` from an effectCmd handler * User-visible command failure. Throw via `fail("...")` from an effectCmd handler
@@ -47,7 +47,7 @@ interface EffectCmdOpts<Args, A> {
instance?: boolean | ((args: Args) => boolean) instance?: boolean | ((args: Args) => boolean)
/** Defaults to process.cwd(). Override for commands that take a directory positional. */ /** Defaults to process.cwd(). Override for commands that take a directory positional. */
directory?: (args: Args) => string directory?: (args: Args) => string
handler: (args: Args) => Effect.Effect<A, CliError, AppServices | InstanceStore.Service> handler: (args: WithDoubleDash<Args>) => Effect.Effect<A, CliError, AppServices | InstanceStore.Service>
} }
/** /**
@@ -75,7 +75,7 @@ export const effectCmd = <Args, A>(opts: EffectCmdOpts<Args, A>) =>
builder: opts.builder as never, builder: opts.builder as never,
async handler(rawArgs) { async handler(rawArgs) {
// yargs typing wraps Args in ArgumentsCamelCase<WithDoubleDash<...>>; cast at the boundary. // yargs typing wraps Args in ArgumentsCamelCase<WithDoubleDash<...>>; cast at the boundary.
const args = rawArgs as unknown as Args const args = rawArgs as unknown as WithDoubleDash<Args>
const useInstance = typeof opts.instance === "function" ? opts.instance(args) : opts.instance !== false const useInstance = typeof opts.instance === "function" ? opts.instance(args) : opts.instance !== false
if (!useInstance) { if (!useInstance) {
await AppRuntime.runPromise(opts.handler(args)) await AppRuntime.runPromise(opts.handler(args))