pltr-cli 0.10.0__py3-none-any.whl → 0.12.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.
pltr/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.10.0"
1
+ __version__ = "0.12.0"
pltr/cli.py CHANGED
@@ -24,7 +24,10 @@ from pltr.commands import (
24
24
  alias,
25
25
  mediasets,
26
26
  connectivity,
27
+ third_party_applications,
28
+ aip_agents,
27
29
  )
30
+ from pltr.commands.cp import cp_command
28
31
 
29
32
  app = typer.Typer(
30
33
  name="pltr",
@@ -54,6 +57,16 @@ app.add_typer(
54
57
  app.add_typer(
55
58
  connectivity.app, name="connectivity", help="Manage connections and data imports"
56
59
  )
60
+ app.add_typer(
61
+ third_party_applications.app,
62
+ name="third-party-apps",
63
+ help="Manage third-party applications",
64
+ )
65
+ app.add_typer(
66
+ aip_agents.app,
67
+ name="aip-agents",
68
+ help="Manage AIP Agents, sessions, and versions",
69
+ )
57
70
  app.add_typer(
58
71
  admin.app,
59
72
  name="admin",
@@ -62,6 +75,9 @@ app.add_typer(
62
75
  app.add_typer(shell.shell_app, name="shell", help="Interactive shell mode")
63
76
  app.add_typer(completion.app, name="completion", help="Manage shell completions")
64
77
  app.add_typer(alias.app, name="alias", help="Manage command aliases")
78
+ app.command("cp", help="Copy datasets or folders into another Compass folder")(
79
+ cp_command
80
+ )
65
81
 
66
82
 
67
83
  def version_callback(value: bool):
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(
@@ -456,6 +563,47 @@ def delete_group(
456
563
  raise typer.Exit(code=1)
457
564
 
458
565
 
566
+ @group_app.command("batch-get")
567
+ def batch_get_groups(
568
+ group_ids: List[str] = typer.Argument(
569
+ ..., help="Group IDs or RIDs (space-separated, max 500)"
570
+ ),
571
+ profile: Optional[str] = typer.Option(
572
+ None, "--profile", help="Auth profile to use"
573
+ ),
574
+ output_format: str = typer.Option(
575
+ "table", "--format", help="Output format (table, json, csv)"
576
+ ),
577
+ output_file: Optional[Path] = typer.Option(
578
+ None, "--output", help="Save results to file"
579
+ ),
580
+ ) -> None:
581
+ """Batch retrieve multiple groups (max 500)."""
582
+ console = Console()
583
+ formatter = OutputFormatter()
584
+
585
+ try:
586
+ service = AdminService(profile=profile)
587
+
588
+ with SpinnerProgressTracker().track_spinner(
589
+ f"Fetching {len(group_ids)} groups..."
590
+ ):
591
+ result = service.get_batch_groups(group_ids)
592
+
593
+ if output_file:
594
+ formatter.save_to_file(result, output_file, output_format)
595
+ console.print(f"Results saved to {output_file}")
596
+ else:
597
+ formatter.display(result, output_format)
598
+
599
+ except ValueError as e:
600
+ console.print(f"[red]Error:[/red] {e}")
601
+ raise typer.Exit(code=1)
602
+ except Exception as e:
603
+ console.print(f"[red]Error:[/red] {e}")
604
+ raise typer.Exit(code=1)
605
+
606
+
459
607
  # Role Management Commands
460
608
  @role_app.command("get")
461
609
  def get_role(
@@ -492,6 +640,47 @@ def get_role(
492
640
  raise typer.Exit(code=1)
493
641
 
494
642
 
643
+ @role_app.command("batch-get")
644
+ def batch_get_roles(
645
+ role_ids: List[str] = typer.Argument(
646
+ ..., help="Role IDs or RIDs (space-separated, max 500)"
647
+ ),
648
+ profile: Optional[str] = typer.Option(
649
+ None, "--profile", help="Auth profile to use"
650
+ ),
651
+ output_format: str = typer.Option(
652
+ "table", "--format", help="Output format (table, json, csv)"
653
+ ),
654
+ output_file: Optional[Path] = typer.Option(
655
+ None, "--output", help="Save results to file"
656
+ ),
657
+ ) -> None:
658
+ """Batch retrieve multiple roles (max 500)."""
659
+ console = Console()
660
+ formatter = OutputFormatter()
661
+
662
+ try:
663
+ service = AdminService(profile=profile)
664
+
665
+ with SpinnerProgressTracker().track_spinner(
666
+ f"Fetching {len(role_ids)} roles..."
667
+ ):
668
+ result = service.get_batch_roles(role_ids)
669
+
670
+ if output_file:
671
+ formatter.save_to_file(result, output_file, output_format)
672
+ console.print(f"Results saved to {output_file}")
673
+ else:
674
+ formatter.display(result, output_format)
675
+
676
+ except ValueError as e:
677
+ console.print(f"[red]Error:[/red] {e}")
678
+ raise typer.Exit(code=1)
679
+ except Exception as e:
680
+ console.print(f"[red]Error:[/red] {e}")
681
+ raise typer.Exit(code=1)
682
+
683
+
495
684
  # Organization Management Commands
496
685
  @org_app.command("get")
497
686
  def get_organization(
@@ -528,3 +717,358 @@ def get_organization(
528
717
  except Exception as e:
529
718
  console.print(f"[red]Error:[/red] {e}")
530
719
  raise typer.Exit(code=1)
720
+
721
+
722
+ @org_app.command("create")
723
+ def create_organization(
724
+ name: str = typer.Argument(..., help="Organization name"),
725
+ enrollment_rid: str = typer.Option(..., "--enrollment-rid", help="Enrollment RID"),
726
+ profile: Optional[str] = typer.Option(
727
+ None, "--profile", help="Auth profile to use"
728
+ ),
729
+ admin_ids: Optional[List[str]] = typer.Option(
730
+ None, "--admin-id", help="Admin user IDs (can specify multiple)"
731
+ ),
732
+ output_format: str = typer.Option(
733
+ "table", "--format", help="Output format (table, json, csv)"
734
+ ),
735
+ output_file: Optional[Path] = typer.Option(
736
+ None, "--output", help="Save results to file"
737
+ ),
738
+ ) -> None:
739
+ """Create a new organization."""
740
+ console = Console()
741
+ formatter = OutputFormatter()
742
+
743
+ try:
744
+ service = AdminService(profile=profile)
745
+
746
+ with SpinnerProgressTracker().track_spinner(
747
+ f"Creating organization '{name}'..."
748
+ ):
749
+ result = service.create_organization(
750
+ name=name, enrollment_rid=enrollment_rid, admin_ids=admin_ids
751
+ )
752
+
753
+ if output_file:
754
+ formatter.save_to_file(result, output_file, output_format)
755
+ console.print(f"Results saved to {output_file}")
756
+ else:
757
+ formatter.display(result, output_format)
758
+ console.print(f"[green]Organization '{name}' created successfully[/green]")
759
+
760
+ except Exception as e:
761
+ console.print(f"[red]Error:[/red] {e}")
762
+ raise typer.Exit(code=1)
763
+
764
+
765
+ @org_app.command("replace")
766
+ def replace_organization(
767
+ organization_rid: str = typer.Argument(..., help="Organization RID"),
768
+ name: str = typer.Argument(..., help="New organization name"),
769
+ profile: Optional[str] = typer.Option(
770
+ None, "--profile", help="Auth profile to use"
771
+ ),
772
+ description: Optional[str] = typer.Option(
773
+ None, "--description", help="New organization description"
774
+ ),
775
+ output_format: str = typer.Option(
776
+ "table", "--format", help="Output format (table, json, csv)"
777
+ ),
778
+ output_file: Optional[Path] = typer.Option(
779
+ None, "--output", help="Save results to file"
780
+ ),
781
+ confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation prompt"),
782
+ ) -> None:
783
+ """Replace/update an existing organization."""
784
+ console = Console()
785
+ formatter = OutputFormatter()
786
+
787
+ if not confirm:
788
+ user_confirm = typer.confirm(
789
+ f"Are you sure you want to replace organization {organization_rid}?"
790
+ )
791
+ if not user_confirm:
792
+ console.print("Operation cancelled.")
793
+ return
794
+
795
+ try:
796
+ service = AdminService(profile=profile)
797
+
798
+ with SpinnerProgressTracker().track_spinner(
799
+ f"Replacing organization {organization_rid}..."
800
+ ):
801
+ result = service.replace_organization(
802
+ organization_rid=organization_rid, name=name, description=description
803
+ )
804
+
805
+ if output_file:
806
+ formatter.save_to_file(result, output_file, output_format)
807
+ console.print(f"Results saved to {output_file}")
808
+ else:
809
+ formatter.display(result, output_format)
810
+ console.print(
811
+ f"[green]Organization '{organization_rid}' replaced successfully[/green]"
812
+ )
813
+
814
+ except Exception as e:
815
+ console.print(f"[red]Error:[/red] {e}")
816
+ raise typer.Exit(code=1)
817
+
818
+
819
+ @org_app.command("available-roles")
820
+ def list_available_roles(
821
+ organization_rid: str = typer.Argument(..., help="Organization RID"),
822
+ profile: Optional[str] = typer.Option(
823
+ None, "--profile", help="Auth profile to use"
824
+ ),
825
+ output_format: str = typer.Option(
826
+ "table", "--format", help="Output format (table, json, csv)"
827
+ ),
828
+ output_file: Optional[Path] = typer.Option(
829
+ None, "--output", help="Save results to file"
830
+ ),
831
+ page_size: Optional[int] = typer.Option(
832
+ None, "--page-size", help="Number of roles per page"
833
+ ),
834
+ page_token: Optional[str] = typer.Option(
835
+ None, "--page-token", help="Pagination token from previous response"
836
+ ),
837
+ ) -> None:
838
+ """List available roles for an organization."""
839
+ console = Console()
840
+ formatter = OutputFormatter()
841
+
842
+ try:
843
+ service = AdminService(profile=profile)
844
+
845
+ with SpinnerProgressTracker().track_spinner(
846
+ f"Fetching available roles for organization {organization_rid}..."
847
+ ):
848
+ result = service.list_available_roles(
849
+ organization_rid, page_size=page_size, page_token=page_token
850
+ )
851
+
852
+ if output_file:
853
+ formatter.save_to_file(result, output_file, output_format)
854
+ console.print(f"Results saved to {output_file}")
855
+ else:
856
+ formatter.display(result, output_format)
857
+
858
+ except Exception as e:
859
+ console.print(f"[red]Error:[/red] {e}")
860
+ raise typer.Exit(code=1)
861
+
862
+
863
+ # Marking Management Commands
864
+ @marking_app.command("list")
865
+ def list_markings(
866
+ profile: Optional[str] = typer.Option(
867
+ None, "--profile", help="Auth profile to use"
868
+ ),
869
+ output_format: str = typer.Option(
870
+ "table", "--format", help="Output format (table, json, csv)"
871
+ ),
872
+ output_file: Optional[Path] = typer.Option(
873
+ None, "--output", help="Save results to file"
874
+ ),
875
+ page_size: Optional[int] = typer.Option(
876
+ None, "--page-size", help="Number of markings per page"
877
+ ),
878
+ page_token: Optional[str] = typer.Option(
879
+ None, "--page-token", help="Pagination token from previous response"
880
+ ),
881
+ ) -> None:
882
+ """List all markings."""
883
+ console = Console()
884
+ formatter = OutputFormatter()
885
+
886
+ try:
887
+ service = AdminService(profile=profile)
888
+
889
+ with SpinnerProgressTracker().track_spinner("Fetching markings..."):
890
+ result = service.list_markings(page_size=page_size, page_token=page_token)
891
+
892
+ if output_file:
893
+ formatter.save_to_file(result, output_file, output_format)
894
+ console.print(f"Results saved to {output_file}")
895
+ else:
896
+ formatter.display(result, output_format)
897
+
898
+ except Exception as e:
899
+ console.print(f"[red]Error:[/red] {e}")
900
+ raise typer.Exit(code=1)
901
+
902
+
903
+ @marking_app.command("get")
904
+ def get_marking(
905
+ marking_id: str = typer.Argument(..., help="Marking ID"),
906
+ profile: Optional[str] = typer.Option(
907
+ None, "--profile", help="Auth profile to use"
908
+ ),
909
+ output_format: str = typer.Option(
910
+ "table", "--format", help="Output format (table, json, csv)"
911
+ ),
912
+ output_file: Optional[Path] = typer.Option(
913
+ None, "--output", help="Save results to file"
914
+ ),
915
+ ) -> None:
916
+ """Get information about a specific marking."""
917
+ console = Console()
918
+ formatter = OutputFormatter()
919
+
920
+ try:
921
+ service = AdminService(profile=profile)
922
+
923
+ with SpinnerProgressTracker().track_spinner(
924
+ f"Fetching marking {marking_id}..."
925
+ ):
926
+ result = service.get_marking(marking_id)
927
+
928
+ if output_file:
929
+ formatter.save_to_file(result, output_file, output_format)
930
+ console.print(f"Results saved to {output_file}")
931
+ else:
932
+ formatter.display(result, output_format)
933
+
934
+ except Exception as e:
935
+ console.print(f"[red]Error:[/red] {e}")
936
+ raise typer.Exit(code=1)
937
+
938
+
939
+ @marking_app.command("batch-get")
940
+ def batch_get_markings(
941
+ marking_ids: List[str] = typer.Argument(
942
+ ..., help="Marking IDs (space-separated, max 500)"
943
+ ),
944
+ profile: Optional[str] = typer.Option(
945
+ None, "--profile", help="Auth profile to use"
946
+ ),
947
+ output_format: str = typer.Option(
948
+ "table", "--format", help="Output format (table, json, csv)"
949
+ ),
950
+ output_file: Optional[Path] = typer.Option(
951
+ None, "--output", help="Save results to file"
952
+ ),
953
+ ) -> None:
954
+ """Batch retrieve multiple markings (max 500)."""
955
+ console = Console()
956
+ formatter = OutputFormatter()
957
+
958
+ try:
959
+ service = AdminService(profile=profile)
960
+
961
+ with SpinnerProgressTracker().track_spinner(
962
+ f"Fetching {len(marking_ids)} markings..."
963
+ ):
964
+ result = service.get_batch_markings(marking_ids)
965
+
966
+ if output_file:
967
+ formatter.save_to_file(result, output_file, output_format)
968
+ console.print(f"Results saved to {output_file}")
969
+ else:
970
+ formatter.display(result, output_format)
971
+
972
+ except ValueError as e:
973
+ console.print(f"[red]Error:[/red] {e}")
974
+ raise typer.Exit(code=1)
975
+ except Exception as e:
976
+ console.print(f"[red]Error:[/red] {e}")
977
+ raise typer.Exit(code=1)
978
+
979
+
980
+ @marking_app.command("create")
981
+ def create_marking(
982
+ name: str = typer.Argument(..., help="Marking name"),
983
+ profile: Optional[str] = typer.Option(
984
+ None, "--profile", help="Auth profile to use"
985
+ ),
986
+ description: Optional[str] = typer.Option(
987
+ None, "--description", help="Marking description"
988
+ ),
989
+ category_id: Optional[str] = typer.Option(
990
+ None, "--category-id", help="Category ID for the marking"
991
+ ),
992
+ output_format: str = typer.Option(
993
+ "table", "--format", help="Output format (table, json, csv)"
994
+ ),
995
+ output_file: Optional[Path] = typer.Option(
996
+ None, "--output", help="Save results to file"
997
+ ),
998
+ ) -> None:
999
+ """Create a new marking."""
1000
+ console = Console()
1001
+ formatter = OutputFormatter()
1002
+
1003
+ try:
1004
+ service = AdminService(profile=profile)
1005
+
1006
+ with SpinnerProgressTracker().track_spinner(f"Creating marking '{name}'..."):
1007
+ result = service.create_marking(
1008
+ name=name, description=description, category_id=category_id
1009
+ )
1010
+
1011
+ if output_file:
1012
+ formatter.save_to_file(result, output_file, output_format)
1013
+ console.print(f"Results saved to {output_file}")
1014
+ else:
1015
+ formatter.display(result, output_format)
1016
+ console.print(f"[green]Marking '{name}' created successfully[/green]")
1017
+
1018
+ except Exception as e:
1019
+ console.print(f"[red]Error:[/red] {e}")
1020
+ raise typer.Exit(code=1)
1021
+
1022
+
1023
+ @marking_app.command("replace")
1024
+ def replace_marking(
1025
+ marking_id: str = typer.Argument(..., help="Marking ID"),
1026
+ name: str = typer.Argument(..., help="New marking name"),
1027
+ profile: Optional[str] = typer.Option(
1028
+ None, "--profile", help="Auth profile to use"
1029
+ ),
1030
+ description: Optional[str] = typer.Option(
1031
+ None, "--description", help="New marking description"
1032
+ ),
1033
+ output_format: str = typer.Option(
1034
+ "table", "--format", help="Output format (table, json, csv)"
1035
+ ),
1036
+ output_file: Optional[Path] = typer.Option(
1037
+ None, "--output", help="Save results to file"
1038
+ ),
1039
+ confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation prompt"),
1040
+ ) -> None:
1041
+ """Replace/update an existing marking."""
1042
+ console = Console()
1043
+ formatter = OutputFormatter()
1044
+
1045
+ if not confirm:
1046
+ user_confirm = typer.confirm(
1047
+ f"Are you sure you want to replace marking {marking_id}?"
1048
+ )
1049
+ if not user_confirm:
1050
+ console.print("Operation cancelled.")
1051
+ return
1052
+
1053
+ try:
1054
+ service = AdminService(profile=profile)
1055
+
1056
+ with SpinnerProgressTracker().track_spinner(
1057
+ f"Replacing marking {marking_id}..."
1058
+ ):
1059
+ result = service.replace_marking(
1060
+ marking_id=marking_id, name=name, description=description
1061
+ )
1062
+
1063
+ if output_file:
1064
+ formatter.save_to_file(result, output_file, output_format)
1065
+ console.print(f"Results saved to {output_file}")
1066
+ else:
1067
+ formatter.display(result, output_format)
1068
+ console.print(
1069
+ f"[green]Marking '{marking_id}' replaced successfully[/green]"
1070
+ )
1071
+
1072
+ except Exception as e:
1073
+ console.print(f"[red]Error:[/red] {e}")
1074
+ raise typer.Exit(code=1)