@@ -7,6 +7,8 @@ import { Locale } from "../../util/locale"
77import { Flag } from "../../flag/flag"
88import { EOL } from "os"
99import path from "path"
10+ import * as prompts from "@clack/prompts"
11+ import { formatTranscript } from "./tui/util/transcript"
1012
1113function pagerCmd ( ) : string [ ] {
1214 const lessOptions = [ "-R" , "-S" ]
@@ -38,7 +40,7 @@ function pagerCmd(): string[] {
3840export const SessionCommand = cmd ( {
3941 command : "session" ,
4042 describe : "manage sessions" ,
41- builder : ( yargs : Argv ) => yargs . command ( SessionListCommand ) . demandCommand ( ) ,
43+ builder : ( yargs : Argv ) => yargs . command ( SessionListCommand ) . command ( SessionExportCommand ) . demandCommand ( ) ,
4244 async handler ( ) { } ,
4345} )
4446
@@ -133,3 +135,135 @@ function formatSessionJSON(sessions: Session.Info[]): string {
133135 } ) )
134136 return JSON . stringify ( jsonData , null , 2 )
135137}
138+
139+ export const SessionExportCommand = cmd ( {
140+ command : "export [sessionID]" ,
141+ describe : "export session transcript to file" ,
142+ builder : ( yargs : Argv ) => {
143+ return yargs
144+ . positional ( "sessionID" , {
145+ describe : "session id to export" ,
146+ type : "string" ,
147+ } )
148+ . option ( "output" , {
149+ alias : "o" ,
150+ describe : "output file path" ,
151+ type : "string" ,
152+ } )
153+ . option ( "format" , {
154+ alias : "f" ,
155+ describe : "output format" ,
156+ type : "string" ,
157+ choices : [ "markdown" , "json" ] ,
158+ default : "markdown" ,
159+ } )
160+ . option ( "thinking" , {
161+ describe : "include thinking/reasoning blocks" ,
162+ type : "boolean" ,
163+ default : true ,
164+ } )
165+ . option ( "tool-details" , {
166+ describe : "include tool input/output details" ,
167+ type : "boolean" ,
168+ default : true ,
169+ } )
170+ . option ( "assistant-metadata" , {
171+ describe : "include assistant metadata (agent, model, duration)" ,
172+ type : "boolean" ,
173+ default : true ,
174+ } )
175+ } ,
176+ handler : async ( args ) => {
177+ await bootstrap ( process . cwd ( ) , async ( ) => {
178+ let sessionID = args . sessionID
179+
180+ if ( ! sessionID ) {
181+ prompts . intro ( "Export session" , {
182+ output : process . stderr ,
183+ } )
184+
185+ const sessions = [ ]
186+ for await ( const session of Session . list ( ) ) {
187+ sessions . push ( session )
188+ }
189+
190+ if ( sessions . length === 0 ) {
191+ prompts . log . error ( "No sessions found" , {
192+ output : process . stderr ,
193+ } )
194+ prompts . outro ( "Done" , {
195+ output : process . stderr ,
196+ } )
197+ return
198+ }
199+
200+ sessions . sort ( ( a , b ) => b . time . updated - a . time . updated )
201+
202+ const selectedSession = await prompts . autocomplete ( {
203+ message : "Select session to export" ,
204+ maxItems : 10 ,
205+ options : sessions . map ( ( session ) => ( {
206+ label : session . title ,
207+ value : session . id ,
208+ hint : `${ new Date ( session . time . updated ) . toLocaleString ( ) } • ${ session . id . slice ( - 8 ) } ` ,
209+ } ) ) ,
210+ output : process . stderr ,
211+ } )
212+
213+ if ( prompts . isCancel ( selectedSession ) ) {
214+ throw new UI . CancelledError ( )
215+ }
216+
217+ sessionID = selectedSession as string
218+ }
219+
220+ const sessionInfo = await Session . get ( sessionID ! )
221+
222+ let content : string
223+ let defaultExtension : string
224+
225+ const sessionMessages = await Session . messages ( { sessionID : sessionID ! } )
226+
227+ if ( args . format === "json" ) {
228+ const exportData = {
229+ info : sessionInfo ,
230+ messages : sessionMessages . map ( ( msg ) => ( {
231+ info : msg . info ,
232+ parts : msg . parts ,
233+ } ) ) ,
234+ }
235+ content = JSON . stringify ( exportData , null , 2 )
236+ defaultExtension = "json"
237+ } else {
238+ content = formatTranscript ( sessionInfo , sessionMessages , {
239+ thinking : args . thinking ,
240+ toolDetails : args . toolDetails ,
241+ assistantMetadata : args . assistantMetadata ,
242+ } )
243+ defaultExtension = "md"
244+ }
245+
246+ const outputPath = await ( async ( ) => {
247+ if ( args . output ) return args . output
248+ const defaultFilename = `session-${ sessionInfo . id . slice ( 0 , 8 ) } .${ defaultExtension } `
249+ const filenameInput = await prompts . text ( {
250+ message : "Export filename" ,
251+ defaultValue : defaultFilename ,
252+ output : process . stderr ,
253+ } )
254+
255+ if ( prompts . isCancel ( filenameInput ) ) {
256+ throw new UI . CancelledError ( )
257+ }
258+
259+ return filenameInput . trim ( )
260+ } ) ( )
261+
262+ await Bun . write ( outputPath , content )
263+
264+ prompts . outro ( `Session exported to ${ outputPath } ` , {
265+ output : process . stderr ,
266+ } )
267+ } )
268+ } ,
269+ } )
0 commit comments