99from humanloop import Humanloop
1010from humanloop .sync .sync_client import SyncClient
1111from datetime import datetime
12+ from humanloop .cli .progress import progress_context
1213
1314# Set up logging
1415logger = logging .getLogger (__name__ )
2526INFO_COLOR = "blue"
2627WARNING_COLOR = "yellow"
2728
29+ MAX_FILES_TO_DISPLAY = 10
30+
2831def get_client (api_key : Optional [str ] = None , env_file : Optional [str ] = None , base_url : Optional [str ] = None ) -> Humanloop :
2932 """Get a Humanloop client instance."""
3033 if not api_key :
@@ -65,7 +68,7 @@ def common_options(f: Callable) -> Callable:
6568 )
6669 @click .option (
6770 "--base-dir" ,
68- help = "Base directory for synced files" ,
71+ help = "Base directory for pulled files" ,
6972 default = "humanloop" ,
7073 type = click .Path (),
7174 )
@@ -116,9 +119,23 @@ def cli():
116119 help = "Environment to pull from (e.g. 'production', 'staging')" ,
117120 default = None ,
118121)
122+ @click .option (
123+ "--verbose" ,
124+ "-v" ,
125+ is_flag = True ,
126+ help = "Show detailed progress information" ,
127+ )
119128@handle_sync_errors
120129@common_options
121- def pull (path : Optional [str ], environment : Optional [str ], api_key : Optional [str ], env_file : Optional [str ], base_dir : str , base_url : Optional [str ]):
130+ def pull (
131+ path : Optional [str ],
132+ environment : Optional [str ],
133+ api_key : Optional [str ],
134+ env_file : Optional [str ],
135+ base_dir : str ,
136+ base_url : Optional [str ],
137+ verbose : bool
138+ ):
122139 """Pull prompt and agent files from Humanloop to your local filesystem.
123140
124141 \b
@@ -143,29 +160,43 @@ def pull(path: Optional[str], environment: Optional[str], api_key: Optional[str]
143160
144161 Currently only supports syncing prompt and agent files. Other file types will be skipped."""
145162 client = get_client (api_key , env_file , base_url )
146- sync_client = SyncClient (client , base_dir = base_dir )
163+ sync_client = SyncClient (client , base_dir = base_dir , log_level = logging . DEBUG if verbose else logging . WARNING )
147164
148165 click .echo (click .style ("Pulling files from Humanloop..." , fg = INFO_COLOR ))
149-
150166 click .echo (click .style (f"Path: { path or '(root)' } " , fg = INFO_COLOR ))
151167 click .echo (click .style (f"Environment: { environment or '(default)' } " , fg = INFO_COLOR ))
152-
153- successful_files = sync_client .pull (path , environment )
168+
169+ if verbose :
170+ # Don't use the spinner in verbose mode as the spinner and sync client logging compete
171+ successful_files = sync_client .pull (path , environment )
172+ else :
173+ with progress_context ("Pulling files..." ):
174+ successful_files = sync_client .pull (path , environment )
154175
155176 # Get metadata about the operation
156177 metadata = sync_client .metadata .get_last_operation ()
157178 if metadata :
158179 # Determine if the operation was successful based on failed_files
159180 is_successful = not metadata .get ('failed_files' ) and not metadata .get ('error' )
160181 duration_color = SUCCESS_COLOR if is_successful else ERROR_COLOR
161- click .echo (click .style (f"\n Sync completed in { metadata ['duration_ms' ]} ms" , fg = duration_color ))
182+ click .echo (click .style (f"Pull completed in { metadata ['duration_ms' ]} ms" , fg = duration_color ))
162183
163184 if metadata ['successful_files' ]:
164- click .echo (click .style (f"\n Successfully synced { len (metadata ['successful_files' ])} files:" , fg = SUCCESS_COLOR ))
165- for file in metadata ['successful_files' ]:
166- click .echo (click .style (f" ✓ { file } " , fg = SUCCESS_COLOR ))
185+ click .echo (click .style (f"\n Successfully pulled { len (metadata ['successful_files' ])} files:" , fg = SUCCESS_COLOR ))
186+
187+ if verbose :
188+ for file in metadata ['successful_files' ]:
189+ click .echo (click .style (f" ✓ { file } " , fg = SUCCESS_COLOR ))
190+ else :
191+ files_to_display = metadata ['successful_files' ][:MAX_FILES_TO_DISPLAY ]
192+ for file in files_to_display :
193+ click .echo (click .style (f" ✓ { file } " , fg = SUCCESS_COLOR ))
194+
195+ if len (metadata ['successful_files' ]) > MAX_FILES_TO_DISPLAY :
196+ remaining = len (metadata ['successful_files' ]) - MAX_FILES_TO_DISPLAY
197+ click .echo (click .style (f" ...and { remaining } more" , fg = SUCCESS_COLOR ))
167198 if metadata ['failed_files' ]:
168- click .echo (click .style (f"\n Failed to sync { len (metadata ['failed_files' ])} files:" , fg = ERROR_COLOR ))
199+ click .echo (click .style (f"\n Failed to pull { len (metadata ['failed_files' ])} files:" , fg = ERROR_COLOR ))
169200 for file in metadata ['failed_files' ]:
170201 click .echo (click .style (f" ✗ { file } " , fg = ERROR_COLOR ))
171202 if metadata .get ('error' ):
@@ -214,9 +245,9 @@ def history(api_key: Optional[str], env_file: Optional[str], base_dir: str, base
214245 click .echo (f"Environment: { op ['environment' ]} " )
215246 click .echo (f"Duration: { op ['duration_ms' ]} ms" )
216247 if op ['successful_files' ]:
217- click .echo (click .style (f"Successfully synced { len (op ['successful_files' ])} file{ '' if len (op ['successful_files' ]) == 1 else 's' } " , fg = SUCCESS_COLOR ))
248+ click .echo (click .style (f"Successfully { op [ 'operation_type' ] } ed { len (op ['successful_files' ])} file{ '' if len (op ['successful_files' ]) == 1 else 's' } " , fg = SUCCESS_COLOR ))
218249 if op ['failed_files' ]:
219- click .echo (click .style (f"Failed to sync { len (op ['failed_files' ])} file{ '' if len (op ['failed_files' ]) == 1 else 's' } " , fg = ERROR_COLOR ))
250+ click .echo (click .style (f"Failed to { op [ 'operation_type' ] } ed { len (op ['failed_files' ])} file{ '' if len (op ['failed_files' ]) == 1 else 's' } " , fg = ERROR_COLOR ))
220251 if op ['error' ]:
221252 click .echo (click .style (f"Error: { op ['error' ]} " , fg = ERROR_COLOR ))
222253 click .echo (click .style ("----------------------" , fg = INFO_COLOR ))
0 commit comments