@@ -263,15 +263,77 @@ def artifact():
263263
264264
265265@artifact .command (['list' , 'ls' ])
266- @click .argument ('path' )
267- @click .option ('--versions' , is_flag = True , help = 'List artifact versions instead of artifacts' )
266+ @click .argument ('path' , required = False )
267+ @click .option ('--user_id' , required = False , help = 'User ID. Defaults to the current context user.' )
268+ @click .option (
269+ '--app_id' ,
270+ required = False ,
271+ help = (
272+ "App ID. If omitted, lists artifacts across all of the user's apps. "
273+ "Required with --artifact_id."
274+ ),
275+ )
276+ @click .option (
277+ '--artifact_id' ,
278+ required = False ,
279+ help = (
280+ 'Artifact ID. With --versions, lists versions for this artifact. '
281+ 'Without --versions, lists the artifact itself. Requires --app_id.'
282+ ),
283+ )
284+ @click .option (
285+ '--versions' ,
286+ is_flag = True ,
287+ help = 'List versions for the specified artifact instead of artifacts.' ,
288+ )
289+ @click .option (
290+ '--page_no' ,
291+ type = click .IntRange (min = 1 ),
292+ default = None ,
293+ help = (
294+ 'Page number to list (must be >= 1). '
295+ 'Omit both --page_no and --per_page to list all results. '
296+ 'Supported with --app_id or --versions.'
297+ ),
298+ )
299+ @click .option (
300+ '--per_page' ,
301+ type = click .IntRange (min = 1 ),
302+ default = None ,
303+ help = ('Number of items per page (must be >= 1). Supported with --app_id or --versions.' ),
304+ )
268305@click .pass_context
269- def list (ctx , path , versions ):
306+ def list (ctx , path , user_id , app_id , artifact_id , versions , page_no , per_page ):
270307 """List artifacts or artifact versions.
271308
309+ \b
272310 Examples:
273- clarifai af list users/u/apps/a
274- clarifai af list users/u/apps/a/artifacts/my-artifact --versions
311+ # List all artifacts for the current user across all of their apps
312+ clarifai af list
313+
314+ \b
315+ # Scope to a single app
316+ clarifai af list --app_id my-app
317+
318+ \b
319+ # Paginate within a single app
320+ clarifai af list --app_id my-app --page_no 2 --per_page 50
321+
322+ \b
323+ # List versions of a specific artifact
324+ clarifai af list --versions --app_id my-app --artifact_id my-artifact
325+
326+ \b
327+ # Look up a single artifact by id
328+ clarifai af list --app_id my-app --artifact_id my-artifact
329+
330+ \b
331+ # Another user
332+ clarifai af list --user_id other-user --app_id their-app
333+
334+ \b
335+ # Legacy URI path (deprecated)
336+ clarifai af list users/u/apps/a
275337 """
276338
277339 from clarifai_grpc .grpc .api import resources_pb2
@@ -282,25 +344,48 @@ def list(ctx, path, versions):
282344 try :
283345 validate_context (ctx )
284346
285- # Parse path and extract components
286- if versions :
347+ # Back-compat: parse legacy positional URI path and back-fill any unset flags.
348+ if path :
349+ logger .warning (
350+ "Passing a URI path positionally is deprecated; use --user_id/--app_id/--artifact_id flags instead."
351+ )
287352 parsed = parse_artifact_path (path )
288- if not parsed ['artifact_id' ]:
353+ user_id = user_id or parsed .get ('user_id' )
354+ app_id = app_id or parsed .get ('app_id' )
355+ artifact_id = artifact_id or parsed .get ('artifact_id' )
356+
357+ # Default user_id from the current CLI context.
358+ if not user_id :
359+ user_id = getattr (ctx .obj .current , 'user_id' , None )
360+ if not user_id :
361+ click .echo (
362+ "No user_id provided and none found in current context. "
363+ "Pass --user_id or set a context with a user_id." ,
364+ err = True ,
365+ )
366+ raise click .Abort ()
367+
368+ if versions :
369+ if not app_id or not artifact_id :
289370 click .echo (
290- "When using --versions, path must include artifact ID: users/<user-id>/apps/<app-id>/artifacts/<artifact-id> " ,
371+ "--versions requires --app_id and --artifact_id. " ,
291372 err = True ,
292373 )
293374 raise click .Abort ()
294375
295- # List artifact versions with full data
296376 artifact_version = ArtifactVersion (
297- artifact_id = parsed [ ' artifact_id' ] ,
298- user_id = parsed [ ' user_id' ] ,
299- app_id = parsed [ ' app_id' ] ,
377+ artifact_id = artifact_id ,
378+ user_id = user_id ,
379+ app_id = app_id ,
300380 pat = ctx .obj .current .pat ,
301381 base = ctx .obj .current .api_base ,
302382 )
303- versions_list = builtin_list (artifact_version .list ())
383+ list_kwargs = {}
384+ if page_no is not None :
385+ list_kwargs ['page' ] = page_no
386+ if per_page is not None :
387+ list_kwargs ['per_page' ] = per_page
388+ versions_list = builtin_list (artifact_version .list (** list_kwargs ))
304389
305390 if not versions_list :
306391 click .echo ("No artifact versions found" )
@@ -321,28 +406,51 @@ def list(ctx, path, versions):
321406 'CREATED_AT' : lambda v : str (v .created_at .ToDatetime ()) if v .created_at else '' ,
322407 },
323408 )
324- else :
325- # For listing artifacts, we expect app-level path: users/<user-id>/apps/<app-id>
326- if '/artifacts/' in path :
327- click .echo ("To list artifacts, use: users/<user-id>/apps/<app-id>" , err = True )
409+ return
410+
411+ # Listing artifacts (not versions).
412+ list_kwargs = {}
413+ if page_no is not None :
414+ list_kwargs ['page' ] = page_no
415+ if per_page is not None :
416+ list_kwargs ['per_page' ] = per_page
417+
418+ if artifact_id :
419+ # Single-artifact lookup mode (kubectl-style: pass the ID, get one row).
420+ if not app_id :
421+ click .echo (
422+ "--artifact_id requires --app_id." ,
423+ err = True ,
424+ )
328425 raise click .Abort ()
329426
330- # Parse app-level path
331- parsed = parse_artifact_path (path )
332-
333- # Validate it's an app-level path (no artifact_id)
334- if parsed ['artifact_id' ] is not None :
335- click .echo ("To list artifacts, use: users/<user-id>/apps/<app-id>" , err = True )
336- raise click .Abort ()
427+ artifact_client = Artifact (
428+ user_id = user_id ,
429+ app_id = app_id ,
430+ pat = ctx .obj .current .pat ,
431+ base = ctx .obj .current .api_base ,
432+ )
433+ single = artifact_client .get (artifact_id = artifact_id )
434+ display_co_resources (
435+ [single ],
436+ custom_columns = {
437+ 'ARTIFACT' : lambda a : a .id ,
438+ 'LATEST_VERSION' : lambda a : _resolve_latest_version_id (a ),
439+ 'VISIBILITY' : lambda a : _resolve_visibility (resources_pb2 , a ),
440+ 'CREATED_AT' : lambda a : str (a .created_at .ToDatetime ()) if a .created_at else '' ,
441+ },
442+ )
443+ return
337444
338- # List artifacts
339- artifact = Artifact (
340- user_id = parsed ['user_id' ],
341- app_id = parsed ['app_id' ],
445+ if app_id :
446+ # Single-app mode.
447+ artifact_client = Artifact (
448+ user_id = user_id ,
449+ app_id = app_id ,
342450 pat = ctx .obj .current .pat ,
343451 base = ctx .obj .current .api_base ,
344452 )
345- artifacts_list = builtin_list (artifact .list ())
453+ artifacts_list = builtin_list (artifact_client .list (** list_kwargs ))
346454
347455 if not artifacts_list :
348456 click .echo ("No artifacts found" )
@@ -357,7 +465,69 @@ def list(ctx, path, versions):
357465 'CREATED_AT' : lambda a : str (a .created_at .ToDatetime ()) if a .created_at else '' ,
358466 },
359467 )
468+ return
469+
470+ if page_no is not None or per_page is not None :
471+ click .echo (
472+ "--page_no and --per_page require --app_id (or --versions). "
473+ "Global pagination across all apps is not supported." ,
474+ err = True ,
475+ )
476+ raise click .Abort ()
477+
478+ # User-default mode: iterate apps and aggregate artifacts.
479+ from clarifai .client .user import User
480+
481+ user_client = User (
482+ user_id = user_id ,
483+ pat = ctx .obj .current .pat ,
484+ base_url = ctx .obj .current .api_base ,
485+ )
486+ apps = builtin_list (user_client .list_apps ())
487+ if not apps :
488+ click .echo ("No apps found for user" )
489+ return
490+
491+ logger .info (
492+ f"Listing all artifacts across { len (apps )} app(s) for user { user_id } . "
493+ "Use --app_id to scope to a single app."
494+ )
495+
496+ aggregated = []
497+ for app in apps :
498+ app_id_value = getattr (app , 'id' , None ) or getattr (app , 'app_id' , None )
499+ if not app_id_value :
500+ continue
501+ try :
502+ art_client = Artifact (
503+ user_id = user_id ,
504+ app_id = app_id_value ,
505+ pat = ctx .obj .current .pat ,
506+ base = ctx .obj .current .api_base ,
507+ )
508+ for a in art_client .list (** list_kwargs ):
509+ aggregated .append ((app_id_value , a ))
510+ except Exception as inner :
511+ logger .warning (f"Failed to list artifacts for app { app_id_value } : { inner } " )
512+
513+ if not aggregated :
514+ click .echo ("No artifacts found" )
515+ return
516+
517+ app_id_by_artifact = {id (a ): app_id_value for app_id_value , a in aggregated }
518+ display_co_resources (
519+ [a for _ , a in aggregated ],
520+ custom_columns = {
521+ 'APP_ID' : lambda a : app_id_by_artifact .get (id (a ), '' ),
522+ 'ARTIFACT' : lambda a : a .id ,
523+ 'LATEST_VERSION' : lambda a : _resolve_latest_version_id (a ),
524+ 'VISIBILITY' : lambda a : _resolve_visibility (resources_pb2 , a ),
525+ 'CREATED_AT' : lambda a : str (a .created_at .ToDatetime ()) if a .created_at else '' ,
526+ },
527+ )
360528
529+ except click .Abort :
530+ raise
361531 except UserError as e :
362532 click .echo (str (e ), err = True )
363533 raise click .Abort ()
0 commit comments