pltr-cli 0.11.0__py3-none-any.whl → 0.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. pltr/__init__.py +1 -1
  2. pltr/cli.py +40 -0
  3. pltr/commands/admin.py +565 -11
  4. pltr/commands/aip_agents.py +333 -0
  5. pltr/commands/connectivity.py +309 -1
  6. pltr/commands/cp.py +103 -0
  7. pltr/commands/dataset.py +104 -4
  8. pltr/commands/functions.py +503 -0
  9. pltr/commands/language_models.py +515 -0
  10. pltr/commands/mediasets.py +176 -0
  11. pltr/commands/models.py +362 -0
  12. pltr/commands/ontology.py +44 -13
  13. pltr/commands/orchestration.py +167 -11
  14. pltr/commands/project.py +231 -22
  15. pltr/commands/resource.py +416 -17
  16. pltr/commands/space.py +25 -303
  17. pltr/commands/sql.py +54 -7
  18. pltr/commands/streams.py +616 -0
  19. pltr/commands/third_party_applications.py +82 -0
  20. pltr/services/admin.py +331 -3
  21. pltr/services/aip_agents.py +147 -0
  22. pltr/services/base.py +104 -1
  23. pltr/services/connectivity.py +139 -0
  24. pltr/services/copy.py +391 -0
  25. pltr/services/dataset.py +77 -4
  26. pltr/services/folder.py +6 -1
  27. pltr/services/functions.py +223 -0
  28. pltr/services/language_models.py +281 -0
  29. pltr/services/mediasets.py +144 -9
  30. pltr/services/models.py +179 -0
  31. pltr/services/ontology.py +48 -1
  32. pltr/services/orchestration.py +133 -1
  33. pltr/services/project.py +213 -39
  34. pltr/services/resource.py +229 -60
  35. pltr/services/space.py +24 -175
  36. pltr/services/sql.py +44 -20
  37. pltr/services/streams.py +290 -0
  38. pltr/services/third_party_applications.py +53 -0
  39. pltr/utils/formatting.py +195 -1
  40. pltr/utils/pagination.py +325 -0
  41. {pltr_cli-0.11.0.dist-info → pltr_cli-0.13.0.dist-info}/METADATA +55 -4
  42. pltr_cli-0.13.0.dist-info/RECORD +70 -0
  43. {pltr_cli-0.11.0.dist-info → pltr_cli-0.13.0.dist-info}/WHEEL +1 -1
  44. pltr_cli-0.11.0.dist-info/RECORD +0 -55
  45. {pltr_cli-0.11.0.dist-info → pltr_cli-0.13.0.dist-info}/entry_points.txt +0 -0
  46. {pltr_cli-0.11.0.dist-info → pltr_cli-0.13.0.dist-info}/licenses/LICENSE +0 -0
pltr/commands/admin.py CHANGED
@@ -4,7 +4,7 @@ Provides commands for user, group, role, and organization management.
4
4
  """
5
5
 
6
6
  from pathlib import Path
7
- from typing import Optional
7
+ from typing import List, Optional
8
8
 
9
9
  import typer
10
10
  from rich.console import Console
@@ -12,6 +12,7 @@ from rich.console import Console
12
12
  from ..services.admin import AdminService
13
13
  from ..utils.formatting import OutputFormatter
14
14
  from ..utils.progress import SpinnerProgressTracker
15
+ from ..utils.pagination import PaginationConfig
15
16
 
16
17
  # Create the main admin app
17
18
  app = typer.Typer(
@@ -23,12 +24,14 @@ user_app = typer.Typer(name="user", help="User management operations")
23
24
  group_app = typer.Typer(name="group", help="Group management operations")
24
25
  role_app = typer.Typer(name="role", help="Role management operations")
25
26
  org_app = typer.Typer(name="org", help="Organization management operations")
27
+ marking_app = typer.Typer(name="marking", help="Marking management operations")
26
28
 
27
29
  # Add sub-apps to main admin app
28
30
  app.add_typer(user_app, name="user")
29
31
  app.add_typer(group_app, name="group")
30
32
  app.add_typer(role_app, name="role")
31
33
  app.add_typer(org_app, name="org")
34
+ app.add_typer(marking_app, name="marking")
32
35
 
33
36
 
34
37
  # User Management Commands
@@ -44,28 +47,60 @@ def list_users(
44
47
  None, "--output", help="Save results to file"
45
48
  ),
46
49
  page_size: Optional[int] = typer.Option(
47
- None, "--page-size", help="Number of users per page"
50
+ None, "--page-size", help="Number of users per page (default: from settings)"
51
+ ),
52
+ max_pages: Optional[int] = typer.Option(
53
+ 1, "--max-pages", help="Maximum number of pages to fetch (default: 1)"
48
54
  ),
49
55
  page_token: Optional[str] = typer.Option(
50
- None, "--page-token", help="Pagination token from previous response"
56
+ None, "--page-token", help="Page token to resume from previous response"
57
+ ),
58
+ all: bool = typer.Option(
59
+ False, "--all", help="Fetch all available pages (overrides --max-pages)"
51
60
  ),
52
61
  ) -> None:
53
- """List all users in the organization."""
62
+ """
63
+ List users in the organization with pagination support.
64
+
65
+ By default, fetches only the first page of results. Use --all to fetch all users,
66
+ or --max-pages to control how many pages to fetch.
67
+
68
+ Examples:
69
+ # List first page of users (default)
70
+ pltr admin user list
71
+
72
+ # List all users
73
+ pltr admin user list --all
74
+
75
+ # List first 3 pages
76
+ pltr admin user list --max-pages 3
77
+
78
+ # Resume from a specific page
79
+ pltr admin user list --page-token abc123
80
+ """
54
81
  console = Console()
55
82
  formatter = OutputFormatter()
56
83
 
57
84
  try:
58
85
  service = AdminService(profile=profile)
59
86
 
87
+ # Create pagination config
88
+ config = PaginationConfig(
89
+ page_size=page_size,
90
+ max_pages=max_pages,
91
+ page_token=page_token,
92
+ fetch_all=all,
93
+ )
94
+
60
95
  with SpinnerProgressTracker().track_spinner("Fetching users..."):
61
- result = service.list_users(page_size=page_size, page_token=page_token)
96
+ result = service.list_users_paginated(config)
62
97
 
63
- # Format and display results
98
+ # Format and display paginated results
64
99
  if output_file:
65
- formatter.save_to_file(result, output_file, output_format)
66
- console.print(f"Results saved to {output_file}")
100
+ formatter.format_paginated_output(result, output_format, str(output_file))
101
+ console.print(f"[green]Results saved to {output_file}[/green]")
67
102
  else:
68
- formatter.display(result, output_format)
103
+ formatter.format_paginated_output(result, output_format)
69
104
 
70
105
  except Exception as e:
71
106
  console.print(f"[red]Error:[/red] {e}")
@@ -258,6 +293,78 @@ def revoke_user_tokens(
258
293
  raise typer.Exit(code=1)
259
294
 
260
295
 
296
+ @user_app.command("delete")
297
+ def delete_user(
298
+ user_id: str = typer.Argument(..., help="User ID or RID"),
299
+ profile: Optional[str] = typer.Option(
300
+ None, "--profile", help="Auth profile to use"
301
+ ),
302
+ confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation prompt"),
303
+ ) -> None:
304
+ """Delete a specific user."""
305
+ console = Console()
306
+
307
+ # Confirmation prompt
308
+ if not confirm:
309
+ user_confirm = typer.confirm(f"Are you sure you want to delete user {user_id}?")
310
+ if not user_confirm:
311
+ console.print("Operation cancelled.")
312
+ return
313
+
314
+ try:
315
+ service = AdminService(profile=profile)
316
+
317
+ with SpinnerProgressTracker().track_spinner(f"Deleting user {user_id}..."):
318
+ result = service.delete_user(user_id)
319
+
320
+ console.print(f"[green]{result['message']}[/green]")
321
+
322
+ except Exception as e:
323
+ console.print(f"[red]Error:[/red] {e}")
324
+ raise typer.Exit(code=1)
325
+
326
+
327
+ @user_app.command("batch-get")
328
+ def batch_get_users(
329
+ user_ids: List[str] = typer.Argument(
330
+ ..., help="User IDs or RIDs (space-separated, max 500)"
331
+ ),
332
+ profile: Optional[str] = typer.Option(
333
+ None, "--profile", help="Auth profile to use"
334
+ ),
335
+ output_format: str = typer.Option(
336
+ "table", "--format", help="Output format (table, json, csv)"
337
+ ),
338
+ output_file: Optional[Path] = typer.Option(
339
+ None, "--output", help="Save results to file"
340
+ ),
341
+ ) -> None:
342
+ """Batch retrieve multiple users (max 500)."""
343
+ console = Console()
344
+ formatter = OutputFormatter()
345
+
346
+ try:
347
+ service = AdminService(profile=profile)
348
+
349
+ with SpinnerProgressTracker().track_spinner(
350
+ f"Fetching {len(user_ids)} users..."
351
+ ):
352
+ result = service.get_batch_users(user_ids)
353
+
354
+ if output_file:
355
+ formatter.save_to_file(result, output_file, output_format)
356
+ console.print(f"Results saved to {output_file}")
357
+ else:
358
+ formatter.display(result, output_format)
359
+
360
+ except ValueError as e:
361
+ console.print(f"[red]Error:[/red] {e}")
362
+ raise typer.Exit(code=1)
363
+ except Exception as e:
364
+ console.print(f"[red]Error:[/red] {e}")
365
+ raise typer.Exit(code=1)
366
+
367
+
261
368
  # Group Management Commands
262
369
  @group_app.command("list")
263
370
  def list_groups(
@@ -287,12 +394,22 @@ def list_groups(
287
394
  with SpinnerProgressTracker().track_spinner("Fetching groups..."):
288
395
  result = service.list_groups(page_size=page_size, page_token=page_token)
289
396
 
397
+ # Extract data from result (service returns {data: [...], next_page_token: ...})
398
+ groups = result.get("data", [])
399
+ next_token = result.get("next_page_token")
400
+
290
401
  # Format and display results
291
402
  if output_file:
292
- formatter.save_to_file(result, output_file, output_format)
403
+ formatter.save_to_file(groups, output_file, output_format)
293
404
  console.print(f"Results saved to {output_file}")
294
405
  else:
295
- formatter.display(result, output_format)
406
+ formatter.display(groups, output_format)
407
+
408
+ # Show pagination info
409
+ if next_token:
410
+ console.print(
411
+ f"\n[dim]More results available. Use --page-token {next_token} to continue[/dim]"
412
+ )
296
413
 
297
414
  except Exception as e:
298
415
  console.print(f"[red]Error:[/red] {e}")
@@ -456,6 +573,47 @@ def delete_group(
456
573
  raise typer.Exit(code=1)
457
574
 
458
575
 
576
+ @group_app.command("batch-get")
577
+ def batch_get_groups(
578
+ group_ids: List[str] = typer.Argument(
579
+ ..., help="Group IDs or RIDs (space-separated, max 500)"
580
+ ),
581
+ profile: Optional[str] = typer.Option(
582
+ None, "--profile", help="Auth profile to use"
583
+ ),
584
+ output_format: str = typer.Option(
585
+ "table", "--format", help="Output format (table, json, csv)"
586
+ ),
587
+ output_file: Optional[Path] = typer.Option(
588
+ None, "--output", help="Save results to file"
589
+ ),
590
+ ) -> None:
591
+ """Batch retrieve multiple groups (max 500)."""
592
+ console = Console()
593
+ formatter = OutputFormatter()
594
+
595
+ try:
596
+ service = AdminService(profile=profile)
597
+
598
+ with SpinnerProgressTracker().track_spinner(
599
+ f"Fetching {len(group_ids)} groups..."
600
+ ):
601
+ result = service.get_batch_groups(group_ids)
602
+
603
+ if output_file:
604
+ formatter.save_to_file(result, output_file, output_format)
605
+ console.print(f"Results saved to {output_file}")
606
+ else:
607
+ formatter.display(result, output_format)
608
+
609
+ except ValueError as e:
610
+ console.print(f"[red]Error:[/red] {e}")
611
+ raise typer.Exit(code=1)
612
+ except Exception as e:
613
+ console.print(f"[red]Error:[/red] {e}")
614
+ raise typer.Exit(code=1)
615
+
616
+
459
617
  # Role Management Commands
460
618
  @role_app.command("get")
461
619
  def get_role(
@@ -492,6 +650,47 @@ def get_role(
492
650
  raise typer.Exit(code=1)
493
651
 
494
652
 
653
+ @role_app.command("batch-get")
654
+ def batch_get_roles(
655
+ role_ids: List[str] = typer.Argument(
656
+ ..., help="Role IDs or RIDs (space-separated, max 500)"
657
+ ),
658
+ profile: Optional[str] = typer.Option(
659
+ None, "--profile", help="Auth profile to use"
660
+ ),
661
+ output_format: str = typer.Option(
662
+ "table", "--format", help="Output format (table, json, csv)"
663
+ ),
664
+ output_file: Optional[Path] = typer.Option(
665
+ None, "--output", help="Save results to file"
666
+ ),
667
+ ) -> None:
668
+ """Batch retrieve multiple roles (max 500)."""
669
+ console = Console()
670
+ formatter = OutputFormatter()
671
+
672
+ try:
673
+ service = AdminService(profile=profile)
674
+
675
+ with SpinnerProgressTracker().track_spinner(
676
+ f"Fetching {len(role_ids)} roles..."
677
+ ):
678
+ result = service.get_batch_roles(role_ids)
679
+
680
+ if output_file:
681
+ formatter.save_to_file(result, output_file, output_format)
682
+ console.print(f"Results saved to {output_file}")
683
+ else:
684
+ formatter.display(result, output_format)
685
+
686
+ except ValueError as e:
687
+ console.print(f"[red]Error:[/red] {e}")
688
+ raise typer.Exit(code=1)
689
+ except Exception as e:
690
+ console.print(f"[red]Error:[/red] {e}")
691
+ raise typer.Exit(code=1)
692
+
693
+
495
694
  # Organization Management Commands
496
695
  @org_app.command("get")
497
696
  def get_organization(
@@ -528,3 +727,358 @@ def get_organization(
528
727
  except Exception as e:
529
728
  console.print(f"[red]Error:[/red] {e}")
530
729
  raise typer.Exit(code=1)
730
+
731
+
732
+ @org_app.command("create")
733
+ def create_organization(
734
+ name: str = typer.Argument(..., help="Organization name"),
735
+ enrollment_rid: str = typer.Option(..., "--enrollment-rid", help="Enrollment RID"),
736
+ profile: Optional[str] = typer.Option(
737
+ None, "--profile", help="Auth profile to use"
738
+ ),
739
+ admin_ids: Optional[List[str]] = typer.Option(
740
+ None, "--admin-id", help="Admin user IDs (can specify multiple)"
741
+ ),
742
+ output_format: str = typer.Option(
743
+ "table", "--format", help="Output format (table, json, csv)"
744
+ ),
745
+ output_file: Optional[Path] = typer.Option(
746
+ None, "--output", help="Save results to file"
747
+ ),
748
+ ) -> None:
749
+ """Create a new organization."""
750
+ console = Console()
751
+ formatter = OutputFormatter()
752
+
753
+ try:
754
+ service = AdminService(profile=profile)
755
+
756
+ with SpinnerProgressTracker().track_spinner(
757
+ f"Creating organization '{name}'..."
758
+ ):
759
+ result = service.create_organization(
760
+ name=name, enrollment_rid=enrollment_rid, admin_ids=admin_ids
761
+ )
762
+
763
+ if output_file:
764
+ formatter.save_to_file(result, output_file, output_format)
765
+ console.print(f"Results saved to {output_file}")
766
+ else:
767
+ formatter.display(result, output_format)
768
+ console.print(f"[green]Organization '{name}' created successfully[/green]")
769
+
770
+ except Exception as e:
771
+ console.print(f"[red]Error:[/red] {e}")
772
+ raise typer.Exit(code=1)
773
+
774
+
775
+ @org_app.command("replace")
776
+ def replace_organization(
777
+ organization_rid: str = typer.Argument(..., help="Organization RID"),
778
+ name: str = typer.Argument(..., help="New organization name"),
779
+ profile: Optional[str] = typer.Option(
780
+ None, "--profile", help="Auth profile to use"
781
+ ),
782
+ description: Optional[str] = typer.Option(
783
+ None, "--description", help="New organization description"
784
+ ),
785
+ output_format: str = typer.Option(
786
+ "table", "--format", help="Output format (table, json, csv)"
787
+ ),
788
+ output_file: Optional[Path] = typer.Option(
789
+ None, "--output", help="Save results to file"
790
+ ),
791
+ confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation prompt"),
792
+ ) -> None:
793
+ """Replace/update an existing organization."""
794
+ console = Console()
795
+ formatter = OutputFormatter()
796
+
797
+ if not confirm:
798
+ user_confirm = typer.confirm(
799
+ f"Are you sure you want to replace organization {organization_rid}?"
800
+ )
801
+ if not user_confirm:
802
+ console.print("Operation cancelled.")
803
+ return
804
+
805
+ try:
806
+ service = AdminService(profile=profile)
807
+
808
+ with SpinnerProgressTracker().track_spinner(
809
+ f"Replacing organization {organization_rid}..."
810
+ ):
811
+ result = service.replace_organization(
812
+ organization_rid=organization_rid, name=name, description=description
813
+ )
814
+
815
+ if output_file:
816
+ formatter.save_to_file(result, output_file, output_format)
817
+ console.print(f"Results saved to {output_file}")
818
+ else:
819
+ formatter.display(result, output_format)
820
+ console.print(
821
+ f"[green]Organization '{organization_rid}' replaced successfully[/green]"
822
+ )
823
+
824
+ except Exception as e:
825
+ console.print(f"[red]Error:[/red] {e}")
826
+ raise typer.Exit(code=1)
827
+
828
+
829
+ @org_app.command("available-roles")
830
+ def list_available_roles(
831
+ organization_rid: str = typer.Argument(..., help="Organization RID"),
832
+ profile: Optional[str] = typer.Option(
833
+ None, "--profile", help="Auth profile to use"
834
+ ),
835
+ output_format: str = typer.Option(
836
+ "table", "--format", help="Output format (table, json, csv)"
837
+ ),
838
+ output_file: Optional[Path] = typer.Option(
839
+ None, "--output", help="Save results to file"
840
+ ),
841
+ page_size: Optional[int] = typer.Option(
842
+ None, "--page-size", help="Number of roles per page"
843
+ ),
844
+ page_token: Optional[str] = typer.Option(
845
+ None, "--page-token", help="Pagination token from previous response"
846
+ ),
847
+ ) -> None:
848
+ """List available roles for an organization."""
849
+ console = Console()
850
+ formatter = OutputFormatter()
851
+
852
+ try:
853
+ service = AdminService(profile=profile)
854
+
855
+ with SpinnerProgressTracker().track_spinner(
856
+ f"Fetching available roles for organization {organization_rid}..."
857
+ ):
858
+ result = service.list_available_roles(
859
+ organization_rid, page_size=page_size, page_token=page_token
860
+ )
861
+
862
+ if output_file:
863
+ formatter.save_to_file(result, output_file, output_format)
864
+ console.print(f"Results saved to {output_file}")
865
+ else:
866
+ formatter.display(result, output_format)
867
+
868
+ except Exception as e:
869
+ console.print(f"[red]Error:[/red] {e}")
870
+ raise typer.Exit(code=1)
871
+
872
+
873
+ # Marking Management Commands
874
+ @marking_app.command("list")
875
+ def list_markings(
876
+ profile: Optional[str] = typer.Option(
877
+ None, "--profile", help="Auth profile to use"
878
+ ),
879
+ output_format: str = typer.Option(
880
+ "table", "--format", help="Output format (table, json, csv)"
881
+ ),
882
+ output_file: Optional[Path] = typer.Option(
883
+ None, "--output", help="Save results to file"
884
+ ),
885
+ page_size: Optional[int] = typer.Option(
886
+ None, "--page-size", help="Number of markings per page"
887
+ ),
888
+ page_token: Optional[str] = typer.Option(
889
+ None, "--page-token", help="Pagination token from previous response"
890
+ ),
891
+ ) -> None:
892
+ """List all markings."""
893
+ console = Console()
894
+ formatter = OutputFormatter()
895
+
896
+ try:
897
+ service = AdminService(profile=profile)
898
+
899
+ with SpinnerProgressTracker().track_spinner("Fetching markings..."):
900
+ result = service.list_markings(page_size=page_size, page_token=page_token)
901
+
902
+ if output_file:
903
+ formatter.save_to_file(result, output_file, output_format)
904
+ console.print(f"Results saved to {output_file}")
905
+ else:
906
+ formatter.display(result, output_format)
907
+
908
+ except Exception as e:
909
+ console.print(f"[red]Error:[/red] {e}")
910
+ raise typer.Exit(code=1)
911
+
912
+
913
+ @marking_app.command("get")
914
+ def get_marking(
915
+ marking_id: str = typer.Argument(..., help="Marking ID"),
916
+ profile: Optional[str] = typer.Option(
917
+ None, "--profile", help="Auth profile to use"
918
+ ),
919
+ output_format: str = typer.Option(
920
+ "table", "--format", help="Output format (table, json, csv)"
921
+ ),
922
+ output_file: Optional[Path] = typer.Option(
923
+ None, "--output", help="Save results to file"
924
+ ),
925
+ ) -> None:
926
+ """Get information about a specific marking."""
927
+ console = Console()
928
+ formatter = OutputFormatter()
929
+
930
+ try:
931
+ service = AdminService(profile=profile)
932
+
933
+ with SpinnerProgressTracker().track_spinner(
934
+ f"Fetching marking {marking_id}..."
935
+ ):
936
+ result = service.get_marking(marking_id)
937
+
938
+ if output_file:
939
+ formatter.save_to_file(result, output_file, output_format)
940
+ console.print(f"Results saved to {output_file}")
941
+ else:
942
+ formatter.display(result, output_format)
943
+
944
+ except Exception as e:
945
+ console.print(f"[red]Error:[/red] {e}")
946
+ raise typer.Exit(code=1)
947
+
948
+
949
+ @marking_app.command("batch-get")
950
+ def batch_get_markings(
951
+ marking_ids: List[str] = typer.Argument(
952
+ ..., help="Marking IDs (space-separated, max 500)"
953
+ ),
954
+ profile: Optional[str] = typer.Option(
955
+ None, "--profile", help="Auth profile to use"
956
+ ),
957
+ output_format: str = typer.Option(
958
+ "table", "--format", help="Output format (table, json, csv)"
959
+ ),
960
+ output_file: Optional[Path] = typer.Option(
961
+ None, "--output", help="Save results to file"
962
+ ),
963
+ ) -> None:
964
+ """Batch retrieve multiple markings (max 500)."""
965
+ console = Console()
966
+ formatter = OutputFormatter()
967
+
968
+ try:
969
+ service = AdminService(profile=profile)
970
+
971
+ with SpinnerProgressTracker().track_spinner(
972
+ f"Fetching {len(marking_ids)} markings..."
973
+ ):
974
+ result = service.get_batch_markings(marking_ids)
975
+
976
+ if output_file:
977
+ formatter.save_to_file(result, output_file, output_format)
978
+ console.print(f"Results saved to {output_file}")
979
+ else:
980
+ formatter.display(result, output_format)
981
+
982
+ except ValueError as e:
983
+ console.print(f"[red]Error:[/red] {e}")
984
+ raise typer.Exit(code=1)
985
+ except Exception as e:
986
+ console.print(f"[red]Error:[/red] {e}")
987
+ raise typer.Exit(code=1)
988
+
989
+
990
+ @marking_app.command("create")
991
+ def create_marking(
992
+ name: str = typer.Argument(..., help="Marking name"),
993
+ profile: Optional[str] = typer.Option(
994
+ None, "--profile", help="Auth profile to use"
995
+ ),
996
+ description: Optional[str] = typer.Option(
997
+ None, "--description", help="Marking description"
998
+ ),
999
+ category_id: Optional[str] = typer.Option(
1000
+ None, "--category-id", help="Category ID for the marking"
1001
+ ),
1002
+ output_format: str = typer.Option(
1003
+ "table", "--format", help="Output format (table, json, csv)"
1004
+ ),
1005
+ output_file: Optional[Path] = typer.Option(
1006
+ None, "--output", help="Save results to file"
1007
+ ),
1008
+ ) -> None:
1009
+ """Create a new marking."""
1010
+ console = Console()
1011
+ formatter = OutputFormatter()
1012
+
1013
+ try:
1014
+ service = AdminService(profile=profile)
1015
+
1016
+ with SpinnerProgressTracker().track_spinner(f"Creating marking '{name}'..."):
1017
+ result = service.create_marking(
1018
+ name=name, description=description, category_id=category_id
1019
+ )
1020
+
1021
+ if output_file:
1022
+ formatter.save_to_file(result, output_file, output_format)
1023
+ console.print(f"Results saved to {output_file}")
1024
+ else:
1025
+ formatter.display(result, output_format)
1026
+ console.print(f"[green]Marking '{name}' created successfully[/green]")
1027
+
1028
+ except Exception as e:
1029
+ console.print(f"[red]Error:[/red] {e}")
1030
+ raise typer.Exit(code=1)
1031
+
1032
+
1033
+ @marking_app.command("replace")
1034
+ def replace_marking(
1035
+ marking_id: str = typer.Argument(..., help="Marking ID"),
1036
+ name: str = typer.Argument(..., help="New marking name"),
1037
+ profile: Optional[str] = typer.Option(
1038
+ None, "--profile", help="Auth profile to use"
1039
+ ),
1040
+ description: Optional[str] = typer.Option(
1041
+ None, "--description", help="New marking description"
1042
+ ),
1043
+ output_format: str = typer.Option(
1044
+ "table", "--format", help="Output format (table, json, csv)"
1045
+ ),
1046
+ output_file: Optional[Path] = typer.Option(
1047
+ None, "--output", help="Save results to file"
1048
+ ),
1049
+ confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation prompt"),
1050
+ ) -> None:
1051
+ """Replace/update an existing marking."""
1052
+ console = Console()
1053
+ formatter = OutputFormatter()
1054
+
1055
+ if not confirm:
1056
+ user_confirm = typer.confirm(
1057
+ f"Are you sure you want to replace marking {marking_id}?"
1058
+ )
1059
+ if not user_confirm:
1060
+ console.print("Operation cancelled.")
1061
+ return
1062
+
1063
+ try:
1064
+ service = AdminService(profile=profile)
1065
+
1066
+ with SpinnerProgressTracker().track_spinner(
1067
+ f"Replacing marking {marking_id}..."
1068
+ ):
1069
+ result = service.replace_marking(
1070
+ marking_id=marking_id, name=name, description=description
1071
+ )
1072
+
1073
+ if output_file:
1074
+ formatter.save_to_file(result, output_file, output_format)
1075
+ console.print(f"Results saved to {output_file}")
1076
+ else:
1077
+ formatter.display(result, output_format)
1078
+ console.print(
1079
+ f"[green]Marking '{marking_id}' replaced successfully[/green]"
1080
+ )
1081
+
1082
+ except Exception as e:
1083
+ console.print(f"[red]Error:[/red] {e}")
1084
+ raise typer.Exit(code=1)