|
|
@@ -1,13 +1,13 @@
|
|
|
-import type { Argv } from "yargs"
|
|
|
import { Session } from "@/session/session"
|
|
|
import { MessageV2 } from "../../session/message-v2"
|
|
|
import { SessionID } from "../../session/schema"
|
|
|
-import { cmd } from "./cmd"
|
|
|
-import { bootstrap } from "../bootstrap"
|
|
|
+import { effectCmd, fail } from "../effect-cmd"
|
|
|
import { UI } from "../ui"
|
|
|
import * as prompts from "@clack/prompts"
|
|
|
import { EOL } from "os"
|
|
|
-import { AppRuntime } from "@/effect/app-runtime"
|
|
|
+import { Effect } from "effect"
|
|
|
+import { InstanceRef } from "@/effect/instance-ref"
|
|
|
+import { InstanceStore } from "@/project/instance-store"
|
|
|
|
|
|
function redact(kind: string, id: string, value: string) {
|
|
|
return value.trim() ? `[redacted:${kind}:${id}]` : value
|
|
|
@@ -220,11 +220,11 @@ function sanitize(data: { info: Session.Info; messages: MessageV2.WithParts[] })
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-export const ExportCommand = cmd({
|
|
|
+export const ExportCommand = effectCmd({
|
|
|
command: "export [sessionID]",
|
|
|
describe: "export session data as JSON",
|
|
|
- builder: (yargs: Argv) => {
|
|
|
- return yargs
|
|
|
+ builder: (yargs) =>
|
|
|
+ yargs
|
|
|
.positional("sessionID", {
|
|
|
describe: "session id to export",
|
|
|
type: "string",
|
|
|
@@ -232,72 +232,65 @@ export const ExportCommand = cmd({
|
|
|
.option("sanitize", {
|
|
|
describe: "redact sensitive transcript and file data",
|
|
|
type: "boolean",
|
|
|
- })
|
|
|
- },
|
|
|
- handler: async (args) => {
|
|
|
- await bootstrap(process.cwd(), async () => {
|
|
|
- let sessionID = args.sessionID ? SessionID.make(args.sessionID) : undefined
|
|
|
- process.stderr.write(`Exporting session: ${sessionID ?? "latest"}\n`)
|
|
|
+ }),
|
|
|
+ handler: Effect.fn("Cli.export")(function* (args) {
|
|
|
+ const ctx = yield* InstanceRef
|
|
|
+ if (!ctx) return
|
|
|
+ const store = yield* InstanceStore.Service
|
|
|
+ return yield* run(args).pipe(Effect.ensuring(store.dispose(ctx)))
|
|
|
+ }),
|
|
|
+})
|
|
|
|
|
|
- if (!sessionID) {
|
|
|
- UI.empty()
|
|
|
- prompts.intro("Export session", {
|
|
|
- output: process.stderr,
|
|
|
- })
|
|
|
+const run = Effect.fn("Cli.export.body")(function* (args: { sessionID?: string; sanitize?: boolean }) {
|
|
|
+ const svc = yield* Session.Service
|
|
|
+ let sessionID = args.sessionID ? SessionID.make(args.sessionID) : undefined
|
|
|
+ process.stderr.write(`Exporting session: ${sessionID ?? "latest"}\n`)
|
|
|
|
|
|
- const sessions = await AppRuntime.runPromise(Session.Service.use((svc) => svc.list()))
|
|
|
+ if (!sessionID) {
|
|
|
+ UI.empty()
|
|
|
+ prompts.intro("Export session", { output: process.stderr })
|
|
|
|
|
|
- if (sessions.length === 0) {
|
|
|
- prompts.log.error("No sessions found", {
|
|
|
- output: process.stderr,
|
|
|
- })
|
|
|
- prompts.outro("Done", {
|
|
|
- output: process.stderr,
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
+ const sessions = yield* svc.list()
|
|
|
+
|
|
|
+ if (sessions.length === 0) {
|
|
|
+ prompts.log.error("No sessions found", { output: process.stderr })
|
|
|
+ prompts.outro("Done", { output: process.stderr })
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- sessions.sort((a, b) => b.time.updated - a.time.updated)
|
|
|
+ sessions.sort((a, b) => b.time.updated - a.time.updated)
|
|
|
|
|
|
- const selectedSession = await prompts.autocomplete({
|
|
|
- message: "Select session to export",
|
|
|
- maxItems: 10,
|
|
|
- options: sessions.map((session) => ({
|
|
|
- label: session.title,
|
|
|
- value: session.id,
|
|
|
- hint: `${new Date(session.time.updated).toLocaleString()} • ${session.id.slice(-8)}`,
|
|
|
- })),
|
|
|
- output: process.stderr,
|
|
|
- })
|
|
|
+ const selectedSession = yield* Effect.promise(() =>
|
|
|
+ prompts.autocomplete({
|
|
|
+ message: "Select session to export",
|
|
|
+ maxItems: 10,
|
|
|
+ options: sessions.map((session) => ({
|
|
|
+ label: session.title,
|
|
|
+ value: session.id,
|
|
|
+ hint: `${new Date(session.time.updated).toLocaleString()} • ${session.id.slice(-8)}`,
|
|
|
+ })),
|
|
|
+ output: process.stderr,
|
|
|
+ }),
|
|
|
+ )
|
|
|
|
|
|
- if (prompts.isCancel(selectedSession)) {
|
|
|
- throw new UI.CancelledError()
|
|
|
- }
|
|
|
+ if (prompts.isCancel(selectedSession)) {
|
|
|
+ return yield* Effect.die(new UI.CancelledError())
|
|
|
+ }
|
|
|
|
|
|
- sessionID = selectedSession
|
|
|
+ sessionID = selectedSession
|
|
|
|
|
|
- prompts.outro("Exporting session...", {
|
|
|
- output: process.stderr,
|
|
|
- })
|
|
|
- }
|
|
|
+ prompts.outro("Exporting session...", { output: process.stderr })
|
|
|
+ }
|
|
|
|
|
|
- try {
|
|
|
- const sessionInfo = await AppRuntime.runPromise(Session.Service.use((svc) => svc.get(sessionID!)))
|
|
|
- const messages = await AppRuntime.runPromise(
|
|
|
- Session.Service.use((svc) => svc.messages({ sessionID: sessionInfo.id })),
|
|
|
- )
|
|
|
+ // Match legacy try/catch — catches both typed failures and defects
|
|
|
+ // (Session.Service.get throws NotFoundError as a defect, not a typed E).
|
|
|
+ return yield* Effect.gen(function* () {
|
|
|
+ const sessionInfo = yield* svc.get(sessionID!)
|
|
|
+ const messages = yield* svc.messages({ sessionID: sessionInfo.id })
|
|
|
|
|
|
- const exportData = {
|
|
|
- info: sessionInfo,
|
|
|
- messages,
|
|
|
- }
|
|
|
+ const exportData = { info: sessionInfo, messages }
|
|
|
|
|
|
- process.stdout.write(JSON.stringify(args.sanitize ? sanitize(exportData) : exportData, null, 2))
|
|
|
- process.stdout.write(EOL)
|
|
|
- } catch {
|
|
|
- UI.error(`Session not found: ${sessionID!}`)
|
|
|
- process.exit(1)
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
+ process.stdout.write(JSON.stringify(args.sanitize ? sanitize(exportData) : exportData, null, 2))
|
|
|
+ process.stdout.write(EOL)
|
|
|
+ }).pipe(Effect.catchCause(() => fail(`Session not found: ${sessionID!}`)))
|
|
|
})
|