Skip to content

Commit 1cf8113

Browse files
committed
refactor(cli): convert export command to effectCmd
bootstrap() → effectCmd + Effect.ensuring(store.dispose(ctx)) for disposal parity. Session.Service yielded directly (drops three AppRuntime.runPromise wrappers). UI.CancelledError still propagates as a defect; the try/catch around session lookup becomes Effect.catchCause to match legacy semantics (Session.get surfaces NotFoundError as a defect, not a typed failure).
1 parent c444e97 commit 1cf8113

1 file changed

Lines changed: 57 additions & 64 deletions

File tree

packages/opencode/src/cli/cmd/export.ts

Lines changed: 57 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import type { Argv } from "yargs"
21
import { Session } from "@/session/session"
32
import { MessageV2 } from "../../session/message-v2"
43
import { SessionID } from "../../session/schema"
5-
import { cmd } from "./cmd"
6-
import { bootstrap } from "../bootstrap"
4+
import { effectCmd, fail } from "../effect-cmd"
75
import { UI } from "../ui"
86
import * as prompts from "@clack/prompts"
97
import { EOL } from "os"
10-
import { AppRuntime } from "@/effect/app-runtime"
8+
import { Effect } from "effect"
9+
import { InstanceRef } from "@/effect/instance-ref"
10+
import { InstanceStore } from "@/project/instance-store"
1111

1212
function redact(kind: string, id: string, value: string) {
1313
return value.trim() ? `[redacted:${kind}:${id}]` : value
@@ -220,84 +220,77 @@ function sanitize(data: { info: Session.Info; messages: MessageV2.WithParts[] })
220220
}
221221
}
222222

223-
export const ExportCommand = cmd({
223+
export const ExportCommand = effectCmd({
224224
command: "export [sessionID]",
225225
describe: "export session data as JSON",
226-
builder: (yargs: Argv) => {
227-
return yargs
226+
builder: (yargs) =>
227+
yargs
228228
.positional("sessionID", {
229229
describe: "session id to export",
230230
type: "string",
231231
})
232232
.option("sanitize", {
233233
describe: "redact sensitive transcript and file data",
234234
type: "boolean",
235-
})
236-
},
237-
handler: async (args) => {
238-
await bootstrap(process.cwd(), async () => {
239-
let sessionID = args.sessionID ? SessionID.make(args.sessionID) : undefined
240-
process.stderr.write(`Exporting session: ${sessionID ?? "latest"}\n`)
235+
}),
236+
handler: Effect.fn("Cli.export")(function* (args) {
237+
const ctx = yield* InstanceRef
238+
if (!ctx) return
239+
const store = yield* InstanceStore.Service
240+
return yield* run(args).pipe(Effect.ensuring(store.dispose(ctx)))
241+
}),
242+
})
241243

242-
if (!sessionID) {
243-
UI.empty()
244-
prompts.intro("Export session", {
245-
output: process.stderr,
246-
})
244+
const run = Effect.fn("Cli.export.body")(function* (args: { sessionID?: string; sanitize?: boolean }) {
245+
const svc = yield* Session.Service
246+
let sessionID = args.sessionID ? SessionID.make(args.sessionID) : undefined
247+
process.stderr.write(`Exporting session: ${sessionID ?? "latest"}\n`)
247248

248-
const sessions = await AppRuntime.runPromise(Session.Service.use((svc) => svc.list()))
249+
if (!sessionID) {
250+
UI.empty()
251+
prompts.intro("Export session", { output: process.stderr })
249252

250-
if (sessions.length === 0) {
251-
prompts.log.error("No sessions found", {
252-
output: process.stderr,
253-
})
254-
prompts.outro("Done", {
255-
output: process.stderr,
256-
})
257-
return
258-
}
253+
const sessions = yield* svc.list()
254+
255+
if (sessions.length === 0) {
256+
prompts.log.error("No sessions found", { output: process.stderr })
257+
prompts.outro("Done", { output: process.stderr })
258+
return
259+
}
259260

260-
sessions.sort((a, b) => b.time.updated - a.time.updated)
261+
sessions.sort((a, b) => b.time.updated - a.time.updated)
261262

262-
const selectedSession = await prompts.autocomplete({
263-
message: "Select session to export",
264-
maxItems: 10,
265-
options: sessions.map((session) => ({
266-
label: session.title,
267-
value: session.id,
268-
hint: `${new Date(session.time.updated).toLocaleString()}${session.id.slice(-8)}`,
269-
})),
270-
output: process.stderr,
271-
})
263+
const selectedSession = yield* Effect.promise(() =>
264+
prompts.autocomplete({
265+
message: "Select session to export",
266+
maxItems: 10,
267+
options: sessions.map((session) => ({
268+
label: session.title,
269+
value: session.id,
270+
hint: `${new Date(session.time.updated).toLocaleString()}${session.id.slice(-8)}`,
271+
})),
272+
output: process.stderr,
273+
}),
274+
)
272275

273-
if (prompts.isCancel(selectedSession)) {
274-
throw new UI.CancelledError()
275-
}
276+
if (prompts.isCancel(selectedSession)) {
277+
return yield* Effect.die(new UI.CancelledError())
278+
}
276279

277-
sessionID = selectedSession
280+
sessionID = selectedSession
278281

279-
prompts.outro("Exporting session...", {
280-
output: process.stderr,
281-
})
282-
}
282+
prompts.outro("Exporting session...", { output: process.stderr })
283+
}
283284

284-
try {
285-
const sessionInfo = await AppRuntime.runPromise(Session.Service.use((svc) => svc.get(sessionID!)))
286-
const messages = await AppRuntime.runPromise(
287-
Session.Service.use((svc) => svc.messages({ sessionID: sessionInfo.id })),
288-
)
285+
// Match legacy try/catch — catches both typed failures and defects
286+
// (Session.Service.get throws NotFoundError as a defect, not a typed E).
287+
return yield* Effect.gen(function* () {
288+
const sessionInfo = yield* svc.get(sessionID!)
289+
const messages = yield* svc.messages({ sessionID: sessionInfo.id })
289290

290-
const exportData = {
291-
info: sessionInfo,
292-
messages,
293-
}
291+
const exportData = { info: sessionInfo, messages }
294292

295-
process.stdout.write(JSON.stringify(args.sanitize ? sanitize(exportData) : exportData, null, 2))
296-
process.stdout.write(EOL)
297-
} catch {
298-
UI.error(`Session not found: ${sessionID!}`)
299-
process.exit(1)
300-
}
301-
})
302-
},
293+
process.stdout.write(JSON.stringify(args.sanitize ? sanitize(exportData) : exportData, null, 2))
294+
process.stdout.write(EOL)
295+
}).pipe(Effect.catchCause(() => fail(`Session not found: ${sessionID!}`)))
303296
})

0 commit comments

Comments
 (0)