Skip to content

Commit 8331cab

Browse files
committed
refactor(cli): convert debug subcommands to effectCmd
Convert 6 debug commands (and 17 of their subcommands) from cmd() + bootstrap() to effectCmd + Effect.ensuring(store.dispose(ctx)). Files: - debug/config.ts - debug/skill.ts - debug/snapshot.ts (3 subcommands) - debug/lsp.ts (3 subcommands) - debug/ripgrep.ts (3 subcommands) - debug/file.ts (5 subcommands) The parent group commands (snapshot/lsp/rg/file) stay on cmd() since they just dispatch to subcommands and don't run any handler logic. Service calls with typed errors (Ripgrep.tree/files/search) now use Effect.orDie to satisfy effectCmd's E=CliError handler signature. Instance.directory references switched to ctx.directory from InstanceRef.
1 parent 43e2087 commit 8331cab

6 files changed

Lines changed: 185 additions & 139 deletions

File tree

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { EOL } from "os"
2+
import { Effect } from "effect"
23
import { Config } from "@/config/config"
3-
import { AppRuntime } from "@/effect/app-runtime"
4-
import { bootstrap } from "../../bootstrap"
5-
import { cmd } from "../cmd"
4+
import { effectCmd } from "../../effect-cmd"
5+
import { InstanceRef } from "@/effect/instance-ref"
6+
import { InstanceStore } from "@/project/instance-store"
67

7-
export const ConfigCommand = cmd({
8+
export const ConfigCommand = effectCmd({
89
command: "config",
910
describe: "show resolved configuration",
1011
builder: (yargs) => yargs,
11-
async handler() {
12-
await bootstrap(process.cwd(), async () => {
13-
const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.get()))
12+
handler: Effect.fn("Cli.debug.config")(function* () {
13+
const ctx = yield* InstanceRef
14+
if (!ctx) return
15+
const store = yield* InstanceStore.Service
16+
return yield* Effect.gen(function* () {
17+
const config = yield* Config.Service.use((cfg) => cfg.get())
1418
process.stdout.write(JSON.stringify(config, null, 2) + EOL)
15-
})
16-
},
19+
}).pipe(Effect.ensuring(store.dispose(ctx)))
20+
}),
1721
})

packages/opencode/src/cli/cmd/debug/file.ts

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { EOL } from "os"
2-
import { AppRuntime } from "@/effect/app-runtime"
2+
import { Effect } from "effect"
33
import { File } from "../../../file"
44
import { Ripgrep } from "@/file/ripgrep"
5-
import { bootstrap } from "../../bootstrap"
5+
import { effectCmd } from "../../effect-cmd"
66
import { cmd } from "../cmd"
7+
import { InstanceRef } from "@/effect/instance-ref"
8+
import { InstanceStore } from "@/project/instance-store"
79

8-
const FileSearchCommand = cmd({
10+
const FileSearchCommand = effectCmd({
911
command: "search <query>",
1012
describe: "search files by query",
1113
builder: (yargs) =>
@@ -14,15 +16,18 @@ const FileSearchCommand = cmd({
1416
demandOption: true,
1517
description: "Search query",
1618
}),
17-
async handler(args) {
18-
await bootstrap(process.cwd(), async () => {
19-
const results = await AppRuntime.runPromise(File.Service.use((svc) => svc.search({ query: args.query })))
19+
handler: Effect.fn("Cli.debug.file.search")(function* (args) {
20+
const ctx = yield* InstanceRef
21+
if (!ctx) return
22+
const store = yield* InstanceStore.Service
23+
return yield* Effect.gen(function* () {
24+
const results = yield* File.Service.use((svc) => svc.search({ query: args.query }))
2025
process.stdout.write(results.join(EOL) + EOL)
21-
})
22-
},
26+
}).pipe(Effect.ensuring(store.dispose(ctx)))
27+
}),
2328
})
2429

25-
const FileReadCommand = cmd({
30+
const FileReadCommand = effectCmd({
2631
command: "read <path>",
2732
describe: "read file contents as JSON",
2833
builder: (yargs) =>
@@ -31,27 +36,33 @@ const FileReadCommand = cmd({
3136
demandOption: true,
3237
description: "File path to read",
3338
}),
34-
async handler(args) {
35-
await bootstrap(process.cwd(), async () => {
36-
const content = await AppRuntime.runPromise(File.Service.use((svc) => svc.read(args.path)))
39+
handler: Effect.fn("Cli.debug.file.read")(function* (args) {
40+
const ctx = yield* InstanceRef
41+
if (!ctx) return
42+
const store = yield* InstanceStore.Service
43+
return yield* Effect.gen(function* () {
44+
const content = yield* File.Service.use((svc) => svc.read(args.path))
3745
process.stdout.write(JSON.stringify(content, null, 2) + EOL)
38-
})
39-
},
46+
}).pipe(Effect.ensuring(store.dispose(ctx)))
47+
}),
4048
})
4149

42-
const FileStatusCommand = cmd({
50+
const FileStatusCommand = effectCmd({
4351
command: "status",
4452
describe: "show file status information",
4553
builder: (yargs) => yargs,
46-
async handler() {
47-
await bootstrap(process.cwd(), async () => {
48-
const status = await AppRuntime.runPromise(File.Service.use((svc) => svc.status()))
54+
handler: Effect.fn("Cli.debug.file.status")(function* () {
55+
const ctx = yield* InstanceRef
56+
if (!ctx) return
57+
const store = yield* InstanceStore.Service
58+
return yield* Effect.gen(function* () {
59+
const status = yield* File.Service.use((svc) => svc.status())
4960
process.stdout.write(JSON.stringify(status, null, 2) + EOL)
50-
})
51-
},
61+
}).pipe(Effect.ensuring(store.dispose(ctx)))
62+
}),
5263
})
5364

54-
const FileListCommand = cmd({
65+
const FileListCommand = effectCmd({
5566
command: "list <path>",
5667
describe: "list files in a directory",
5768
builder: (yargs) =>
@@ -60,15 +71,18 @@ const FileListCommand = cmd({
6071
demandOption: true,
6172
description: "File path to list",
6273
}),
63-
async handler(args) {
64-
await bootstrap(process.cwd(), async () => {
65-
const files = await AppRuntime.runPromise(File.Service.use((svc) => svc.list(args.path)))
74+
handler: Effect.fn("Cli.debug.file.list")(function* (args) {
75+
const ctx = yield* InstanceRef
76+
if (!ctx) return
77+
const store = yield* InstanceStore.Service
78+
return yield* Effect.gen(function* () {
79+
const files = yield* File.Service.use((svc) => svc.list(args.path))
6680
process.stdout.write(JSON.stringify(files, null, 2) + EOL)
67-
})
68-
},
81+
}).pipe(Effect.ensuring(store.dispose(ctx)))
82+
}),
6983
})
7084

71-
const FileTreeCommand = cmd({
85+
const FileTreeCommand = effectCmd({
7286
command: "tree [dir]",
7387
describe: "show directory tree",
7488
builder: (yargs) =>
@@ -77,12 +91,15 @@ const FileTreeCommand = cmd({
7791
description: "Directory to tree",
7892
default: process.cwd(),
7993
}),
80-
async handler(args) {
81-
await bootstrap(process.cwd(), async () => {
82-
const tree = await AppRuntime.runPromise(Ripgrep.Service.use((svc) => svc.tree({ cwd: args.dir, limit: 200 })))
94+
handler: Effect.fn("Cli.debug.file.tree")(function* (args) {
95+
const ctx = yield* InstanceRef
96+
if (!ctx) return
97+
const store = yield* InstanceStore.Service
98+
return yield* Effect.gen(function* () {
99+
const tree = yield* Effect.orDie(Ripgrep.Service.use((svc) => svc.tree({ cwd: args.dir, limit: 200 })))
83100
console.log(JSON.stringify(tree, null, 2))
84-
})
85-
},
101+
}).pipe(Effect.ensuring(store.dispose(ctx)))
102+
}),
86103
})
87104

88105
export const FileCommand = cmd({
Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { LSP } from "@/lsp/lsp"
2-
import { AppRuntime } from "../../../effect/app-runtime"
32
import { Effect } from "effect"
4-
import { bootstrap } from "../../bootstrap"
3+
import { effectCmd } from "../../effect-cmd"
54
import { cmd } from "../cmd"
65
import * as Log from "@opencode-ai/core/util/log"
76
import { EOL } from "os"
7+
import { InstanceRef } from "@/effect/instance-ref"
8+
import { InstanceStore } from "@/project/instance-store"
89

910
export const LSPCommand = cmd({
1011
command: "lsp",
@@ -14,47 +15,54 @@ export const LSPCommand = cmd({
1415
async handler() {},
1516
})
1617

17-
const DiagnosticsCommand = cmd({
18+
const DiagnosticsCommand = effectCmd({
1819
command: "diagnostics <file>",
1920
describe: "get diagnostics for a file",
2021
builder: (yargs) => yargs.positional("file", { type: "string", demandOption: true }),
21-
async handler(args) {
22-
await bootstrap(process.cwd(), async () => {
23-
const out = await AppRuntime.runPromise(
24-
LSP.Service.use((lsp) =>
25-
Effect.gen(function* () {
26-
yield* lsp.touchFile(args.file, "full")
27-
return yield* lsp.diagnostics()
28-
}),
29-
),
22+
handler: Effect.fn("Cli.debug.lsp.diagnostics")(function* (args) {
23+
const ctx = yield* InstanceRef
24+
if (!ctx) return
25+
const store = yield* InstanceStore.Service
26+
return yield* Effect.gen(function* () {
27+
const out = yield* LSP.Service.use((lsp) =>
28+
Effect.gen(function* () {
29+
yield* lsp.touchFile(args.file, "full")
30+
return yield* lsp.diagnostics()
31+
}),
3032
)
3133
process.stdout.write(JSON.stringify(out, null, 2) + EOL)
32-
})
33-
},
34+
}).pipe(Effect.ensuring(store.dispose(ctx)))
35+
}),
3436
})
3537

36-
export const SymbolsCommand = cmd({
38+
export const SymbolsCommand = effectCmd({
3739
command: "symbols <query>",
3840
describe: "search workspace symbols",
3941
builder: (yargs) => yargs.positional("query", { type: "string", demandOption: true }),
40-
async handler(args) {
41-
await bootstrap(process.cwd(), async () => {
42+
handler: Effect.fn("Cli.debug.lsp.symbols")(function* (args) {
43+
const ctx = yield* InstanceRef
44+
if (!ctx) return
45+
const store = yield* InstanceStore.Service
46+
return yield* Effect.gen(function* () {
4247
using _ = Log.Default.time("symbols")
43-
const results = await AppRuntime.runPromise(LSP.Service.use((lsp) => lsp.workspaceSymbol(args.query)))
48+
const results = yield* LSP.Service.use((lsp) => lsp.workspaceSymbol(args.query))
4449
process.stdout.write(JSON.stringify(results, null, 2) + EOL)
45-
})
46-
},
50+
}).pipe(Effect.ensuring(store.dispose(ctx)))
51+
}),
4752
})
4853

49-
export const DocumentSymbolsCommand = cmd({
54+
export const DocumentSymbolsCommand = effectCmd({
5055
command: "document-symbols <uri>",
5156
describe: "get symbols from a document",
5257
builder: (yargs) => yargs.positional("uri", { type: "string", demandOption: true }),
53-
async handler(args) {
54-
await bootstrap(process.cwd(), async () => {
58+
handler: Effect.fn("Cli.debug.lsp.documentSymbols")(function* (args) {
59+
const ctx = yield* InstanceRef
60+
if (!ctx) return
61+
const store = yield* InstanceStore.Service
62+
return yield* Effect.gen(function* () {
5563
using _ = Log.Default.time("document-symbols")
56-
const results = await AppRuntime.runPromise(LSP.Service.use((lsp) => lsp.documentSymbol(args.uri)))
64+
const results = yield* LSP.Service.use((lsp) => lsp.documentSymbol(args.uri))
5765
process.stdout.write(JSON.stringify(results, null, 2) + EOL)
58-
})
59-
},
66+
}).pipe(Effect.ensuring(store.dispose(ctx)))
67+
}),
6068
})
Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { EOL } from "os"
22
import { Effect, Stream } from "effect"
3-
import { AppRuntime } from "../../../effect/app-runtime"
43
import { Ripgrep } from "../../../file/ripgrep"
5-
import { Instance } from "../../../project/instance"
6-
import { bootstrap } from "../../bootstrap"
4+
import { effectCmd } from "../../effect-cmd"
75
import { cmd } from "../cmd"
6+
import { InstanceRef } from "@/effect/instance-ref"
7+
import { InstanceStore } from "@/project/instance-store"
88

99
export const RipgrepCommand = cmd({
1010
command: "rg",
@@ -13,24 +13,25 @@ export const RipgrepCommand = cmd({
1313
async handler() {},
1414
})
1515

16-
const TreeCommand = cmd({
16+
const TreeCommand = effectCmd({
1717
command: "tree",
1818
describe: "show file tree using ripgrep",
1919
builder: (yargs) =>
2020
yargs.option("limit", {
2121
type: "number",
2222
}),
23-
async handler(args) {
24-
await bootstrap(process.cwd(), async () => {
25-
const tree = await AppRuntime.runPromise(
26-
Ripgrep.Service.use((svc) => svc.tree({ cwd: Instance.directory, limit: args.limit })),
27-
)
23+
handler: Effect.fn("Cli.debug.rg.tree")(function* (args) {
24+
const ctx = yield* InstanceRef
25+
if (!ctx) return
26+
const store = yield* InstanceStore.Service
27+
return yield* Effect.gen(function* () {
28+
const tree = yield* Effect.orDie(Ripgrep.Service.use((svc) => svc.tree({ cwd: ctx.directory, limit: args.limit })))
2829
process.stdout.write(tree + EOL)
29-
})
30-
},
30+
}).pipe(Effect.ensuring(store.dispose(ctx)))
31+
}),
3132
})
3233

33-
const FilesCommand = cmd({
34+
const FilesCommand = effectCmd({
3435
command: "files",
3536
describe: "list files using ripgrep",
3637
builder: (yargs) =>
@@ -47,29 +48,29 @@ const FilesCommand = cmd({
4748
type: "number",
4849
description: "Limit number of results",
4950
}),
50-
async handler(args) {
51-
await bootstrap(process.cwd(), async () => {
52-
const files = await AppRuntime.runPromise(
53-
Effect.gen(function* () {
54-
const rg = yield* Ripgrep.Service
55-
return yield* rg
56-
.files({
57-
cwd: Instance.directory,
58-
glob: args.glob ? [args.glob] : undefined,
59-
})
60-
.pipe(
61-
Stream.take(args.limit ?? Infinity),
62-
Stream.runCollect,
63-
Effect.map((c) => [...c]),
64-
)
65-
}),
66-
)
51+
handler: Effect.fn("Cli.debug.rg.files")(function* (args) {
52+
const ctx = yield* InstanceRef
53+
if (!ctx) return
54+
const store = yield* InstanceStore.Service
55+
return yield* Effect.gen(function* () {
56+
const rg = yield* Ripgrep.Service
57+
const files = yield* rg
58+
.files({
59+
cwd: ctx.directory,
60+
glob: args.glob ? [args.glob] : undefined,
61+
})
62+
.pipe(
63+
Stream.take(args.limit ?? Infinity),
64+
Stream.runCollect,
65+
Effect.map((c) => [...c]),
66+
Effect.orDie,
67+
)
6768
process.stdout.write(files.join(EOL) + EOL)
68-
})
69-
},
69+
}).pipe(Effect.ensuring(store.dispose(ctx)))
70+
}),
7071
})
7172

72-
const SearchCommand = cmd({
73+
const SearchCommand = effectCmd({
7374
command: "search <pattern>",
7475
describe: "search file contents using ripgrep",
7576
builder: (yargs) =>
@@ -87,19 +88,22 @@ const SearchCommand = cmd({
8788
type: "number",
8889
description: "Limit number of results",
8990
}),
90-
async handler(args) {
91-
await bootstrap(process.cwd(), async () => {
92-
const results = await AppRuntime.runPromise(
91+
handler: Effect.fn("Cli.debug.rg.search")(function* (args) {
92+
const ctx = yield* InstanceRef
93+
if (!ctx) return
94+
const store = yield* InstanceStore.Service
95+
return yield* Effect.gen(function* () {
96+
const results = yield* Effect.orDie(
9397
Ripgrep.Service.use((svc) =>
9498
svc.search({
95-
cwd: Instance.directory,
99+
cwd: ctx.directory,
96100
pattern: args.pattern,
97101
glob: args.glob as string[] | undefined,
98102
limit: args.limit,
99103
}),
100104
),
101105
)
102106
process.stdout.write(JSON.stringify(results.items, null, 2) + EOL)
103-
})
104-
},
107+
}).pipe(Effect.ensuring(store.dispose(ctx)))
108+
}),
105109
})

0 commit comments

Comments
 (0)