@@ -40,8 +40,19 @@ pub struct SyncService {
4040 pub cancellation : CancellationToken ,
4141}
4242
43+ struct SyncRunDetails {
44+ outcome : SyncOutcome ,
45+ restored_skills : usize ,
46+ }
47+
4348impl SyncService {
4449 pub async fn run ( & mut self , request : SyncRequest ) -> Result < SyncOutcome , AppError > {
50+ self . run_with_details ( request)
51+ . await
52+ . map ( |details| details. outcome )
53+ }
54+
55+ async fn run_with_details ( & mut self , request : SyncRequest ) -> Result < SyncRunDetails , AppError > {
4556 let started = Instant :: now ( ) ;
4657 let result = self . run_inner ( request, started) . await ;
4758 if let Err ( error) = & result {
@@ -64,7 +75,7 @@ impl SyncService {
6475 & mut self ,
6576 request : SyncRequest ,
6677 started : Instant ,
67- ) -> Result < SyncOutcome , AppError > {
78+ ) -> Result < SyncRunDetails , AppError > {
6879 let source = self
6980 . catalog
7081 . resolve ( request. source_name . as_deref ( ) ) ?
@@ -94,7 +105,9 @@ impl SyncService {
94105 Arc :: clone ( & self . progress ) ,
95106 backup_config,
96107 ) ;
97- let restored = restore. apply ( snapshot, & lock, & source_name) ?;
108+ let restored = restore. apply_with_details ( snapshot, & lock, & source_name) ?;
109+ let restored_skills = restored. restored_skills ;
110+ let restored = restored. outcome ;
98111
99112 let settings_path = self . paths . cc_switch_dir . join ( "settings.json" ) ;
100113 let mut settings = DeviceSettings :: load ( & settings_path) ?;
@@ -131,12 +144,15 @@ impl SyncService {
131144 snapshot_id : snapshot_id. clone ( ) ,
132145 } ) ;
133146
134- Ok ( SyncOutcome {
135- source_name,
136- snapshot_id,
137- backup_dir : restored. backup_dir ,
138- projection,
139- duration,
147+ Ok ( SyncRunDetails {
148+ outcome : SyncOutcome {
149+ source_name,
150+ snapshot_id,
151+ backup_dir : restored. backup_dir ,
152+ projection,
153+ duration,
154+ } ,
155+ restored_skills,
140156 } )
141157 }
142158}
@@ -158,9 +174,14 @@ pub async fn run_cli(
158174 progress,
159175 cancellation,
160176 } ;
161- let outcome = service. run ( SyncRequest { source_name } ) . await ?;
162- println ! ( "{}" , render_outcome( translator, & outcome) ) ;
163- Ok ( outcome)
177+ let details = service
178+ . run_with_details ( SyncRequest { source_name } )
179+ . await ?;
180+ println ! (
181+ "{}" ,
182+ render_outcome( translator, & details. outcome, details. restored_skills)
183+ ) ;
184+ Ok ( details. outcome )
164185}
165186
166187pub struct CliProgress {
@@ -293,39 +314,53 @@ impl ProgressSink for CliProgress {
293314 return ;
294315 } ;
295316 if self . tty {
296- let terminal = matches ! (
297- event,
298- ProgressEvent :: Completed { .. }
299- | ProgressEvent :: Failed { .. }
300- | ProgressEvent :: Warning { .. }
301- ) ;
302- if terminal {
303- eprintln ! ( "\r \x1b [2K{line}" ) ;
304- } else {
317+ if rewrites_current_line ( & event) {
305318 eprint ! ( "\r \x1b [2K{line}" ) ;
306319 let _ = std:: io:: stderr ( ) . flush ( ) ;
320+ } else {
321+ eprintln ! ( "\r \x1b [2K{line}" ) ;
307322 }
308323 } else {
309324 eprintln ! ( "{line}" ) ;
310325 }
311326 }
312327
328+ fn emit_restored_skills ( & self , total : usize ) {
329+ let line = format ! (
330+ "{}: {total}" ,
331+ Translator :: new( self . language)
332+ . text( MessageKey :: ProgressRestoringSkills , & MessageArgs :: default ( ) , )
333+ ) ;
334+ if self . tty {
335+ eprintln ! ( "\r \x1b [2K{line}" ) ;
336+ } else {
337+ eprintln ! ( "{line}" ) ;
338+ }
339+ }
340+
313341 fn emit_skill ( & self , agent : String , skill : String , completed : usize , total : usize ) {
314342 let line = format ! (
315343 "{}: {agent} · {skill} {completed}/{total}" ,
316344 Translator :: new( self . language)
317345 . text( MessageKey :: ProgressApplyingSkills , & MessageArgs :: default ( ) )
318346 ) ;
319347 if self . tty {
320- eprint ! ( "\r \x1b [2K{line}" ) ;
321- let _ = std:: io:: stderr ( ) . flush ( ) ;
348+ eprintln ! ( "\r \x1b [2K{line}" ) ;
322349 } else {
323350 eprintln ! ( "{line}" ) ;
324351 }
325352 }
326353}
327354
328- fn render_outcome ( translator : & Translator , outcome : & SyncOutcome ) -> String {
355+ fn rewrites_current_line ( event : & ProgressEvent ) -> bool {
356+ matches ! ( event, ProgressEvent :: Downloading { .. } )
357+ }
358+
359+ fn render_outcome (
360+ translator : & Translator ,
361+ outcome : & SyncOutcome ,
362+ restored_skills : usize ,
363+ ) -> String {
329364 let mut args = MessageArgs :: default ( ) ;
330365 args. 0 . insert ( "source" , outcome. source_name . clone ( ) ) ;
331366 args. 0 . insert ( "snapshot" , outcome. snapshot_id . clone ( ) ) ;
@@ -337,6 +372,7 @@ fn render_outcome(translator: &Translator, outcome: &SyncOutcome) -> String {
337372 "applied" ,
338373 outcome. projection . applied_agents . len ( ) . to_string ( ) ,
339374 ) ;
375+ args. 0 . insert ( "skills" , restored_skills. to_string ( ) ) ;
340376 args. 0
341377 . insert ( "warnings" , outcome. projection . warnings . len ( ) . to_string ( ) ) ;
342378 let backup = outcome. backup_dir . as_ref ( ) . map_or_else (
@@ -425,3 +461,24 @@ fn set_private_file(path: &Path) -> Result<(), AppError> {
425461fn set_private_file ( _path : & Path ) -> Result < ( ) , AppError > {
426462 Ok ( ( ) )
427463}
464+
465+ #[ cfg( test) ]
466+ mod tests {
467+ use super :: rewrites_current_line;
468+ use crate :: progress:: ProgressEvent ;
469+
470+ #[ test]
471+ fn tty_rewrites_only_download_progress ( ) {
472+ assert ! ( rewrites_current_line( & ProgressEvent :: Downloading {
473+ artifact: "db.sql" . to_string( ) ,
474+ downloaded: 1 ,
475+ total: 2 ,
476+ } ) ) ;
477+ assert ! ( !rewrites_current_line( & ProgressEvent :: RestoringSkills ) ) ;
478+ assert ! ( !rewrites_current_line( & ProgressEvent :: ApplyingSkills {
479+ agent: "Codex" . to_string( ) ,
480+ completed: 1 ,
481+ total: 1 ,
482+ } ) ) ;
483+ }
484+ }
0 commit comments