|
1 | | -import type { Argv } from "yargs" |
2 | 1 | import { Session } from "@/session/session" |
3 | 2 | import { MessageV2 } from "../../session/message-v2" |
4 | 3 | import { SessionID } from "../../session/schema" |
5 | | -import { cmd } from "./cmd" |
6 | | -import { bootstrap } from "../bootstrap" |
| 4 | +import { effectCmd, fail } from "../effect-cmd" |
7 | 5 | import { UI } from "../ui" |
8 | 6 | import * as prompts from "@clack/prompts" |
9 | 7 | 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" |
11 | 11 |
|
12 | 12 | function redact(kind: string, id: string, value: string) { |
13 | 13 | return value.trim() ? `[redacted:${kind}:${id}]` : value |
@@ -220,84 +220,77 @@ function sanitize(data: { info: Session.Info; messages: MessageV2.WithParts[] }) |
220 | 220 | } |
221 | 221 | } |
222 | 222 |
|
223 | | -export const ExportCommand = cmd({ |
| 223 | +export const ExportCommand = effectCmd({ |
224 | 224 | command: "export [sessionID]", |
225 | 225 | describe: "export session data as JSON", |
226 | | - builder: (yargs: Argv) => { |
227 | | - return yargs |
| 226 | + builder: (yargs) => |
| 227 | + yargs |
228 | 228 | .positional("sessionID", { |
229 | 229 | describe: "session id to export", |
230 | 230 | type: "string", |
231 | 231 | }) |
232 | 232 | .option("sanitize", { |
233 | 233 | describe: "redact sensitive transcript and file data", |
234 | 234 | 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 | +}) |
241 | 243 |
|
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`) |
247 | 248 |
|
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 }) |
249 | 252 |
|
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 | + } |
259 | 260 |
|
260 | | - sessions.sort((a, b) => b.time.updated - a.time.updated) |
| 261 | + sessions.sort((a, b) => b.time.updated - a.time.updated) |
261 | 262 |
|
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 | + ) |
272 | 275 |
|
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 | + } |
276 | 279 |
|
277 | | - sessionID = selectedSession |
| 280 | + sessionID = selectedSession |
278 | 281 |
|
279 | | - prompts.outro("Exporting session...", { |
280 | | - output: process.stderr, |
281 | | - }) |
282 | | - } |
| 282 | + prompts.outro("Exporting session...", { output: process.stderr }) |
| 283 | + } |
283 | 284 |
|
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 }) |
289 | 290 |
|
290 | | - const exportData = { |
291 | | - info: sessionInfo, |
292 | | - messages, |
293 | | - } |
| 291 | + const exportData = { info: sessionInfo, messages } |
294 | 292 |
|
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!}`))) |
303 | 296 | }) |
0 commit comments