pragmatiks-cli 0.5.1__py3-none-any.whl → 0.8.1__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.
@@ -5,13 +5,19 @@ import webbrowser
5
5
  from http.server import BaseHTTPRequestHandler, HTTPServer
6
6
  from urllib.parse import parse_qs, urlparse
7
7
 
8
+ import httpx
8
9
  import typer
10
+ from pragma_sdk import PragmaClient
9
11
  from pragma_sdk.config import load_credentials
10
12
  from rich import print
13
+ from rich.console import Console
11
14
 
12
15
  from pragma_cli.config import CREDENTIALS_FILE, get_current_context, load_config
13
16
 
14
17
 
18
+ console = Console()
19
+
20
+
15
21
  app = typer.Typer()
16
22
 
17
23
  CALLBACK_PORT = int(os.getenv("PRAGMA_AUTH_CALLBACK_PORT", "8765"))
@@ -218,30 +224,49 @@ def logout(
218
224
 
219
225
  @app.command()
220
226
  def whoami():
221
- """Show current authentication status.
227
+ """Show current authentication status and user information.
222
228
 
223
- Displays which contexts have stored credentials and current authentication state.
229
+ Displays the current context, authentication state, and user details
230
+ including email and organization name from the API.
224
231
  """
225
- config = load_config()
226
- current_context_name, _ = get_current_context()
232
+ current_context_name, current_context_config = get_current_context()
233
+ token = load_credentials(current_context_name)
234
+
235
+ console.print()
236
+ console.print("[bold]Authentication Status[/bold]")
237
+ console.print()
238
+
239
+ if not token:
240
+ console.print(f" Context: [cyan]{current_context_name}[/cyan]")
241
+ console.print(" Status: [yellow]Not authenticated[/yellow]")
242
+ console.print()
243
+ console.print("[dim]Run 'pragma auth login' to authenticate[/dim]")
244
+ return
227
245
 
228
- print()
229
- print("[bold]Authentication Status[/bold]")
230
- print()
246
+ console.print(f" Context: [cyan]{current_context_name}[/cyan]")
247
+ console.print(" Status: [green]\u2713 Authenticated[/green]")
231
248
 
232
- has_any_creds = False
233
- for context_name in config.contexts.keys():
234
- token = load_credentials(context_name)
235
- marker = "[green]*[/green]" if context_name == current_context_name else " "
249
+ try:
250
+ client = PragmaClient(base_url=current_context_config.api_url, auth_token=token)
251
+ user_info = client.get_me()
236
252
 
237
- if token:
238
- has_any_creds = True
239
- print(f"{marker} [cyan]{context_name}[/cyan]: [green]\u2713 Authenticated[/green]")
253
+ console.print()
254
+ console.print("[bold]User Information[/bold]")
255
+ console.print()
256
+ console.print(f" User ID: [cyan]{user_info.user_id}[/cyan]")
257
+ if user_info.email:
258
+ console.print(f" Email: [cyan]{user_info.email}[/cyan]")
240
259
  else:
241
- print(f"{marker} [cyan]{context_name}[/cyan]: [dim]Not authenticated[/dim]")
242
-
243
- print()
260
+ console.print(" Email: [dim]Not set[/dim]")
261
+ console.print(f" Organization: [cyan]{user_info.organization_name or user_info.organization_id}[/cyan]")
244
262
 
245
- if not has_any_creds:
246
- print("[yellow]No stored credentials found[/yellow]")
247
- print("[dim]Run 'pragma login' to authenticate[/dim]")
263
+ except httpx.HTTPStatusError as e:
264
+ if e.response.status_code == 401:
265
+ console.print()
266
+ console.print("[yellow]Token expired or invalid. Run 'pragma auth login' to re-authenticate.[/yellow]")
267
+ else:
268
+ console.print()
269
+ console.print(f"[red]Error fetching user info:[/red] {e.response.text}")
270
+ except httpx.RequestError as e:
271
+ console.print()
272
+ console.print(f"[red]Connection error:[/red] {e}")
@@ -1,10 +1,52 @@
1
- """CLI auto-completion functions for resource operations."""
1
+ """CLI auto-completion functions for resource and provider operations."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import typer
6
+ from pragma_sdk import PragmaClient
6
7
 
7
- from pragma_cli import get_client
8
+ from pragma_cli.config import get_current_context
9
+
10
+
11
+ def _get_completion_client() -> PragmaClient | None:
12
+ """Get a client for shell completion context.
13
+
14
+ Returns:
15
+ PragmaClient instance or None if configuration unavailable.
16
+ """
17
+ try:
18
+ context_name, context_config = get_current_context()
19
+ if context_config is None:
20
+ return None
21
+ return PragmaClient(
22
+ base_url=context_config.api_url,
23
+ context=context_name,
24
+ require_auth=False,
25
+ )
26
+ except Exception:
27
+ return None
28
+
29
+
30
+ def completion_provider_ids(incomplete: str):
31
+ """Complete provider identifiers based on deployed providers.
32
+
33
+ Args:
34
+ incomplete: Partial input to complete against available providers.
35
+
36
+ Yields:
37
+ Provider IDs matching the incomplete input.
38
+ """
39
+ client = _get_completion_client()
40
+ if client is None:
41
+ return
42
+ try:
43
+ providers = client.list_providers()
44
+ except Exception:
45
+ return
46
+
47
+ for provider in providers:
48
+ if provider.provider_id.lower().startswith(incomplete.lower()):
49
+ yield provider.provider_id
8
50
 
9
51
 
10
52
  def completion_resource_ids(incomplete: str):
@@ -16,7 +58,9 @@ def completion_resource_ids(incomplete: str):
16
58
  Yields:
17
59
  Resource identifiers matching the incomplete input.
18
60
  """
19
- client = get_client()
61
+ client = _get_completion_client()
62
+ if client is None:
63
+ return
20
64
  try:
21
65
  resources = client.list_resources()
22
66
  except Exception:
@@ -40,7 +84,9 @@ def completion_resource_names(ctx: typer.Context, incomplete: str):
40
84
  Yields:
41
85
  Resource names matching the incomplete input for the selected resource type.
42
86
  """
43
- client = get_client()
87
+ client = _get_completion_client()
88
+ if client is None:
89
+ return
44
90
  resource_id = ctx.params.get("resource_id")
45
91
  if not resource_id or "/" not in resource_id:
46
92
  return
@@ -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
@@ -17,20 +17,18 @@ import typer
17
17
  from pragma_sdk import (
18
18
  BuildResult,
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
34
32
 
35
33
 
36
34
  app = typer.Typer(help="Provider management commands")
@@ -129,7 +127,7 @@ def list_providers():
129
127
  - Last deployed timestamp
130
128
 
131
129
  Example:
132
- pragma provider list
130
+ pragma providers list
133
131
 
134
132
  Raises:
135
133
  typer.Exit: If authentication is missing or API call fails.
@@ -178,11 +176,7 @@ def _print_providers_table(providers: list[ProviderInfo]) -> None:
178
176
  for provider in providers:
179
177
  status = _format_deployment_status(provider.deployment_status)
180
178
  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
- )
179
+ updated = provider.updated_at.strftime("%Y-%m-%d %H:%M:%S") if provider.updated_at else "[dim]-[/dim]"
186
180
 
187
181
  table.add_row(provider.provider_id, version, status, updated)
188
182
 
@@ -248,9 +242,9 @@ def init(
248
242
  - mise.toml for tool management
249
243
 
250
244
  Example:
251
- pragma provider init mycompany
252
- pragma provider init postgres --output ./providers/postgres
253
- pragma provider init mycompany --defaults --description "My provider"
245
+ pragma providers init mycompany
246
+ pragma providers init postgres --output ./providers/postgres
247
+ pragma providers init mycompany --defaults --description "My provider"
254
248
 
255
249
  Raises:
256
250
  typer.Exit: If directory already exists or template copy fails.
@@ -303,7 +297,7 @@ def init(
303
297
  typer.echo(" copier update")
304
298
  typer.echo("")
305
299
  typer.echo("When ready to deploy:")
306
- typer.echo(" pragma provider push")
300
+ typer.echo(" pragma providers push")
307
301
 
308
302
 
309
303
  @app.command()
@@ -319,8 +313,8 @@ def update(
319
313
  incorporating template updates.
320
314
 
321
315
  Example:
322
- pragma provider update
323
- pragma provider update ./my-provider
316
+ pragma providers update
317
+ pragma providers update ./my-provider
324
318
 
325
319
  Raises:
326
320
  typer.Exit: If directory is not a Copier project or update fails.
@@ -374,25 +368,25 @@ def push(
374
368
  a container image.
375
369
 
376
370
  Build only:
377
- pragma provider push
371
+ pragma providers push
378
372
  -> Uploads code and waits for build
379
373
 
380
374
  Build and deploy:
381
- pragma provider push --deploy
375
+ pragma providers push --deploy
382
376
  -> Uploads code, builds, and deploys
383
377
 
384
378
  Async build:
385
- pragma provider push --no-wait
379
+ pragma providers push --no-wait
386
380
  -> Uploads code and returns immediately
387
381
 
388
382
  With logs:
389
- pragma provider push --logs
383
+ pragma providers push --logs
390
384
  -> Shows build output in real-time
391
385
 
392
386
  Example:
393
- pragma provider push
394
- pragma provider push --deploy
395
- pragma provider push --logs --deploy
387
+ pragma providers push
388
+ pragma providers push --deploy
389
+ pragma providers push --logs --deploy
396
390
 
397
391
  Raises:
398
392
  typer.Exit: If provider detection fails or build fails.
@@ -437,18 +431,14 @@ def push(
437
431
  if not wait:
438
432
  console.print()
439
433
  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}")
434
+ console.print(f" pragma providers status {provider_id} --job {push_result.job_name}")
441
435
  return
442
436
 
443
- build_result = _wait_for_build(client, provider_id, push_result.job_name, logs)
437
+ _wait_for_build(client, provider_id, push_result.job_name, logs)
444
438
 
445
439
  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
440
  console.print()
451
- _deploy_provider(client, provider_id, build_result.image)
441
+ _deploy_provider(client, provider_id, push_result.version)
452
442
  except Exception as e:
453
443
  if isinstance(e, typer.Exit):
454
444
  raise
@@ -583,13 +573,15 @@ def _stream_build_logs(client: PragmaClient, provider_id: str, job_name: str) ->
583
573
  console.print("-" * 40)
584
574
 
585
575
 
586
- def _deploy_provider(client: PragmaClient, provider_id: str, image: str) -> None:
587
- """Deploy the provider.
576
+ def _deploy_provider(
577
+ client: PragmaClient, provider_id: str, version: str | None = None
578
+ ) -> None:
579
+ """Deploy the provider to a specific version.
588
580
 
589
581
  Args:
590
582
  client: SDK client instance.
591
583
  provider_id: Provider identifier.
592
- image: Container image to deploy.
584
+ version: Version to deploy (CalVer format). If None, deploys latest.
593
585
  """
594
586
  with Progress(
595
587
  SpinnerColumn(),
@@ -598,31 +590,34 @@ def _deploy_provider(client: PragmaClient, provider_id: str, image: str) -> None
598
590
  transient=True,
599
591
  ) as progress:
600
592
  progress.add_task("Deploying...", total=None)
601
- deploy_result = client.deploy_provider(provider_id, image)
593
+ deploy_result = client.deploy_provider(provider_id, version)
602
594
 
603
595
  console.print(f"[green]Deployment started:[/green] {deploy_result.deployment_name}")
596
+ console.print(f"[dim]Version:[/dim] {deploy_result.version}")
604
597
  console.print(f"[dim]Status:[/dim] {deploy_result.status.value}")
605
598
 
606
599
 
607
600
  @app.command()
608
601
  def deploy(
609
- image: Annotated[
610
- str,
611
- typer.Option("--image", "-i", help="Container image to deploy (required)"),
612
- ],
602
+ version: Annotated[
603
+ str | None,
604
+ typer.Option("--version", "-v", help="Version to deploy (e.g., 20250115.120000). Defaults to latest."),
605
+ ] = None,
613
606
  package: Annotated[
614
607
  str | None,
615
608
  typer.Option("--package", "-p", help="Provider package name (auto-detected if not specified)"),
616
609
  ] = None,
617
610
  ):
618
- """Deploy a provider from a built container image.
611
+ """Deploy a provider to a specific version.
619
612
 
620
- Deploys a provider image to Kubernetes. Use after 'pragma provider push'
621
- completes successfully, or to redeploy/rollback to a specific version.
613
+ Deploys the provider to Kubernetes. If no version is specified, deploys
614
+ the latest successful build. Use 'pragma providers builds' to see available versions.
622
615
 
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
616
+ Deploy latest:
617
+ pragma providers deploy
618
+
619
+ Deploy specific version:
620
+ pragma providers deploy --version 20250115.120000
626
621
 
627
622
  Raises:
628
623
  typer.Exit: If deployment fails.
@@ -637,7 +632,10 @@ def deploy(
637
632
  provider_id = provider_name.replace("_", "-").removesuffix("-provider")
638
633
 
639
634
  console.print(f"[bold]Deploying provider:[/bold] {provider_id}")
640
- console.print(f"[dim]Image:[/dim] {image}")
635
+ if version:
636
+ console.print(f"[dim]Version:[/dim] {version}")
637
+ else:
638
+ console.print("[dim]Version:[/dim] latest")
641
639
  console.print()
642
640
 
643
641
  client = get_client()
@@ -647,119 +645,12 @@ def deploy(
647
645
  raise typer.Exit(1)
648
646
 
649
647
  try:
650
- _deploy_provider(client, provider_id, image)
648
+ _deploy_provider(client, provider_id, version)
651
649
  except Exception as e:
652
650
  console.print(f"[red]Error:[/red] {e}")
653
651
  raise typer.Exit(1)
654
652
 
655
653
 
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
654
  def detect_provider_package() -> str | None:
764
655
  """Detect provider package name from current directory.
765
656
 
@@ -784,7 +675,10 @@ def detect_provider_package() -> str | None:
784
675
  def delete(
785
676
  provider_id: Annotated[
786
677
  str,
787
- typer.Argument(help="Provider ID to delete (e.g., 'postgres', 'my-provider')"),
678
+ typer.Argument(
679
+ help="Provider ID to delete (e.g., 'postgres', 'my-provider')",
680
+ autocompletion=completion_provider_ids,
681
+ ),
788
682
  ],
789
683
  cascade: Annotated[
790
684
  bool,
@@ -801,21 +695,21 @@ def delete(
801
695
  from the platform. By default, fails if the provider has any resources.
802
696
 
803
697
  Without --cascade:
804
- pragma provider delete my-provider
698
+ pragma providers delete my-provider
805
699
  -> Fails if provider has resources
806
700
 
807
701
  With --cascade:
808
- pragma provider delete my-provider --cascade
702
+ pragma providers delete my-provider --cascade
809
703
  -> Deletes provider and all its resources
810
704
 
811
705
  Skip confirmation:
812
- pragma provider delete my-provider --force
813
- pragma provider delete my-provider --cascade --force
706
+ pragma providers delete my-provider --force
707
+ pragma providers delete my-provider --cascade --force
814
708
 
815
709
  Example:
816
- pragma provider delete postgres
817
- pragma provider delete postgres --cascade
818
- pragma provider delete postgres --cascade --force
710
+ pragma providers delete postgres
711
+ pragma providers delete postgres --cascade
712
+ pragma providers delete postgres --cascade --force
819
713
 
820
714
  Raises:
821
715
  typer.Exit: If deletion fails or user cancels.
@@ -890,127 +784,27 @@ def _print_delete_result(result: ProviderDeleteResult) -> None:
890
784
  console.print(table)
891
785
 
892
786
 
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
994
-
995
-
996
787
  @app.command()
997
788
  def status(
998
789
  provider_id: Annotated[
999
790
  str,
1000
- typer.Argument(help="Provider ID to check status (e.g., 'postgres', 'my-provider')"),
791
+ typer.Argument(
792
+ help="Provider ID to check status (e.g., 'postgres', 'my-provider')",
793
+ autocompletion=completion_provider_ids,
794
+ ),
1001
795
  ],
1002
796
  ):
1003
797
  """Check the deployment status of a provider.
1004
798
 
1005
799
  Displays:
1006
800
  - Deployment status (pending/progressing/available/failed)
1007
- - Replica count (available/ready)
1008
- - Current image version
801
+ - Deployed version
802
+ - Health status
1009
803
  - Last updated timestamp
1010
804
 
1011
805
  Example:
1012
- pragma provider status postgres
1013
- pragma provider status my-provider
806
+ pragma providers status postgres
807
+ pragma providers status my-provider
1014
808
 
1015
809
  Raises:
1016
810
  typer.Exit: If deployment not found or status check fails.
@@ -1018,7 +812,7 @@ def status(
1018
812
  client = get_client()
1019
813
 
1020
814
  if client._auth is None:
1021
- console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
815
+ console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
1022
816
  raise typer.Exit(1)
1023
817
 
1024
818
  try:
@@ -1041,7 +835,7 @@ def _print_deployment_status(provider_id: str, result) -> None:
1041
835
 
1042
836
  Args:
1043
837
  provider_id: Provider identifier.
1044
- result: DeploymentResult from the API.
838
+ result: ProviderStatus from the API.
1045
839
  """
1046
840
  status_colors = {
1047
841
  "pending": "yellow",
@@ -1060,19 +854,16 @@ def _print_deployment_status(provider_id: str, result) -> None:
1060
854
  table.add_column("Value")
1061
855
 
1062
856
  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
857
 
1065
- 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]")
858
+ if result.version:
859
+ table.add_row("Version", result.version)
860
+
861
+ healthy_display = "[green]yes[/green]" if result.healthy else "[red]no[/red]"
862
+ table.add_row("Healthy", healthy_display)
1069
863
 
1070
864
  if result.updated_at:
1071
865
  table.add_row("Updated", result.updated_at.strftime("%Y-%m-%d %H:%M:%S UTC"))
1072
866
 
1073
- if result.message:
1074
- table.add_row("Message", result.message)
1075
-
1076
867
  console.print(table)
1077
868
 
1078
869
 
@@ -1080,7 +871,10 @@ def _print_deployment_status(provider_id: str, result) -> None:
1080
871
  def builds(
1081
872
  provider_id: Annotated[
1082
873
  str,
1083
- typer.Argument(help="Provider ID to list builds for (e.g., 'postgres', 'my-provider')"),
874
+ typer.Argument(
875
+ help="Provider ID to list builds for (e.g., 'postgres', 'my-provider')",
876
+ autocompletion=completion_provider_ids,
877
+ ),
1084
878
  ],
1085
879
  ):
1086
880
  """List build history for a provider.
@@ -1089,8 +883,8 @@ def builds(
1089
883
  Useful for selecting versions for rollback and verifying build status.
1090
884
 
1091
885
  Example:
1092
- pragma provider builds postgres
1093
- pragma provider builds my-provider
886
+ pragma providers builds postgres
887
+ pragma providers builds my-provider
1094
888
 
1095
889
  Raises:
1096
890
  typer.Exit: If request fails.
@@ -93,7 +93,13 @@ def list_resources(
93
93
  ):
94
94
  """List resources, optionally filtered by provider, resource type, or tags."""
95
95
  client = get_client()
96
- for res in client.list_resources(provider=provider, resource=resource, tags=tags):
96
+ resources = list(client.list_resources(provider=provider, resource=resource, tags=tags))
97
+
98
+ if not resources:
99
+ console.print("[dim]No resources found.[/dim]")
100
+ return
101
+
102
+ for res in resources:
97
103
  print(f"{res['provider']}/{res['resource']}/{res['name']} {format_state(res['lifecycle_state'])}")
98
104
 
99
105
 
pragma_cli/main.py CHANGED
@@ -6,7 +6,7 @@ import typer
6
6
  from pragma_sdk import PragmaClient
7
7
 
8
8
  from pragma_cli import set_client
9
- from pragma_cli.commands import auth, config, ops, provider, resources
9
+ from pragma_cli.commands import auth, config, ops, providers, resources
10
10
  from pragma_cli.config import get_current_context
11
11
 
12
12
 
@@ -61,7 +61,7 @@ app.add_typer(resources.app, name="resources")
61
61
  app.add_typer(auth.app, name="auth")
62
62
  app.add_typer(config.app, name="config")
63
63
  app.add_typer(ops.app, name="ops")
64
- app.add_typer(provider.app, name="provider")
64
+ app.add_typer(providers.app, name="providers")
65
65
 
66
66
  if __name__ == "__main__": # pragma: no cover
67
67
  app()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pragmatiks-cli
3
- Version: 0.5.1
3
+ Version: 0.8.1
4
4
  Summary: Command-line interface for Pragmatiks
5
5
  Requires-Dist: typer>=0.15.3
6
6
  Requires-Dist: pragmatiks-sdk>=0.6.0
@@ -10,19 +10,21 @@ Requires-Dist: rich>=13.9.0
10
10
  Requires-Python: >=3.13
11
11
  Description-Content-Type: text/markdown
12
12
 
13
- # Pragmatiks CLI
13
+ <p align="center">
14
+ <img src="assets/wordmark.png" alt="Pragma-OS" width="800">
15
+ </p>
14
16
 
15
- [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/pragmatiks/cli)
17
+ # Pragma CLI
18
+
19
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/pragmatiks/pragma-cli)
16
20
  [![PyPI version](https://img.shields.io/pypi/v/pragmatiks-cli.svg)](https://pypi.org/project/pragmatiks-cli/)
17
21
  [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
18
22
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
19
23
  [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
20
24
 
21
- **[Documentation](https://docs.pragmatiks.io/cli/overview)** | **[SDK](https://github.com/pragmatiks/sdk)** | **[Providers](https://github.com/pragmatiks/providers)**
22
-
23
- Command-line interface for managing Pragmatiks resources.
25
+ **[Documentation](https://docs.pragmatiks.io/cli/overview)** | **[SDK](https://github.com/pragmatiks/pragma-sdk)** | **[Providers](https://github.com/pragmatiks/pragma-providers)**
24
26
 
25
- <!-- TODO: Add logo and demo GIF -->
27
+ Command-line interface for managing pragma-os resources.
26
28
 
27
29
  ## Quick Start
28
30
 
@@ -0,0 +1,17 @@
1
+ pragma_cli/__init__.py,sha256=9REbOdKs9CeuOd-rxeFs17gWtou1dUdCogYU8G5Cz6c,682
2
+ pragma_cli/commands/__init__.py,sha256=zltFPaCZgkeTdOH1YWrUEqqBF9Dg6tokgAFcmqP4_n4,24
3
+ pragma_cli/commands/auth.py,sha256=HSv4mMuA-2QjYDYf69IQd-yXFANr8Uzw-oz3bAy_R0o,9311
4
+ pragma_cli/commands/completions.py,sha256=DwiyVANoYfDIDSNKwgb4QV_tDH2k5RjXgt3Z7TNNi-A,2872
5
+ pragma_cli/commands/config.py,sha256=Bow71Tg_zeprHlbn_6y8g2YsV_k9nRobA6ooYfaxtWE,2281
6
+ pragma_cli/commands/dead_letter.py,sha256=8Mh_QVZiwkbOA1fYkw1O9BeHgPdqepis6tSJOAY3vhA,6754
7
+ pragma_cli/commands/ops.py,sha256=ztx0Gx2L2mEqJQpbgDHgfOUZ4uaD132NxgKohaPOWv8,361
8
+ pragma_cli/commands/providers.py,sha256=5OI1Rlgk4bN8OqMVhCVIQCOo3OqAZStDuV_4YXBgWeg,29168
9
+ pragma_cli/commands/resources.py,sha256=_1Te1HqEayOEcpKIIJPI6ud_yk1F19Uaw-SRNQ8kCSs,7031
10
+ pragma_cli/config.py,sha256=kcU4tJUV1DfnXC_ydQbgQoJzjdGB1s-6-7g4cwA1nZw,2340
11
+ pragma_cli/helpers.py,sha256=dVxokT-sqF08oY0O35QZ64QyNC0PHZEBJYTvZ726UtI,650
12
+ pragma_cli/main.py,sha256=DBclLuSeymcockptWgzX5I1euqrRrHgnrTgGAsF74Xc,1973
13
+ pragma_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ pragmatiks_cli-0.8.1.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
15
+ pragmatiks_cli-0.8.1.dist-info/entry_points.txt,sha256=9xeQQlnHxq94dks6mlJ2I9LuMUKmqxuJzyKSZCb9iJM,48
16
+ pragmatiks_cli-0.8.1.dist-info/METADATA,sha256=qQTi7V0AAFGf_EsVWV36cQgslubzeBa0O2mcZmsAyy0,4465
17
+ pragmatiks_cli-0.8.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.25
2
+ Generator: uv 0.9.26
3
3
  Root-Is-Purelib: true
4
- Tag: py3-none-any
4
+ Tag: py3-none-any
@@ -1,17 +0,0 @@
1
- pragma_cli/__init__.py,sha256=9REbOdKs9CeuOd-rxeFs17gWtou1dUdCogYU8G5Cz6c,682
2
- pragma_cli/commands/__init__.py,sha256=zltFPaCZgkeTdOH1YWrUEqqBF9Dg6tokgAFcmqP4_n4,24
3
- pragma_cli/commands/auth.py,sha256=6VqLTD8sZ7LRmI8RXEkSze1Ctnq5oM_1Zv0Br-HuFIA,8198
4
- pragma_cli/commands/completions.py,sha256=ZCW38A1-6l_IcGC7Gj2CP5VL6PzaY4zKdcxylDuCoWM,1609
5
- pragma_cli/commands/config.py,sha256=Bow71Tg_zeprHlbn_6y8g2YsV_k9nRobA6ooYfaxtWE,2281
6
- pragma_cli/commands/dead_letter.py,sha256=8Mh_QVZiwkbOA1fYkw1O9BeHgPdqepis6tSJOAY3vhA,6754
7
- pragma_cli/commands/ops.py,sha256=ztx0Gx2L2mEqJQpbgDHgfOUZ4uaD132NxgKohaPOWv8,361
8
- pragma_cli/commands/provider.py,sha256=BX1NqGbH9XOrYFrWb92UpZhjTJA5qJ_U6cppMB1_lq0,36271
9
- pragma_cli/commands/resources.py,sha256=BzwFKK5vqDo37wUPwRkulKdC4TFEJEoSXLlzsxQFD6w,6904
10
- pragma_cli/config.py,sha256=kcU4tJUV1DfnXC_ydQbgQoJzjdGB1s-6-7g4cwA1nZw,2340
11
- pragma_cli/helpers.py,sha256=dVxokT-sqF08oY0O35QZ64QyNC0PHZEBJYTvZ726UtI,650
12
- pragma_cli/main.py,sha256=3UAKMsgc5u2v55ykWy-o_XC5W2CwyDNGfTSFhSvsLfU,1970
13
- pragma_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- pragmatiks_cli-0.5.1.dist-info/WHEEL,sha256=XjEbIc5-wIORjWaafhI6vBtlxDBp7S9KiujWF1EM7Ak,79
15
- pragmatiks_cli-0.5.1.dist-info/entry_points.txt,sha256=9xeQQlnHxq94dks6mlJ2I9LuMUKmqxuJzyKSZCb9iJM,48
16
- pragmatiks_cli-0.5.1.dist-info/METADATA,sha256=N3AEoBl0p7bLIpxYtZtI7mltnY4xdKVHnO9iSIg4njs,4400
17
- pragmatiks_cli-0.5.1.dist-info/RECORD,,