refactor(cli): convert debug subcommands to effectCmd (#25479)

This commit is contained in:
Kit Langton 2026-05-02 19:22:51 -04:00 committed by GitHub
commit 4de44bbbef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 185 additions and 139 deletions

View file

@ -1,17 +1,21 @@
import { EOL } from "os" import { EOL } from "os"
import { Effect } from "effect"
import { Config } from "@/config/config" import { Config } from "@/config/config"
import { AppRuntime } from "@/effect/app-runtime" import { effectCmd } from "../../effect-cmd"
import { bootstrap } from "../../bootstrap" import { InstanceRef } from "@/effect/instance-ref"
import { cmd } from "../cmd" import { InstanceStore } from "@/project/instance-store"
export const ConfigCommand = cmd({ export const ConfigCommand = effectCmd({
command: "config", command: "config",
describe: "show resolved configuration", describe: "show resolved configuration",
builder: (yargs) => yargs, builder: (yargs) => yargs,
async handler() { handler: Effect.fn("Cli.debug.config")(function* () {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.get())) if (!ctx) return
const store = yield* InstanceStore.Service
return yield* Effect.gen(function* () {
const config = yield* Config.Service.use((cfg) => cfg.get())
process.stdout.write(JSON.stringify(config, null, 2) + EOL) process.stdout.write(JSON.stringify(config, null, 2) + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })

View file

@ -1,11 +1,13 @@
import { EOL } from "os" import { EOL } from "os"
import { AppRuntime } from "@/effect/app-runtime" import { Effect } from "effect"
import { File } from "../../../file" import { File } from "../../../file"
import { Ripgrep } from "@/file/ripgrep" import { Ripgrep } from "@/file/ripgrep"
import { bootstrap } from "../../bootstrap" import { effectCmd } from "../../effect-cmd"
import { cmd } from "../cmd" import { cmd } from "../cmd"
import { InstanceRef } from "@/effect/instance-ref"
import { InstanceStore } from "@/project/instance-store"
const FileSearchCommand = cmd({ const FileSearchCommand = effectCmd({
command: "search <query>", command: "search <query>",
describe: "search files by query", describe: "search files by query",
builder: (yargs) => builder: (yargs) =>
@ -14,15 +16,18 @@ const FileSearchCommand = cmd({
demandOption: true, demandOption: true,
description: "Search query", description: "Search query",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.file.search")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const results = await AppRuntime.runPromise(File.Service.use((svc) => svc.search({ query: args.query }))) if (!ctx) return
const store = yield* InstanceStore.Service
return yield* Effect.gen(function* () {
const results = yield* File.Service.use((svc) => svc.search({ query: args.query }))
process.stdout.write(results.join(EOL) + EOL) process.stdout.write(results.join(EOL) + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })
const FileReadCommand = cmd({ const FileReadCommand = effectCmd({
command: "read <path>", command: "read <path>",
describe: "read file contents as JSON", describe: "read file contents as JSON",
builder: (yargs) => builder: (yargs) =>
@ -31,27 +36,33 @@ const FileReadCommand = cmd({
demandOption: true, demandOption: true,
description: "File path to read", description: "File path to read",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.file.read")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const content = await AppRuntime.runPromise(File.Service.use((svc) => svc.read(args.path))) if (!ctx) return
const store = yield* InstanceStore.Service
return yield* Effect.gen(function* () {
const content = yield* File.Service.use((svc) => svc.read(args.path))
process.stdout.write(JSON.stringify(content, null, 2) + EOL) process.stdout.write(JSON.stringify(content, null, 2) + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })
const FileStatusCommand = cmd({ const FileStatusCommand = effectCmd({
command: "status", command: "status",
describe: "show file status information", describe: "show file status information",
builder: (yargs) => yargs, builder: (yargs) => yargs,
async handler() { handler: Effect.fn("Cli.debug.file.status")(function* () {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const status = await AppRuntime.runPromise(File.Service.use((svc) => svc.status())) if (!ctx) return
const store = yield* InstanceStore.Service
return yield* Effect.gen(function* () {
const status = yield* File.Service.use((svc) => svc.status())
process.stdout.write(JSON.stringify(status, null, 2) + EOL) process.stdout.write(JSON.stringify(status, null, 2) + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })
const FileListCommand = cmd({ const FileListCommand = effectCmd({
command: "list <path>", command: "list <path>",
describe: "list files in a directory", describe: "list files in a directory",
builder: (yargs) => builder: (yargs) =>
@ -60,15 +71,18 @@ const FileListCommand = cmd({
demandOption: true, demandOption: true,
description: "File path to list", description: "File path to list",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.file.list")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const files = await AppRuntime.runPromise(File.Service.use((svc) => svc.list(args.path))) if (!ctx) return
const store = yield* InstanceStore.Service
return yield* Effect.gen(function* () {
const files = yield* File.Service.use((svc) => svc.list(args.path))
process.stdout.write(JSON.stringify(files, null, 2) + EOL) process.stdout.write(JSON.stringify(files, null, 2) + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })
const FileTreeCommand = cmd({ const FileTreeCommand = effectCmd({
command: "tree [dir]", command: "tree [dir]",
describe: "show directory tree", describe: "show directory tree",
builder: (yargs) => builder: (yargs) =>
@ -77,12 +91,15 @@ const FileTreeCommand = cmd({
description: "Directory to tree", description: "Directory to tree",
default: process.cwd(), default: process.cwd(),
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.file.tree")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const tree = await AppRuntime.runPromise(Ripgrep.Service.use((svc) => svc.tree({ cwd: args.dir, limit: 200 }))) if (!ctx) return
const store = yield* InstanceStore.Service
return yield* Effect.gen(function* () {
const tree = yield* Effect.orDie(Ripgrep.Service.use((svc) => svc.tree({ cwd: args.dir, limit: 200 })))
console.log(JSON.stringify(tree, null, 2)) console.log(JSON.stringify(tree, null, 2))
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })
export const FileCommand = cmd({ export const FileCommand = cmd({

View file

@ -1,10 +1,11 @@
import { LSP } from "@/lsp/lsp" import { LSP } from "@/lsp/lsp"
import { AppRuntime } from "../../../effect/app-runtime"
import { Effect } from "effect" import { Effect } from "effect"
import { bootstrap } from "../../bootstrap" import { effectCmd } from "../../effect-cmd"
import { cmd } from "../cmd" import { cmd } from "../cmd"
import * as Log from "@opencode-ai/core/util/log" import * as Log from "@opencode-ai/core/util/log"
import { EOL } from "os" import { EOL } from "os"
import { InstanceRef } from "@/effect/instance-ref"
import { InstanceStore } from "@/project/instance-store"
export const LSPCommand = cmd({ export const LSPCommand = cmd({
command: "lsp", command: "lsp",
@ -14,47 +15,54 @@ export const LSPCommand = cmd({
async handler() {}, async handler() {},
}) })
const DiagnosticsCommand = cmd({ const DiagnosticsCommand = effectCmd({
command: "diagnostics <file>", command: "diagnostics <file>",
describe: "get diagnostics for a file", describe: "get diagnostics for a file",
builder: (yargs) => yargs.positional("file", { type: "string", demandOption: true }), builder: (yargs) => yargs.positional("file", { type: "string", demandOption: true }),
async handler(args) { handler: Effect.fn("Cli.debug.lsp.diagnostics")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const out = await AppRuntime.runPromise( if (!ctx) return
LSP.Service.use((lsp) => const store = yield* InstanceStore.Service
Effect.gen(function* () { return yield* Effect.gen(function* () {
yield* lsp.touchFile(args.file, "full") const out = yield* LSP.Service.use((lsp) =>
return yield* lsp.diagnostics() Effect.gen(function* () {
}), yield* lsp.touchFile(args.file, "full")
), return yield* lsp.diagnostics()
}),
) )
process.stdout.write(JSON.stringify(out, null, 2) + EOL) process.stdout.write(JSON.stringify(out, null, 2) + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })
export const SymbolsCommand = cmd({ export const SymbolsCommand = effectCmd({
command: "symbols <query>", command: "symbols <query>",
describe: "search workspace symbols", describe: "search workspace symbols",
builder: (yargs) => yargs.positional("query", { type: "string", demandOption: true }), builder: (yargs) => yargs.positional("query", { type: "string", demandOption: true }),
async handler(args) { handler: Effect.fn("Cli.debug.lsp.symbols")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
if (!ctx) return
const store = yield* InstanceStore.Service
return yield* Effect.gen(function* () {
using _ = Log.Default.time("symbols") using _ = Log.Default.time("symbols")
const results = await AppRuntime.runPromise(LSP.Service.use((lsp) => lsp.workspaceSymbol(args.query))) const results = yield* LSP.Service.use((lsp) => lsp.workspaceSymbol(args.query))
process.stdout.write(JSON.stringify(results, null, 2) + EOL) process.stdout.write(JSON.stringify(results, null, 2) + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })
export const DocumentSymbolsCommand = cmd({ export const DocumentSymbolsCommand = effectCmd({
command: "document-symbols <uri>", command: "document-symbols <uri>",
describe: "get symbols from a document", describe: "get symbols from a document",
builder: (yargs) => yargs.positional("uri", { type: "string", demandOption: true }), builder: (yargs) => yargs.positional("uri", { type: "string", demandOption: true }),
async handler(args) { handler: Effect.fn("Cli.debug.lsp.documentSymbols")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
if (!ctx) return
const store = yield* InstanceStore.Service
return yield* Effect.gen(function* () {
using _ = Log.Default.time("document-symbols") using _ = Log.Default.time("document-symbols")
const results = await AppRuntime.runPromise(LSP.Service.use((lsp) => lsp.documentSymbol(args.uri))) const results = yield* LSP.Service.use((lsp) => lsp.documentSymbol(args.uri))
process.stdout.write(JSON.stringify(results, null, 2) + EOL) process.stdout.write(JSON.stringify(results, null, 2) + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })

View file

@ -1,10 +1,10 @@
import { EOL } from "os" import { EOL } from "os"
import { Effect, Stream } from "effect" import { Effect, Stream } from "effect"
import { AppRuntime } from "../../../effect/app-runtime"
import { Ripgrep } from "../../../file/ripgrep" import { Ripgrep } from "../../../file/ripgrep"
import { Instance } from "../../../project/instance" import { effectCmd } from "../../effect-cmd"
import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd" import { cmd } from "../cmd"
import { InstanceRef } from "@/effect/instance-ref"
import { InstanceStore } from "@/project/instance-store"
export const RipgrepCommand = cmd({ export const RipgrepCommand = cmd({
command: "rg", command: "rg",
@ -13,24 +13,25 @@ export const RipgrepCommand = cmd({
async handler() {}, async handler() {},
}) })
const TreeCommand = cmd({ const TreeCommand = effectCmd({
command: "tree", command: "tree",
describe: "show file tree using ripgrep", describe: "show file tree using ripgrep",
builder: (yargs) => builder: (yargs) =>
yargs.option("limit", { yargs.option("limit", {
type: "number", type: "number",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.rg.tree")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const tree = await AppRuntime.runPromise( if (!ctx) return
Ripgrep.Service.use((svc) => svc.tree({ cwd: Instance.directory, limit: args.limit })), const store = yield* InstanceStore.Service
) return yield* Effect.gen(function* () {
const tree = yield* Effect.orDie(Ripgrep.Service.use((svc) => svc.tree({ cwd: ctx.directory, limit: args.limit })))
process.stdout.write(tree + EOL) process.stdout.write(tree + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })
const FilesCommand = cmd({ const FilesCommand = effectCmd({
command: "files", command: "files",
describe: "list files using ripgrep", describe: "list files using ripgrep",
builder: (yargs) => builder: (yargs) =>
@ -47,29 +48,29 @@ const FilesCommand = cmd({
type: "number", type: "number",
description: "Limit number of results", description: "Limit number of results",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.rg.files")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const files = await AppRuntime.runPromise( if (!ctx) return
Effect.gen(function* () { const store = yield* InstanceStore.Service
const rg = yield* Ripgrep.Service return yield* Effect.gen(function* () {
return yield* rg const rg = yield* Ripgrep.Service
.files({ const files = yield* rg
cwd: Instance.directory, .files({
glob: args.glob ? [args.glob] : undefined, cwd: ctx.directory,
}) glob: args.glob ? [args.glob] : undefined,
.pipe( })
Stream.take(args.limit ?? Infinity), .pipe(
Stream.runCollect, Stream.take(args.limit ?? Infinity),
Effect.map((c) => [...c]), Stream.runCollect,
) Effect.map((c) => [...c]),
}), Effect.orDie,
) )
process.stdout.write(files.join(EOL) + EOL) process.stdout.write(files.join(EOL) + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })
const SearchCommand = cmd({ const SearchCommand = effectCmd({
command: "search <pattern>", command: "search <pattern>",
describe: "search file contents using ripgrep", describe: "search file contents using ripgrep",
builder: (yargs) => builder: (yargs) =>
@ -87,12 +88,15 @@ const SearchCommand = cmd({
type: "number", type: "number",
description: "Limit number of results", description: "Limit number of results",
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.rg.search")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const results = await AppRuntime.runPromise( if (!ctx) return
const store = yield* InstanceStore.Service
return yield* Effect.gen(function* () {
const results = yield* Effect.orDie(
Ripgrep.Service.use((svc) => Ripgrep.Service.use((svc) =>
svc.search({ svc.search({
cwd: Instance.directory, cwd: ctx.directory,
pattern: args.pattern, pattern: args.pattern,
glob: args.glob as string[] | undefined, glob: args.glob as string[] | undefined,
limit: args.limit, limit: args.limit,
@ -100,6 +104,6 @@ const SearchCommand = cmd({
), ),
) )
process.stdout.write(JSON.stringify(results.items, null, 2) + EOL) process.stdout.write(JSON.stringify(results.items, null, 2) + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })

View file

@ -1,23 +1,22 @@
import { EOL } from "os" import { EOL } from "os"
import { Effect } from "effect" import { Effect } from "effect"
import { AppRuntime } from "@/effect/app-runtime"
import { Skill } from "../../../skill" import { Skill } from "../../../skill"
import { bootstrap } from "../../bootstrap" import { effectCmd } from "../../effect-cmd"
import { cmd } from "../cmd" import { InstanceRef } from "@/effect/instance-ref"
import { InstanceStore } from "@/project/instance-store"
export const SkillCommand = cmd({ export const SkillCommand = effectCmd({
command: "skill", command: "skill",
describe: "list all available skills", describe: "list all available skills",
builder: (yargs) => yargs, builder: (yargs) => yargs,
async handler() { handler: Effect.fn("Cli.debug.skill")(function* () {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
const skills = await AppRuntime.runPromise( if (!ctx) return
Effect.gen(function* () { const store = yield* InstanceStore.Service
const skill = yield* Skill.Service return yield* Effect.gen(function* () {
return yield* skill.all() const skill = yield* Skill.Service
}), const skills = yield* skill.all()
)
process.stdout.write(JSON.stringify(skills, null, 2) + EOL) process.stdout.write(JSON.stringify(skills, null, 2) + EOL)
}) }).pipe(Effect.ensuring(store.dispose(ctx)))
}, }),
}) })

View file

@ -1,7 +1,9 @@
import { AppRuntime } from "@/effect/app-runtime" import { Effect } from "effect"
import { Snapshot } from "../../../snapshot" import { Snapshot } from "../../../snapshot"
import { bootstrap } from "../../bootstrap" import { effectCmd } from "../../effect-cmd"
import { cmd } from "../cmd" import { cmd } from "../cmd"
import { InstanceRef } from "@/effect/instance-ref"
import { InstanceStore } from "@/project/instance-store"
export const SnapshotCommand = cmd({ export const SnapshotCommand = cmd({
command: "snapshot", command: "snapshot",
@ -10,17 +12,21 @@ export const SnapshotCommand = cmd({
async handler() {}, async handler() {},
}) })
const TrackCommand = cmd({ const TrackCommand = effectCmd({
command: "track", command: "track",
describe: "track current snapshot state", describe: "track current snapshot state",
async handler() { handler: Effect.fn("Cli.debug.snapshot.track")(function* () {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
console.log(await AppRuntime.runPromise(Snapshot.Service.use((svc) => svc.track()))) if (!ctx) return
}) const store = yield* InstanceStore.Service
}, return yield* Effect.gen(function* () {
const out = yield* Snapshot.Service.use((svc) => svc.track())
console.log(out)
}).pipe(Effect.ensuring(store.dispose(ctx)))
}),
}) })
const PatchCommand = cmd({ const PatchCommand = effectCmd({
command: "patch <hash>", command: "patch <hash>",
describe: "show patch for a snapshot hash", describe: "show patch for a snapshot hash",
builder: (yargs) => builder: (yargs) =>
@ -29,14 +35,18 @@ const PatchCommand = cmd({
description: "hash", description: "hash",
demandOption: true, demandOption: true,
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.snapshot.patch")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
console.log(await AppRuntime.runPromise(Snapshot.Service.use((svc) => svc.patch(args.hash)))) if (!ctx) return
}) const store = yield* InstanceStore.Service
}, return yield* Effect.gen(function* () {
const out = yield* Snapshot.Service.use((svc) => svc.patch(args.hash))
console.log(out)
}).pipe(Effect.ensuring(store.dispose(ctx)))
}),
}) })
const DiffCommand = cmd({ const DiffCommand = effectCmd({
command: "diff <hash>", command: "diff <hash>",
describe: "show diff for a snapshot hash", describe: "show diff for a snapshot hash",
builder: (yargs) => builder: (yargs) =>
@ -45,9 +55,13 @@ const DiffCommand = cmd({
description: "hash", description: "hash",
demandOption: true, demandOption: true,
}), }),
async handler(args) { handler: Effect.fn("Cli.debug.snapshot.diff")(function* (args) {
await bootstrap(process.cwd(), async () => { const ctx = yield* InstanceRef
console.log(await AppRuntime.runPromise(Snapshot.Service.use((svc) => svc.diff(args.hash)))) if (!ctx) return
}) const store = yield* InstanceStore.Service
}, return yield* Effect.gen(function* () {
const out = yield* Snapshot.Service.use((svc) => svc.diff(args.hash))
console.log(out)
}).pipe(Effect.ensuring(store.dispose(ctx)))
}),
}) })