pragmatiks-cli 0.5.1__py3-none-any.whl → 0.12.5__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.
@@ -1,6 +1,6 @@
1
1
  """Provider management commands.
2
2
 
3
- Commands for scaffolding, syncing, and pushing Pragmatiks providers to the platform.
3
+ Commands for scaffolding, building, and deploying Pragmatiks providers to the platform.
4
4
  """
5
5
 
6
6
  import io
@@ -15,22 +15,21 @@ import copier
15
15
  import httpx
16
16
  import typer
17
17
  from pragma_sdk import (
18
- BuildResult,
18
+ BuildInfo,
19
19
  BuildStatus,
20
- Config,
21
20
  DeploymentStatus,
22
21
  PragmaClient,
23
22
  ProviderDeleteResult,
24
23
  ProviderInfo,
25
24
  PushResult,
26
- Resource,
27
25
  )
28
- from pragma_sdk.provider import discover_resources
29
26
  from rich.console import Console
30
27
  from rich.progress import Progress, SpinnerColumn, TextColumn
31
28
  from rich.table import Table
32
29
 
33
30
  from pragma_cli import get_client
31
+ from pragma_cli.commands.completions import completion_provider_ids, completion_provider_versions
32
+ from pragma_cli.helpers import OutputFormat, output_data
34
33
 
35
34
 
36
35
  app = typer.Typer(help="Provider management commands")
@@ -119,7 +118,9 @@ def get_template_source() -> str:
119
118
 
120
119
 
121
120
  @app.command("list")
122
- def list_providers():
121
+ def list_providers(
122
+ output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE,
123
+ ):
123
124
  """List all deployed providers.
124
125
 
125
126
  Shows providers with their deployment status. Displays:
@@ -128,8 +129,9 @@ def list_providers():
128
129
  - Status (running/stopped)
129
130
  - Last deployed timestamp
130
131
 
131
- Example:
132
- pragma provider list
132
+ Examples:
133
+ pragma providers list
134
+ pragma providers list -o json
133
135
 
134
136
  Raises:
135
137
  typer.Exit: If authentication is missing or API call fails.
@@ -160,7 +162,12 @@ def list_providers():
160
162
  console.print("[dim]No providers found.[/dim]")
161
163
  return
162
164
 
163
- _print_providers_table(providers)
165
+ if output == OutputFormat.TABLE:
166
+ _print_providers_table(providers)
167
+ else:
168
+ # Convert Pydantic models to dicts for JSON/YAML output
169
+ data = [p.model_dump(mode="json") for p in providers]
170
+ output_data(data, output)
164
171
 
165
172
 
166
173
  def _print_providers_table(providers: list[ProviderInfo]) -> None:
@@ -178,11 +185,7 @@ def _print_providers_table(providers: list[ProviderInfo]) -> None:
178
185
  for provider in providers:
179
186
  status = _format_deployment_status(provider.deployment_status)
180
187
  version = provider.current_version or "[dim]never deployed[/dim]"
181
- updated = (
182
- provider.updated_at.strftime("%Y-%m-%d %H:%M:%S")
183
- if provider.updated_at
184
- else "[dim]-[/dim]"
185
- )
188
+ updated = provider.updated_at.strftime("%Y-%m-%d %H:%M:%S") if provider.updated_at else "[dim]-[/dim]"
186
189
 
187
190
  table.add_row(provider.provider_id, version, status, updated)
188
191
 
@@ -248,9 +251,9 @@ def init(
248
251
  - mise.toml for tool management
249
252
 
250
253
  Example:
251
- pragma provider init mycompany
252
- pragma provider init postgres --output ./providers/postgres
253
- pragma provider init mycompany --defaults --description "My provider"
254
+ pragma providers init mycompany
255
+ pragma providers init postgres --output ./providers/postgres
256
+ pragma providers init mycompany --defaults --description "My provider"
254
257
 
255
258
  Raises:
256
259
  typer.Exit: If directory already exists or template copy fails.
@@ -303,7 +306,7 @@ def init(
303
306
  typer.echo(" copier update")
304
307
  typer.echo("")
305
308
  typer.echo("When ready to deploy:")
306
- typer.echo(" pragma provider push")
309
+ typer.echo(" pragma providers push")
307
310
 
308
311
 
309
312
  @app.command()
@@ -319,8 +322,8 @@ def update(
319
322
  incorporating template updates.
320
323
 
321
324
  Example:
322
- pragma provider update
323
- pragma provider update ./my-provider
325
+ pragma providers update
326
+ pragma providers update ./my-provider
324
327
 
325
328
  Raises:
326
329
  typer.Exit: If directory is not a Copier project or update fails.
@@ -374,25 +377,25 @@ def push(
374
377
  a container image.
375
378
 
376
379
  Build only:
377
- pragma provider push
380
+ pragma providers push
378
381
  -> Uploads code and waits for build
379
382
 
380
383
  Build and deploy:
381
- pragma provider push --deploy
384
+ pragma providers push --deploy
382
385
  -> Uploads code, builds, and deploys
383
386
 
384
387
  Async build:
385
- pragma provider push --no-wait
388
+ pragma providers push --no-wait
386
389
  -> Uploads code and returns immediately
387
390
 
388
391
  With logs:
389
- pragma provider push --logs
392
+ pragma providers push --logs
390
393
  -> Shows build output in real-time
391
394
 
392
395
  Example:
393
- pragma provider push
394
- pragma provider push --deploy
395
- pragma provider push --logs --deploy
396
+ pragma providers push
397
+ pragma providers push --deploy
398
+ pragma providers push --logs --deploy
396
399
 
397
400
  Raises:
398
401
  typer.Exit: If provider detection fails or build fails.
@@ -428,7 +431,7 @@ def push(
428
431
  client = get_client()
429
432
 
430
433
  if client._auth is None:
431
- console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
434
+ console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
432
435
  raise typer.Exit(1)
433
436
 
434
437
  try:
@@ -437,18 +440,14 @@ def push(
437
440
  if not wait:
438
441
  console.print()
439
442
  console.print("[dim]Build running in background. Check status with:[/dim]")
440
- console.print(f" pragma provider status {provider_id} --job {push_result.job_name}")
443
+ console.print(f" pragma providers builds {provider_id} {push_result.version}")
441
444
  return
442
445
 
443
- build_result = _wait_for_build(client, provider_id, push_result.job_name, logs)
446
+ _wait_for_build(client, provider_id, push_result.version, logs)
444
447
 
445
448
  if deploy:
446
- if not build_result.image:
447
- console.print("[red]Error:[/red] Build succeeded but no image was produced")
448
- raise typer.Exit(1)
449
-
450
449
  console.print()
451
- _deploy_provider(client, provider_id, build_result.image)
450
+ _deploy_provider(client, provider_id, push_result.version)
452
451
  except Exception as e:
453
452
  if isinstance(e, typer.Exit):
454
453
  raise
@@ -465,7 +464,7 @@ def _upload_code(client: PragmaClient, provider_id: str, tarball: bytes) -> Push
465
464
  tarball: Gzipped tarball bytes of provider source.
466
465
 
467
466
  Returns:
468
- PushResult with build job details.
467
+ PushResult with build details including version.
469
468
  """
470
469
  with Progress(
471
470
  SpinnerColumn(),
@@ -476,42 +475,42 @@ def _upload_code(client: PragmaClient, provider_id: str, tarball: bytes) -> Push
476
475
  progress.add_task("Uploading code...", total=None)
477
476
  push_result = client.push_provider(provider_id, tarball)
478
477
 
479
- console.print(f"[green]Build started:[/green] {push_result.job_name}")
478
+ console.print(f"[green]Build started:[/green] {push_result.version}")
480
479
  return push_result
481
480
 
482
481
 
483
482
  def _wait_for_build(
484
483
  client: PragmaClient,
485
484
  provider_id: str,
486
- job_name: str,
485
+ version: str,
487
486
  logs: bool,
488
- ) -> BuildResult:
487
+ ) -> BuildInfo:
489
488
  """Wait for build to complete, optionally streaming logs.
490
489
 
491
490
  Args:
492
491
  client: SDK client instance.
493
492
  provider_id: Provider identifier.
494
- job_name: Build job name.
493
+ version: CalVer version string.
495
494
  logs: Whether to stream build logs.
496
495
 
497
496
  Returns:
498
- Final BuildResult.
497
+ Final BuildInfo.
499
498
 
500
499
  Raises:
501
500
  typer.Exit: On build failure or timeout.
502
501
  """
503
502
  if logs:
504
- _stream_build_logs(client, provider_id, job_name)
503
+ _stream_build_logs(client, provider_id, version)
505
504
  else:
506
- build_result = _poll_build_status(client, provider_id, job_name)
505
+ build_result = _poll_build_status(client, provider_id, version)
507
506
 
508
507
  if build_result.status == BuildStatus.FAILED:
509
508
  console.print(f"[red]Build failed:[/red] {build_result.error_message}")
510
509
  raise typer.Exit(1)
511
510
 
512
- console.print(f"[green]Build successful:[/green] {build_result.image}")
511
+ console.print(f"[green]Build successful:[/green] {build_result.version}")
513
512
 
514
- final_build = client.get_build_status(provider_id, job_name)
513
+ final_build = client.get_build_status(provider_id, version)
515
514
 
516
515
  if final_build.status != BuildStatus.SUCCESS:
517
516
  console.print(f"[red]Build failed:[/red] {final_build.error_message}")
@@ -520,16 +519,16 @@ def _wait_for_build(
520
519
  return final_build
521
520
 
522
521
 
523
- def _poll_build_status(client: PragmaClient, provider_id: str, job_name: str) -> BuildResult:
522
+ def _poll_build_status(client: PragmaClient, provider_id: str, version: str) -> BuildInfo:
524
523
  """Poll build status until completion or timeout.
525
524
 
526
525
  Args:
527
526
  client: SDK client instance.
528
527
  provider_id: Provider identifier.
529
- job_name: Build job name.
528
+ version: CalVer version string.
530
529
 
531
530
  Returns:
532
- Final BuildResult.
531
+ Final BuildInfo.
533
532
 
534
533
  Raises:
535
534
  typer.Exit: If build times out.
@@ -545,7 +544,7 @@ def _poll_build_status(client: PragmaClient, provider_id: str, job_name: str) ->
545
544
  task = progress.add_task("Building...", total=None)
546
545
 
547
546
  while True:
548
- build_result = client.get_build_status(provider_id, job_name)
547
+ build_result = client.get_build_status(provider_id, version)
549
548
 
550
549
  if build_result.status in (BuildStatus.SUCCESS, BuildStatus.FAILED):
551
550
  return build_result
@@ -559,37 +558,37 @@ def _poll_build_status(client: PragmaClient, provider_id: str, job_name: str) ->
559
558
  time.sleep(BUILD_POLL_INTERVAL)
560
559
 
561
560
 
562
- def _stream_build_logs(client: PragmaClient, provider_id: str, job_name: str) -> None:
561
+ def _stream_build_logs(client: PragmaClient, provider_id: str, version: str) -> None:
563
562
  """Stream build logs to console.
564
563
 
565
564
  Args:
566
565
  client: SDK client instance.
567
566
  provider_id: Provider identifier.
568
- job_name: Build job name.
567
+ version: CalVer version string.
569
568
  """
570
569
  console.print()
571
570
  console.print("[bold]Build logs:[/bold]")
572
571
  console.print("-" * 40)
573
572
 
574
573
  try:
575
- with client.stream_build_logs(provider_id, job_name) as response:
574
+ with client.stream_build_logs(provider_id, version) as response:
576
575
  for line in response.iter_lines():
577
576
  console.print(line)
578
577
  except httpx.HTTPError as e:
579
578
  console.print(f"[yellow]Warning:[/yellow] Could not stream logs: {e}")
580
579
  console.print("[dim]Falling back to polling...[/dim]")
581
- _poll_build_status(client, provider_id, job_name)
580
+ _poll_build_status(client, provider_id, version)
582
581
 
583
582
  console.print("-" * 40)
584
583
 
585
584
 
586
- def _deploy_provider(client: PragmaClient, provider_id: str, image: str) -> None:
587
- """Deploy the provider.
585
+ def _deploy_provider(client: PragmaClient, provider_id: str, version: str | None = None) -> None:
586
+ """Deploy the provider to a specific version.
588
587
 
589
588
  Args:
590
589
  client: SDK client instance.
591
590
  provider_id: Provider identifier.
592
- image: Container image to deploy.
591
+ version: Version to deploy (CalVer format). If None, deploys latest.
593
592
  """
594
593
  with Progress(
595
594
  SpinnerColumn(),
@@ -598,46 +597,50 @@ def _deploy_provider(client: PragmaClient, provider_id: str, image: str) -> None
598
597
  transient=True,
599
598
  ) as progress:
600
599
  progress.add_task("Deploying...", total=None)
601
- deploy_result = client.deploy_provider(provider_id, image)
600
+ deploy_result = client.deploy_provider(provider_id, version)
602
601
 
603
- console.print(f"[green]Deployment started:[/green] {deploy_result.deployment_name}")
602
+ console.print(f"[green]Deployment started:[/green] {provider_id}")
603
+ if deploy_result.image:
604
+ console.print(f"[dim]Image:[/dim] {deploy_result.image}")
604
605
  console.print(f"[dim]Status:[/dim] {deploy_result.status.value}")
605
606
 
606
607
 
607
608
  @app.command()
608
609
  def deploy(
609
- image: Annotated[
610
+ provider_id: Annotated[
610
611
  str,
611
- typer.Option("--image", "-i", help="Container image to deploy (required)"),
612
+ typer.Argument(
613
+ help="Provider ID to deploy (e.g., 'postgres', 'my-provider')",
614
+ autocompletion=completion_provider_ids,
615
+ ),
612
616
  ],
613
- package: Annotated[
617
+ version: Annotated[
614
618
  str | None,
615
- typer.Option("--package", "-p", help="Provider package name (auto-detected if not specified)"),
619
+ typer.Argument(
620
+ help="Version to deploy (e.g., 20250115.120000). Defaults to latest.",
621
+ autocompletion=completion_provider_versions,
622
+ ),
616
623
  ] = None,
617
624
  ):
618
- """Deploy a provider from a built container image.
625
+ """Deploy a provider to a specific version.
619
626
 
620
- Deploys a provider image to Kubernetes. Use after 'pragma provider push'
621
- completes successfully, or to redeploy/rollback to a specific version.
627
+ Deploys the provider to Kubernetes. If no version is specified, deploys
628
+ the latest successful build. Use 'pragma providers builds' to see available versions.
622
629
 
623
- Example:
624
- pragma provider deploy --image europe-west4-docker.pkg.dev/project/repo/provider:tag
625
- pragma provider deploy -i provider:latest --package my_provider
630
+ Deploy latest:
631
+ pragma providers deploy postgres
632
+
633
+ Deploy specific version:
634
+ pragma providers deploy postgres 20250115.120000
626
635
 
627
636
  Raises:
628
637
  typer.Exit: If deployment fails.
629
638
  """
630
- provider_name = package or detect_provider_package()
631
-
632
- if not provider_name:
633
- console.print("[red]Error:[/red] Could not detect provider package.")
634
- console.print("Run from a provider directory or specify --package")
635
- raise typer.Exit(1)
636
-
637
- provider_id = provider_name.replace("_", "-").removesuffix("-provider")
638
-
639
639
  console.print(f"[bold]Deploying provider:[/bold] {provider_id}")
640
- console.print(f"[dim]Image:[/dim] {image}")
640
+ if version:
641
+ console.print(f"[dim]Version:[/dim] {version}")
642
+ else:
643
+ console.print("[dim]Version:[/dim] latest")
641
644
  console.print()
642
645
 
643
646
  client = get_client()
@@ -647,119 +650,12 @@ def deploy(
647
650
  raise typer.Exit(1)
648
651
 
649
652
  try:
650
- _deploy_provider(client, provider_id, image)
653
+ _deploy_provider(client, provider_id, version)
651
654
  except Exception as e:
652
655
  console.print(f"[red]Error:[/red] {e}")
653
656
  raise typer.Exit(1)
654
657
 
655
658
 
656
- @app.command()
657
- def sync(
658
- package: Annotated[
659
- str | None,
660
- typer.Option("--package", "-p", help="Provider package name (auto-detected if not specified)"),
661
- ] = None,
662
- dry_run: Annotated[
663
- bool,
664
- typer.Option("--dry-run", help="Show what would be registered without making changes"),
665
- ] = False,
666
- ):
667
- """Sync resource type definitions to the Pragmatiks platform.
668
-
669
- Discovers resources in the provider package and registers their schemas
670
- with the API. This allows users to create instances of these resource types.
671
-
672
- The command introspects the provider code to extract:
673
- - Provider and resource names from @provider.resource() decorator
674
- - JSON schema from Pydantic Config classes
675
-
676
- Example:
677
- pragma provider sync
678
- pragma provider sync --package postgres_provider
679
- pragma provider sync --dry-run
680
-
681
- Raises:
682
- typer.Exit: If package not found or registration fails.
683
- """
684
- package_name = package or detect_provider_package()
685
-
686
- if not package_name:
687
- typer.echo("Error: Could not detect provider package.", err=True)
688
- typer.echo("Run from a provider directory or specify --package", err=True)
689
- raise typer.Exit(1)
690
-
691
- typer.echo(f"Discovering resources in {package_name}...")
692
-
693
- try:
694
- resources = discover_resources(package_name)
695
- except ImportError as e:
696
- typer.echo(f"Error importing package: {e}", err=True)
697
- raise typer.Exit(1)
698
-
699
- if not resources:
700
- typer.echo("No resources found. Ensure resources are decorated with @provider.resource().")
701
- raise typer.Exit(0)
702
-
703
- typer.echo(f"Found {len(resources)} resource(s):")
704
- typer.echo("")
705
-
706
- if dry_run:
707
- for (provider, resource_name), resource_class in resources.items():
708
- typer.echo(f" {provider}/{resource_name} ({resource_class.__name__})")
709
- typer.echo("")
710
- typer.echo("Dry run - no changes made.")
711
- raise typer.Exit(0)
712
-
713
- client = get_client()
714
-
715
- for (provider, resource_name), resource_class in resources.items():
716
- try:
717
- config_class = get_config_class(resource_class)
718
- schema = config_class.model_json_schema()
719
- except ValueError as e:
720
- typer.echo(f" {provider}/{resource_name}: skipped ({e})", err=True)
721
- continue
722
-
723
- try:
724
- client.register_resource(
725
- provider=provider,
726
- resource=resource_name,
727
- schema=schema,
728
- )
729
- typer.echo(f" {provider}/{resource_name}: registered")
730
- except Exception as e:
731
- typer.echo(f" {provider}/{resource_name}: failed ({e})", err=True)
732
-
733
- typer.echo("")
734
- typer.echo("Sync complete.")
735
-
736
-
737
- def get_config_class(resource_class: type[Resource]) -> type[Config]:
738
- """Extract Config subclass from Resource's config field annotation.
739
-
740
- Args:
741
- resource_class: A Resource subclass.
742
-
743
- Returns:
744
- Config subclass type from the Resource's config field.
745
-
746
- Raises:
747
- ValueError: If Resource has no config field or wrong type.
748
- """
749
- annotations = resource_class.model_fields
750
- config_field = annotations.get("config")
751
-
752
- if config_field is None:
753
- raise ValueError(f"Resource {resource_class.__name__} has no config field")
754
-
755
- config_type = config_field.annotation
756
-
757
- if not isinstance(config_type, type) or not issubclass(config_type, Config):
758
- raise ValueError(f"Resource {resource_class.__name__} config field is not a Config subclass")
759
-
760
- return config_type
761
-
762
-
763
659
  def detect_provider_package() -> str | None:
764
660
  """Detect provider package name from current directory.
765
661
 
@@ -784,7 +680,10 @@ def detect_provider_package() -> str | None:
784
680
  def delete(
785
681
  provider_id: Annotated[
786
682
  str,
787
- typer.Argument(help="Provider ID to delete (e.g., 'postgres', 'my-provider')"),
683
+ typer.Argument(
684
+ help="Provider ID to delete (e.g., 'postgres', 'my-provider')",
685
+ autocompletion=completion_provider_ids,
686
+ ),
788
687
  ],
789
688
  cascade: Annotated[
790
689
  bool,
@@ -801,21 +700,21 @@ def delete(
801
700
  from the platform. By default, fails if the provider has any resources.
802
701
 
803
702
  Without --cascade:
804
- pragma provider delete my-provider
703
+ pragma providers delete my-provider
805
704
  -> Fails if provider has resources
806
705
 
807
706
  With --cascade:
808
- pragma provider delete my-provider --cascade
707
+ pragma providers delete my-provider --cascade
809
708
  -> Deletes provider and all its resources
810
709
 
811
710
  Skip confirmation:
812
- pragma provider delete my-provider --force
813
- pragma provider delete my-provider --cascade --force
711
+ pragma providers delete my-provider --force
712
+ pragma providers delete my-provider --cascade --force
814
713
 
815
714
  Example:
816
- pragma provider delete postgres
817
- pragma provider delete postgres --cascade
818
- pragma provider delete postgres --cascade --force
715
+ pragma providers delete postgres
716
+ pragma providers delete postgres --cascade
717
+ pragma providers delete postgres --cascade --force
819
718
 
820
719
  Raises:
821
720
  typer.Exit: If deletion fails or user cancels.
@@ -823,7 +722,7 @@ def delete(
823
722
  client = get_client()
824
723
 
825
724
  if client._auth is None:
826
- console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
725
+ console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
827
726
  raise typer.Exit(1)
828
727
 
829
728
  console.print(f"[bold]Provider:[/bold] {provider_id}")
@@ -871,146 +770,37 @@ def _print_delete_result(result: ProviderDeleteResult) -> None:
871
770
  """
872
771
  console.print()
873
772
  console.print(f"[green]Provider deleted:[/green] {result.provider_id}")
874
- console.print()
875
773
 
876
- table = Table(show_header=True, header_style="bold")
877
- table.add_column("Component")
878
- table.add_column("Deleted", justify="right")
879
-
880
- table.add_row("Builds Cancelled", str(result.builds_cancelled))
881
- table.add_row("Source Archives", str(result.source_archives_deleted))
882
- table.add_row("Deployment", "Yes" if result.deployment_deleted else "No (not found)")
883
- table.add_row("Resources", str(result.resources_deleted))
884
- table.add_row("Resource Definitions", str(result.resource_definitions_deleted))
885
- table.add_row("Outbox Events", str(result.outbox_events_deleted))
886
- table.add_row("Dead Letter Events", str(result.dead_letter_events_deleted))
887
- table.add_row("NATS Messages Purged", str(result.messages_purged))
888
- table.add_row("NATS Consumer", "Deleted" if result.consumer_deleted else "Not found")
774
+ if result.deployment_deleted:
775
+ console.print("[dim]Deployment stopped[/dim]")
889
776
 
890
- console.print(table)
891
-
892
-
893
- @app.command()
894
- def rollback(
895
- provider_id: Annotated[
896
- str,
897
- typer.Argument(help="Provider ID to rollback (e.g., 'postgres', 'my-provider')"),
898
- ],
899
- version: Annotated[
900
- str | None,
901
- typer.Option("--version", "-v", help="Target version to rollback to (default: previous successful build)"),
902
- ] = None,
903
- ):
904
- """Rollback a provider to a previous version.
905
-
906
- Redeploys a previously successful build. If no version is specified,
907
- rolls back to the previous successful version.
908
-
909
- Rollback to specific version:
910
- pragma provider rollback my-provider --version 20250114.120000
911
-
912
- Rollback to previous version:
913
- pragma provider rollback my-provider
914
-
915
- Example:
916
- pragma provider rollback postgres --version 20250114.120000
917
- pragma provider rollback postgres -v 20250114.120000
918
- pragma provider rollback postgres
919
-
920
- Raises:
921
- typer.Exit: If rollback fails or no suitable version found.
922
- """
923
- client = get_client()
924
-
925
- if client._auth is None:
926
- console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
927
- raise typer.Exit(1)
928
-
929
- try:
930
- target_version = version
931
- if target_version is None:
932
- target_version = _find_previous_version(client, provider_id)
933
-
934
- console.print(f"[bold]Rolling back provider:[/bold] {provider_id}")
935
- console.print(f"[dim]Target version:[/dim] {target_version}")
936
- console.print()
937
-
938
- with Progress(
939
- SpinnerColumn(),
940
- TextColumn("[progress.description]{task.description}"),
941
- console=console,
942
- transient=True,
943
- ) as progress:
944
- progress.add_task("Deploying previous version...", total=None)
945
- result = client.rollback_provider(provider_id, target_version)
946
-
947
- console.print(f"[green]Rollback initiated:[/green] {result.deployment_name}")
948
- console.print(f"[dim]Status:[/dim] {result.status.value}")
949
-
950
- except httpx.HTTPStatusError as e:
951
- if e.response.status_code == 404:
952
- detail = e.response.json().get("detail", "Build not found")
953
- console.print(f"[red]Error:[/red] {detail}")
954
- elif e.response.status_code == 400:
955
- detail = e.response.json().get("detail", "Build not deployable")
956
- console.print(f"[red]Error:[/red] {detail}")
957
- else:
958
- console.print(f"[red]Error:[/red] {e.response.text}")
959
- raise typer.Exit(1)
960
- except ValueError as e:
961
- console.print(f"[red]Error:[/red] {e}")
962
- raise typer.Exit(1)
963
- except Exception as e:
964
- console.print(f"[red]Error:[/red] {e}")
965
- raise typer.Exit(1)
966
-
967
-
968
- def _find_previous_version(client: PragmaClient, provider_id: str) -> str:
969
- """Find the previous successful version for rollback.
970
-
971
- Gets the build history and finds the second-most-recent successful build,
972
- which represents the version before the current deployment.
973
-
974
- Args:
975
- client: SDK client instance.
976
- provider_id: Provider identifier.
977
-
978
- Returns:
979
- CalVer version string of the previous successful build.
980
-
981
- Raises:
982
- ValueError: If no previous successful build exists.
983
- """
984
- builds = client.list_builds(provider_id)
985
- successful_builds = [b for b in builds if b.status == BuildStatus.SUCCESS]
986
-
987
- if len(successful_builds) < 2:
988
- raise ValueError(
989
- f"No previous successful build found for provider '{provider_id}'. "
990
- "Specify a version explicitly with --version."
991
- )
992
-
993
- return successful_builds[1].version
777
+ if result.resources_deleted > 0:
778
+ console.print(f"[dim]Resources deleted: {result.resources_deleted}[/dim]")
994
779
 
995
780
 
996
781
  @app.command()
997
782
  def status(
998
783
  provider_id: Annotated[
999
784
  str,
1000
- typer.Argument(help="Provider ID to check status (e.g., 'postgres', 'my-provider')"),
785
+ typer.Argument(
786
+ help="Provider ID to check status (e.g., 'postgres', 'my-provider')",
787
+ autocompletion=completion_provider_ids,
788
+ ),
1001
789
  ],
790
+ output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE,
1002
791
  ):
1003
792
  """Check the deployment status of a provider.
1004
793
 
1005
794
  Displays:
1006
795
  - Deployment status (pending/progressing/available/failed)
1007
- - Replica count (available/ready)
1008
- - Current image version
796
+ - Deployed version
797
+ - Health status
1009
798
  - Last updated timestamp
1010
799
 
1011
- Example:
1012
- pragma provider status postgres
1013
- pragma provider status my-provider
800
+ Examples:
801
+ pragma providers status postgres
802
+ pragma providers status my-provider
803
+ pragma providers status postgres -o json
1014
804
 
1015
805
  Raises:
1016
806
  typer.Exit: If deployment not found or status check fails.
@@ -1018,7 +808,7 @@ def status(
1018
808
  client = get_client()
1019
809
 
1020
810
  if client._auth is None:
1021
- console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
811
+ console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
1022
812
  raise typer.Exit(1)
1023
813
 
1024
814
  try:
@@ -1033,7 +823,13 @@ def status(
1033
823
  console.print(f"[red]Error:[/red] {e}")
1034
824
  raise typer.Exit(1)
1035
825
 
1036
- _print_deployment_status(provider_id, result)
826
+ if output == OutputFormat.TABLE:
827
+ _print_deployment_status(provider_id, result)
828
+ else:
829
+ # Convert Pydantic model to dict for JSON/YAML output
830
+ data = result.model_dump(mode="json")
831
+ data["provider_id"] = provider_id # Include provider_id in output
832
+ output_data(data, output)
1037
833
 
1038
834
 
1039
835
  def _print_deployment_status(provider_id: str, result) -> None:
@@ -1060,19 +856,18 @@ def _print_deployment_status(provider_id: str, result) -> None:
1060
856
  table.add_column("Value")
1061
857
 
1062
858
  table.add_row("Status", f"[{status_color}]{result.status.value}[/{status_color}]")
1063
- table.add_row("Replicas", f"{result.available_replicas} available / {result.ready_replicas} ready")
1064
859
 
1065
860
  if result.image:
1066
- version = result.image.split(":")[-1] if ":" in result.image else "unknown"
1067
- table.add_row("Version", version)
1068
- table.add_row("Image", f"[dim]{result.image}[/dim]")
861
+ table.add_row("Image", result.image)
862
+
863
+ # Healthy is determined by having ready replicas
864
+ healthy = result.ready_replicas > 0
865
+ healthy_display = "[green]yes[/green]" if healthy else "[red]no[/red]"
866
+ table.add_row("Healthy", healthy_display)
1069
867
 
1070
868
  if result.updated_at:
1071
869
  table.add_row("Updated", result.updated_at.strftime("%Y-%m-%d %H:%M:%S UTC"))
1072
870
 
1073
- if result.message:
1074
- table.add_row("Message", result.message)
1075
-
1076
871
  console.print(table)
1077
872
 
1078
873
 
@@ -1080,7 +875,10 @@ def _print_deployment_status(provider_id: str, result) -> None:
1080
875
  def builds(
1081
876
  provider_id: Annotated[
1082
877
  str,
1083
- typer.Argument(help="Provider ID to list builds for (e.g., 'postgres', 'my-provider')"),
878
+ typer.Argument(
879
+ help="Provider ID to list builds for (e.g., 'postgres', 'my-provider')",
880
+ autocompletion=completion_provider_ids,
881
+ ),
1084
882
  ],
1085
883
  ):
1086
884
  """List build history for a provider.
@@ -1089,8 +887,8 @@ def builds(
1089
887
  Useful for selecting versions for rollback and verifying build status.
1090
888
 
1091
889
  Example:
1092
- pragma provider builds postgres
1093
- pragma provider builds my-provider
890
+ pragma providers builds postgres
891
+ pragma providers builds my-provider
1094
892
 
1095
893
  Raises:
1096
894
  typer.Exit: If request fails.
@@ -1098,7 +896,7 @@ def builds(
1098
896
  client = get_client()
1099
897
 
1100
898
  if client._auth is None:
1101
- console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
899
+ console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
1102
900
  raise typer.Exit(1)
1103
901
 
1104
902
  try: