pragmatiks-cli 0.8.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.
@@ -12,7 +12,7 @@ from pragma_sdk.config import load_credentials
12
12
  from rich import print
13
13
  from rich.console import Console
14
14
 
15
- from pragma_cli.config import CREDENTIALS_FILE, get_current_context, load_config
15
+ from pragma_cli.config import CREDENTIALS_FILE, ContextConfig, get_current_context, load_config
16
16
 
17
17
 
18
18
  console = Console()
@@ -22,9 +22,29 @@ app = typer.Typer()
22
22
 
23
23
  CALLBACK_PORT = int(os.getenv("PRAGMA_AUTH_CALLBACK_PORT", "8765"))
24
24
  CALLBACK_PATH = os.getenv("PRAGMA_AUTH_CALLBACK_PATH", "/auth/callback")
25
- CLERK_FRONTEND_URL = os.getenv("PRAGMA_CLERK_FRONTEND_URL", "https://app.pragmatiks.io")
26
- CALLBACK_URL = f"http://localhost:{CALLBACK_PORT}{CALLBACK_PATH}"
27
- LOGIN_URL = f"{CLERK_FRONTEND_URL}/auth/callback?callback={CALLBACK_URL}"
25
+
26
+
27
+ def _get_callback_url() -> str:
28
+ """Build the local callback URL for OAuth flow.
29
+
30
+ Returns:
31
+ Callback URL for the local OAuth server.
32
+ """
33
+ return f"http://localhost:{CALLBACK_PORT}{CALLBACK_PATH}"
34
+
35
+
36
+ def _get_login_url(context_config: ContextConfig) -> str:
37
+ """Build the login URL for a given context.
38
+
39
+ Args:
40
+ context_config: The context configuration to get auth URL from.
41
+
42
+ Returns:
43
+ Full login URL with callback parameter.
44
+ """
45
+ auth_url = context_config.get_auth_url()
46
+ callback_url = _get_callback_url()
47
+ return f"{auth_url}/auth/callback?callback={callback_url}"
28
48
 
29
49
 
30
50
  class CallbackHandler(BaseHTTPRequestHandler):
@@ -142,41 +162,50 @@ def clear_credentials(context_name: str | None = None):
142
162
 
143
163
 
144
164
  @app.command()
145
- def login(context: str = typer.Option("default", help="Context to authenticate for")):
165
+ def login(
166
+ context: str | None = typer.Option(None, help="Context to authenticate for (default: current)"),
167
+ ):
146
168
  """Authenticate with Pragma using browser-based Clerk login.
147
169
 
148
170
  Opens your default browser to Clerk authentication page. After successful
149
171
  login, your credentials are stored locally in ~/.config/pragma/credentials.
150
172
 
151
173
  Example:
152
- pragma login
153
- pragma login --context production
174
+ pragma auth login
175
+ pragma auth login --context production
154
176
 
155
177
  Raises:
156
178
  typer.Exit: If context not found or authentication fails/times out.
157
179
  """
158
180
  config = load_config()
181
+
182
+ # Use current context if not specified
183
+ if context is None:
184
+ context = config.current_context
185
+
159
186
  if context not in config.contexts:
160
187
  print(f"[red]\u2717[/red] Context '{context}' not found")
161
188
  print(f"Available contexts: {', '.join(config.contexts.keys())}")
162
189
  raise typer.Exit(1)
163
190
 
164
- api_url = config.contexts[context].api_url
191
+ context_config = config.contexts[context]
192
+ auth_url = context_config.get_auth_url()
193
+ login_url = _get_login_url(context_config)
165
194
 
166
195
  print(f"[cyan]Authenticating for context:[/cyan] {context}")
167
- print(f"[cyan]API URL:[/cyan] {api_url}")
196
+ print(f"[cyan]API URL:[/cyan] {context_config.api_url}")
168
197
  print()
169
198
 
170
199
  server = HTTPServer(("localhost", CALLBACK_PORT), CallbackHandler)
171
200
 
172
- print(f"[yellow]Opening browser to:[/yellow] {CLERK_FRONTEND_URL}")
201
+ print(f"[yellow]Opening browser to:[/yellow] {auth_url}")
173
202
  print()
174
203
  print("[dim]If browser doesn't open automatically, visit:[/dim]")
175
- print(f"[dim]{LOGIN_URL}[/dim]")
204
+ print(f"[dim]{login_url}[/dim]")
176
205
  print()
177
206
  print("[yellow]Waiting for authentication...[/yellow]")
178
207
 
179
- webbrowser.open(LOGIN_URL)
208
+ webbrowser.open(login_url)
180
209
 
181
210
  server.timeout = 300
182
211
  server.handle_request()
@@ -49,6 +49,31 @@ def completion_provider_ids(incomplete: str):
49
49
  yield provider.provider_id
50
50
 
51
51
 
52
+ def completion_provider_versions(ctx: typer.Context, incomplete: str):
53
+ """Complete provider versions based on available builds.
54
+
55
+ Args:
56
+ ctx: Typer context containing parsed parameters including provider_id.
57
+ incomplete: Partial input to complete against available versions.
58
+
59
+ Yields:
60
+ Version strings matching the incomplete input.
61
+ """
62
+ client = _get_completion_client()
63
+ if client is None:
64
+ return
65
+ provider_id = ctx.params.get("provider_id")
66
+ if not provider_id:
67
+ return
68
+ try:
69
+ builds = client.list_builds(provider_id)
70
+ except Exception:
71
+ return
72
+ for build in builds:
73
+ if build.version.startswith(incomplete):
74
+ yield build.version
75
+
76
+
52
77
  def completion_resource_ids(incomplete: str):
53
78
  """Complete resource identifiers in provider/resource format based on existing resources.
54
79
 
@@ -44,18 +44,25 @@ def current_context():
44
44
  context_name, context_config = get_current_context()
45
45
  print(f"[bold]Current context:[/bold] [cyan]{context_name}[/cyan]")
46
46
  print(f"[bold]API URL:[/bold] {context_config.api_url}")
47
+ print(f"[bold]Auth URL:[/bold] {context_config.get_auth_url()}")
47
48
 
48
49
 
49
50
  @app.command()
50
51
  def set_context(
51
52
  name: str = typer.Argument(..., help="Context name"),
52
53
  api_url: str = typer.Option(..., help="API endpoint URL"),
54
+ auth_url: str | None = typer.Option(None, help="Auth endpoint URL (derived from api_url if not set)"),
53
55
  ):
54
56
  """Create or update a context."""
55
57
  config = load_config()
56
- config.contexts[name] = ContextConfig(api_url=api_url)
58
+ config.contexts[name] = ContextConfig(api_url=api_url, auth_url=auth_url)
57
59
  save_config(config)
60
+
61
+ # Show the effective auth URL
62
+ effective_auth = config.contexts[name].get_auth_url()
58
63
  print(f"[green]\u2713[/green] Context '{name}' configured")
64
+ print(f" API URL: {api_url}")
65
+ print(f" Auth URL: {effective_auth}")
59
66
 
60
67
 
61
68
  @app.command()
@@ -15,7 +15,7 @@ import copier
15
15
  import httpx
16
16
  import typer
17
17
  from pragma_sdk import (
18
- BuildResult,
18
+ BuildInfo,
19
19
  BuildStatus,
20
20
  DeploymentStatus,
21
21
  PragmaClient,
@@ -28,7 +28,8 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
28
28
  from rich.table import Table
29
29
 
30
30
  from pragma_cli import get_client
31
- from pragma_cli.commands.completions import completion_provider_ids
31
+ from pragma_cli.commands.completions import completion_provider_ids, completion_provider_versions
32
+ from pragma_cli.helpers import OutputFormat, output_data
32
33
 
33
34
 
34
35
  app = typer.Typer(help="Provider management commands")
@@ -117,7 +118,9 @@ def get_template_source() -> str:
117
118
 
118
119
 
119
120
  @app.command("list")
120
- def list_providers():
121
+ def list_providers(
122
+ output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE,
123
+ ):
121
124
  """List all deployed providers.
122
125
 
123
126
  Shows providers with their deployment status. Displays:
@@ -126,8 +129,9 @@ def list_providers():
126
129
  - Status (running/stopped)
127
130
  - Last deployed timestamp
128
131
 
129
- Example:
132
+ Examples:
130
133
  pragma providers list
134
+ pragma providers list -o json
131
135
 
132
136
  Raises:
133
137
  typer.Exit: If authentication is missing or API call fails.
@@ -158,7 +162,12 @@ def list_providers():
158
162
  console.print("[dim]No providers found.[/dim]")
159
163
  return
160
164
 
161
- _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)
162
171
 
163
172
 
164
173
  def _print_providers_table(providers: list[ProviderInfo]) -> None:
@@ -422,7 +431,7 @@ def push(
422
431
  client = get_client()
423
432
 
424
433
  if client._auth is None:
425
- console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
434
+ console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
426
435
  raise typer.Exit(1)
427
436
 
428
437
  try:
@@ -431,10 +440,10 @@ def push(
431
440
  if not wait:
432
441
  console.print()
433
442
  console.print("[dim]Build running in background. Check status with:[/dim]")
434
- console.print(f" pragma providers status {provider_id} --job {push_result.job_name}")
443
+ console.print(f" pragma providers builds {provider_id} {push_result.version}")
435
444
  return
436
445
 
437
- _wait_for_build(client, provider_id, push_result.job_name, logs)
446
+ _wait_for_build(client, provider_id, push_result.version, logs)
438
447
 
439
448
  if deploy:
440
449
  console.print()
@@ -455,7 +464,7 @@ def _upload_code(client: PragmaClient, provider_id: str, tarball: bytes) -> Push
455
464
  tarball: Gzipped tarball bytes of provider source.
456
465
 
457
466
  Returns:
458
- PushResult with build job details.
467
+ PushResult with build details including version.
459
468
  """
460
469
  with Progress(
461
470
  SpinnerColumn(),
@@ -466,42 +475,42 @@ def _upload_code(client: PragmaClient, provider_id: str, tarball: bytes) -> Push
466
475
  progress.add_task("Uploading code...", total=None)
467
476
  push_result = client.push_provider(provider_id, tarball)
468
477
 
469
- console.print(f"[green]Build started:[/green] {push_result.job_name}")
478
+ console.print(f"[green]Build started:[/green] {push_result.version}")
470
479
  return push_result
471
480
 
472
481
 
473
482
  def _wait_for_build(
474
483
  client: PragmaClient,
475
484
  provider_id: str,
476
- job_name: str,
485
+ version: str,
477
486
  logs: bool,
478
- ) -> BuildResult:
487
+ ) -> BuildInfo:
479
488
  """Wait for build to complete, optionally streaming logs.
480
489
 
481
490
  Args:
482
491
  client: SDK client instance.
483
492
  provider_id: Provider identifier.
484
- job_name: Build job name.
493
+ version: CalVer version string.
485
494
  logs: Whether to stream build logs.
486
495
 
487
496
  Returns:
488
- Final BuildResult.
497
+ Final BuildInfo.
489
498
 
490
499
  Raises:
491
500
  typer.Exit: On build failure or timeout.
492
501
  """
493
502
  if logs:
494
- _stream_build_logs(client, provider_id, job_name)
503
+ _stream_build_logs(client, provider_id, version)
495
504
  else:
496
- build_result = _poll_build_status(client, provider_id, job_name)
505
+ build_result = _poll_build_status(client, provider_id, version)
497
506
 
498
507
  if build_result.status == BuildStatus.FAILED:
499
508
  console.print(f"[red]Build failed:[/red] {build_result.error_message}")
500
509
  raise typer.Exit(1)
501
510
 
502
- console.print(f"[green]Build successful:[/green] {build_result.image}")
511
+ console.print(f"[green]Build successful:[/green] {build_result.version}")
503
512
 
504
- final_build = client.get_build_status(provider_id, job_name)
513
+ final_build = client.get_build_status(provider_id, version)
505
514
 
506
515
  if final_build.status != BuildStatus.SUCCESS:
507
516
  console.print(f"[red]Build failed:[/red] {final_build.error_message}")
@@ -510,16 +519,16 @@ def _wait_for_build(
510
519
  return final_build
511
520
 
512
521
 
513
- 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:
514
523
  """Poll build status until completion or timeout.
515
524
 
516
525
  Args:
517
526
  client: SDK client instance.
518
527
  provider_id: Provider identifier.
519
- job_name: Build job name.
528
+ version: CalVer version string.
520
529
 
521
530
  Returns:
522
- Final BuildResult.
531
+ Final BuildInfo.
523
532
 
524
533
  Raises:
525
534
  typer.Exit: If build times out.
@@ -535,7 +544,7 @@ def _poll_build_status(client: PragmaClient, provider_id: str, job_name: str) ->
535
544
  task = progress.add_task("Building...", total=None)
536
545
 
537
546
  while True:
538
- build_result = client.get_build_status(provider_id, job_name)
547
+ build_result = client.get_build_status(provider_id, version)
539
548
 
540
549
  if build_result.status in (BuildStatus.SUCCESS, BuildStatus.FAILED):
541
550
  return build_result
@@ -549,33 +558,31 @@ def _poll_build_status(client: PragmaClient, provider_id: str, job_name: str) ->
549
558
  time.sleep(BUILD_POLL_INTERVAL)
550
559
 
551
560
 
552
- 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:
553
562
  """Stream build logs to console.
554
563
 
555
564
  Args:
556
565
  client: SDK client instance.
557
566
  provider_id: Provider identifier.
558
- job_name: Build job name.
567
+ version: CalVer version string.
559
568
  """
560
569
  console.print()
561
570
  console.print("[bold]Build logs:[/bold]")
562
571
  console.print("-" * 40)
563
572
 
564
573
  try:
565
- with client.stream_build_logs(provider_id, job_name) as response:
574
+ with client.stream_build_logs(provider_id, version) as response:
566
575
  for line in response.iter_lines():
567
576
  console.print(line)
568
577
  except httpx.HTTPError as e:
569
578
  console.print(f"[yellow]Warning:[/yellow] Could not stream logs: {e}")
570
579
  console.print("[dim]Falling back to polling...[/dim]")
571
- _poll_build_status(client, provider_id, job_name)
580
+ _poll_build_status(client, provider_id, version)
572
581
 
573
582
  console.print("-" * 40)
574
583
 
575
584
 
576
- def _deploy_provider(
577
- client: PragmaClient, provider_id: str, version: str | None = None
578
- ) -> None:
585
+ def _deploy_provider(client: PragmaClient, provider_id: str, version: str | None = None) -> None:
579
586
  """Deploy the provider to a specific version.
580
587
 
581
588
  Args:
@@ -592,20 +599,27 @@ def _deploy_provider(
592
599
  progress.add_task("Deploying...", total=None)
593
600
  deploy_result = client.deploy_provider(provider_id, version)
594
601
 
595
- console.print(f"[green]Deployment started:[/green] {deploy_result.deployment_name}")
596
- console.print(f"[dim]Version:[/dim] {deploy_result.version}")
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}")
597
605
  console.print(f"[dim]Status:[/dim] {deploy_result.status.value}")
598
606
 
599
607
 
600
608
  @app.command()
601
609
  def deploy(
610
+ provider_id: Annotated[
611
+ str,
612
+ typer.Argument(
613
+ help="Provider ID to deploy (e.g., 'postgres', 'my-provider')",
614
+ autocompletion=completion_provider_ids,
615
+ ),
616
+ ],
602
617
  version: Annotated[
603
618
  str | None,
604
- typer.Option("--version", "-v", help="Version to deploy (e.g., 20250115.120000). Defaults to latest."),
605
- ] = None,
606
- package: Annotated[
607
- str | None,
608
- 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
+ ),
609
623
  ] = None,
610
624
  ):
611
625
  """Deploy a provider to a specific version.
@@ -614,23 +628,14 @@ def deploy(
614
628
  the latest successful build. Use 'pragma providers builds' to see available versions.
615
629
 
616
630
  Deploy latest:
617
- pragma providers deploy
631
+ pragma providers deploy postgres
618
632
 
619
633
  Deploy specific version:
620
- pragma providers deploy --version 20250115.120000
634
+ pragma providers deploy postgres 20250115.120000
621
635
 
622
636
  Raises:
623
637
  typer.Exit: If deployment fails.
624
638
  """
625
- provider_name = package or detect_provider_package()
626
-
627
- if not provider_name:
628
- console.print("[red]Error:[/red] Could not detect provider package.")
629
- console.print("Run from a provider directory or specify --package")
630
- raise typer.Exit(1)
631
-
632
- provider_id = provider_name.replace("_", "-").removesuffix("-provider")
633
-
634
639
  console.print(f"[bold]Deploying provider:[/bold] {provider_id}")
635
640
  if version:
636
641
  console.print(f"[dim]Version:[/dim] {version}")
@@ -717,7 +722,7 @@ def delete(
717
722
  client = get_client()
718
723
 
719
724
  if client._auth is None:
720
- console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
725
+ console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
721
726
  raise typer.Exit(1)
722
727
 
723
728
  console.print(f"[bold]Provider:[/bold] {provider_id}")
@@ -765,23 +770,12 @@ def _print_delete_result(result: ProviderDeleteResult) -> None:
765
770
  """
766
771
  console.print()
767
772
  console.print(f"[green]Provider deleted:[/green] {result.provider_id}")
768
- console.print()
769
773
 
770
- table = Table(show_header=True, header_style="bold")
771
- table.add_column("Component")
772
- table.add_column("Deleted", justify="right")
773
-
774
- table.add_row("Builds Cancelled", str(result.builds_cancelled))
775
- table.add_row("Source Archives", str(result.source_archives_deleted))
776
- table.add_row("Deployment", "Yes" if result.deployment_deleted else "No (not found)")
777
- table.add_row("Resources", str(result.resources_deleted))
778
- table.add_row("Resource Definitions", str(result.resource_definitions_deleted))
779
- table.add_row("Outbox Events", str(result.outbox_events_deleted))
780
- table.add_row("Dead Letter Events", str(result.dead_letter_events_deleted))
781
- table.add_row("NATS Messages Purged", str(result.messages_purged))
782
- 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]")
783
776
 
784
- console.print(table)
777
+ if result.resources_deleted > 0:
778
+ console.print(f"[dim]Resources deleted: {result.resources_deleted}[/dim]")
785
779
 
786
780
 
787
781
  @app.command()
@@ -793,6 +787,7 @@ def status(
793
787
  autocompletion=completion_provider_ids,
794
788
  ),
795
789
  ],
790
+ output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE,
796
791
  ):
797
792
  """Check the deployment status of a provider.
798
793
 
@@ -802,9 +797,10 @@ def status(
802
797
  - Health status
803
798
  - Last updated timestamp
804
799
 
805
- Example:
800
+ Examples:
806
801
  pragma providers status postgres
807
802
  pragma providers status my-provider
803
+ pragma providers status postgres -o json
808
804
 
809
805
  Raises:
810
806
  typer.Exit: If deployment not found or status check fails.
@@ -827,7 +823,13 @@ def status(
827
823
  console.print(f"[red]Error:[/red] {e}")
828
824
  raise typer.Exit(1)
829
825
 
830
- _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)
831
833
 
832
834
 
833
835
  def _print_deployment_status(provider_id: str, result) -> None:
@@ -835,7 +837,7 @@ def _print_deployment_status(provider_id: str, result) -> None:
835
837
 
836
838
  Args:
837
839
  provider_id: Provider identifier.
838
- result: ProviderStatus from the API.
840
+ result: DeploymentResult from the API.
839
841
  """
840
842
  status_colors = {
841
843
  "pending": "yellow",
@@ -855,10 +857,12 @@ def _print_deployment_status(provider_id: str, result) -> None:
855
857
 
856
858
  table.add_row("Status", f"[{status_color}]{result.status.value}[/{status_color}]")
857
859
 
858
- if result.version:
859
- table.add_row("Version", result.version)
860
+ if result.image:
861
+ table.add_row("Image", result.image)
860
862
 
861
- healthy_display = "[green]yes[/green]" if result.healthy else "[red]no[/red]"
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]"
862
866
  table.add_row("Healthy", healthy_display)
863
867
 
864
868
  if result.updated_at:
@@ -892,7 +896,7 @@ def builds(
892
896
  client = get_client()
893
897
 
894
898
  if client._auth is None:
895
- console.print("[red]Error:[/red] Authentication required. Run 'pragma login' first.")
899
+ console.print("[red]Error:[/red] Authentication required. Run 'pragma auth login' first.")
896
900
  raise typer.Exit(1)
897
901
 
898
902
  try:
@@ -6,24 +6,83 @@ import json
6
6
  from pathlib import Path
7
7
  from typing import Annotated
8
8
 
9
+ import httpx
9
10
  import typer
10
11
  import yaml
11
12
  from rich import print
12
13
  from rich.console import Console
13
14
  from rich.markup import escape
15
+ from rich.table import Table
14
16
 
15
17
  from pragma_cli import get_client
16
18
  from pragma_cli.commands.completions import (
17
19
  completion_resource_ids,
18
20
  completion_resource_names,
19
21
  )
20
- from pragma_cli.helpers import parse_resource_id
22
+ from pragma_cli.helpers import OutputFormat, output_data, parse_resource_id
21
23
 
22
24
 
23
25
  console = Console()
24
26
  app = typer.Typer()
25
27
 
26
28
 
29
+ def _format_api_error(error: httpx.HTTPStatusError) -> str:
30
+ """Format an API error response with structured details.
31
+
32
+ Returns:
33
+ Formatted error message with details extracted from JSON response.
34
+ """
35
+ try:
36
+ detail = error.response.json().get("detail", {})
37
+ except (json.JSONDecodeError, ValueError):
38
+ # Fall back to plain text if not JSON
39
+ return error.response.text or str(error)
40
+
41
+ # Handle simple string details
42
+ if isinstance(detail, str):
43
+ return detail
44
+
45
+ # Handle structured error responses
46
+ message = detail.get("message", str(error))
47
+ parts = [message]
48
+
49
+ # DependencyValidationError details
50
+ if missing := detail.get("missing_dependencies"):
51
+ parts.append("\n Missing dependencies:")
52
+ for dep_id in missing:
53
+ parts.append(f" - {dep_id}")
54
+ if not_ready := detail.get("not_ready_dependencies"):
55
+ parts.append("\n Dependencies not ready:")
56
+ for item in not_ready:
57
+ if isinstance(item, dict):
58
+ parts.append(f" - {item['id']} (state: {item['state']})")
59
+ else:
60
+ parts.append(f" - {item}")
61
+
62
+ # FieldReferenceError details
63
+ if field := detail.get("field"):
64
+ ref_parts = [
65
+ detail.get("reference_provider", ""),
66
+ detail.get("reference_resource", ""),
67
+ detail.get("reference_name", ""),
68
+ ]
69
+ ref_id = "/".join(filter(None, ref_parts))
70
+ if ref_id:
71
+ parts.append(f"\n Reference: {ref_id}#{field}")
72
+
73
+ # InvalidLifecycleTransitionError details
74
+ if current_state := detail.get("current_state"):
75
+ target_state = detail.get("target_state", "unknown")
76
+ parts.append(f"\n Current state: {current_state}")
77
+ parts.append(f" Target state: {target_state}")
78
+
79
+ # ResourceInProcessingError details
80
+ if resource_id := detail.get("resource_id"):
81
+ parts.append(f"\n Resource: {resource_id}")
82
+
83
+ return "".join(parts)
84
+
85
+
27
86
  def resolve_file_references(resource: dict, base_dir: Path) -> dict:
28
87
  """Resolve file references in secret resource config.
29
88
 
@@ -55,7 +114,7 @@ def resolve_file_references(resource: dict, base_dir: Path) -> dict:
55
114
  resolved_data = {}
56
115
  for key, value in data.items():
57
116
  if isinstance(value, str) and value.startswith("@"):
58
- file_path = Path(value[1:])
117
+ file_path = Path(value[1:]).expanduser()
59
118
  if not file_path.is_absolute():
60
119
  file_path = base_dir / file_path
61
120
 
@@ -85,13 +144,76 @@ def format_state(state: str) -> str:
85
144
  return escape(f"[{state}]")
86
145
 
87
146
 
147
+ def _print_resource_types_table(types: list[dict]) -> None:
148
+ """Print resource types in a formatted table.
149
+
150
+ Args:
151
+ types: List of resource type dictionaries to display.
152
+ """
153
+ console.print()
154
+ table = Table(show_header=True, header_style="bold")
155
+ table.add_column("Provider")
156
+ table.add_column("Resource")
157
+ table.add_column("Description")
158
+
159
+ for resource_type in types:
160
+ description = resource_type.get("description") or "[dim]—[/dim]"
161
+ table.add_row(
162
+ resource_type["provider"],
163
+ resource_type["resource"],
164
+ description,
165
+ )
166
+
167
+ console.print(table)
168
+ console.print()
169
+
170
+
171
+ @app.command("types")
172
+ def list_resource_types(
173
+ provider: Annotated[str | None, typer.Option("--provider", "-p", help="Filter by provider")] = None,
174
+ output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE,
175
+ ):
176
+ """List available resource types from deployed providers.
177
+
178
+ Displays resource definitions (types) that have been registered by providers.
179
+ Use this to discover what resources you can create.
180
+
181
+ Examples:
182
+ pragma resources types
183
+ pragma resources types --provider gcp
184
+ pragma resources types -o json
185
+
186
+ Raises:
187
+ typer.Exit: If an error occurs while fetching resource types.
188
+ """
189
+ client = get_client()
190
+ try:
191
+ types = client.list_resource_types(provider=provider)
192
+ except httpx.HTTPStatusError as e:
193
+ console.print(f"[red]Error:[/red] {_format_api_error(e)}")
194
+ raise typer.Exit(1)
195
+
196
+ if not types:
197
+ console.print("[dim]No resource types found.[/dim]")
198
+ return
199
+
200
+ output_data(types, output, table_renderer=_print_resource_types_table)
201
+
202
+
88
203
  @app.command("list")
89
204
  def list_resources(
90
205
  provider: Annotated[str | None, typer.Option("--provider", "-p", help="Filter by provider")] = None,
91
206
  resource: Annotated[str | None, typer.Option("--resource", "-r", help="Filter by resource type")] = None,
92
207
  tags: Annotated[list[str] | None, typer.Option("--tag", "-t", help="Filter by tags")] = None,
208
+ output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE,
93
209
  ):
94
- """List resources, optionally filtered by provider, resource type, or tags."""
210
+ """List resources, optionally filtered by provider, resource type, or tags.
211
+
212
+ Examples:
213
+ pragma resources list
214
+ pragma resources list --provider gcp
215
+ pragma resources list -o json
216
+ """
95
217
  client = get_client()
96
218
  resources = list(client.list_resources(provider=provider, resource=resource, tags=tags))
97
219
 
@@ -99,24 +221,211 @@ def list_resources(
99
221
  console.print("[dim]No resources found.[/dim]")
100
222
  return
101
223
 
224
+ output_data(resources, output, table_renderer=_print_resources_table)
225
+
226
+
227
+ def _print_resources_table(resources: list[dict]) -> None:
228
+ """Print resources in a formatted table.
229
+
230
+ Args:
231
+ resources: List of resource dictionaries to display.
232
+ """
233
+ table = Table(show_header=True, header_style="bold")
234
+ table.add_column("Provider")
235
+ table.add_column("Resource")
236
+ table.add_column("Name")
237
+ table.add_column("State")
238
+ table.add_column("Updated")
239
+
240
+ # Track failed resources to show errors after table
241
+ failed_resources: list[tuple[str, str]] = []
242
+
102
243
  for res in resources:
103
- print(f"{res['provider']}/{res['resource']}/{res['name']} {format_state(res['lifecycle_state'])}")
244
+ state = _format_state_color(res["lifecycle_state"])
245
+ updated = res.get("updated_at", "[dim]-[/dim]")
246
+ if updated and updated != "[dim]-[/dim]":
247
+ # Truncate to datetime portion if it's a full ISO string
248
+ updated = updated[:19].replace("T", " ") if len(updated) > 19 else updated
249
+
250
+ table.add_row(
251
+ res["provider"],
252
+ res["resource"],
253
+ res["name"],
254
+ state,
255
+ updated,
256
+ )
257
+
258
+ # Track failed resources for error display
259
+ if res.get("lifecycle_state") == "failed" and res.get("error"):
260
+ resource_id = f"{res['provider']}/{res['resource']}/{res['name']}"
261
+ failed_resources.append((resource_id, res["error"]))
262
+
263
+ console.print(table)
264
+
265
+ # Show errors for failed resources below the table
266
+ for resource_id, error in failed_resources:
267
+ console.print(f" [red]{resource_id}:[/red] {escape(error)}")
104
268
 
105
269
 
106
270
  @app.command()
107
271
  def get(
108
272
  resource_id: Annotated[str, typer.Argument(autocompletion=completion_resource_ids)],
109
273
  name: Annotated[str | None, typer.Argument(autocompletion=completion_resource_names)] = None,
274
+ output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE,
110
275
  ):
111
- """Get resources by provider/resource type, optionally filtered by name."""
276
+ """Get resources by provider/resource type, optionally filtered by name.
277
+
278
+ Examples:
279
+ pragma resources get gcp/secret
280
+ pragma resources get gcp/secret my-secret
281
+ pragma resources get gcp/secret my-secret -o json
282
+ """
112
283
  client = get_client()
113
284
  provider, resource = parse_resource_id(resource_id)
114
285
  if name:
115
286
  res = client.get_resource(provider=provider, resource=resource, name=name)
116
- print(f"{resource_id}/{res['name']} {format_state(res['lifecycle_state'])}")
287
+ output_data([res], output, table_renderer=_print_resources_table)
117
288
  else:
118
- for res in client.list_resources(provider=provider, resource=resource):
119
- print(f"{resource_id}/{res['name']} {format_state(res['lifecycle_state'])}")
289
+ resources = list(client.list_resources(provider=provider, resource=resource))
290
+ if not resources:
291
+ console.print("[dim]No resources found.[/dim]")
292
+ return
293
+ output_data(resources, output, table_renderer=_print_resources_table)
294
+
295
+
296
+ def _format_state_color(state: str) -> str:
297
+ """Format lifecycle state with color markup.
298
+
299
+ Returns:
300
+ State string wrapped in Rich color markup.
301
+ """
302
+ state_colors = {
303
+ "draft": "dim",
304
+ "pending": "yellow",
305
+ "processing": "cyan",
306
+ "ready": "green",
307
+ "failed": "red",
308
+ }
309
+ color = state_colors.get(state.lower(), "white")
310
+ return f"[{color}]{state}[/{color}]"
311
+
312
+
313
+ def _format_config_value(value, *, redact_keys: set[str] | None = None) -> str:
314
+ """Format a config value, redacting sensitive fields.
315
+
316
+ Returns:
317
+ Formatted string representation with sensitive values masked.
318
+ """
319
+ redact_keys = redact_keys or {"credentials", "password", "secret", "token", "key", "data"}
320
+ if isinstance(value, dict):
321
+ # Check if this is a FieldReference
322
+ if "provider" in value and "resource" in value and "name" in value and "field" in value:
323
+ return f"{value['provider']}/{value['resource']}/{value['name']}#{value['field']}"
324
+ # Recursively format nested dicts
325
+ formatted = {}
326
+ for k, v in value.items():
327
+ if k.lower() in redact_keys:
328
+ formatted[k] = "********"
329
+ else:
330
+ formatted[k] = _format_config_value(v, redact_keys=redact_keys)
331
+ return str(formatted)
332
+ elif isinstance(value, list):
333
+ return str([_format_config_value(v, redact_keys=redact_keys) for v in value])
334
+ return str(value)
335
+
336
+
337
+ def _print_resource_details(res: dict) -> None:
338
+ """Print resource details in a formatted table."""
339
+ resource_id = f"{res['provider']}/{res['resource']}/{res['name']}"
340
+
341
+ console.print()
342
+ console.print(f"[bold]Resource:[/bold] {resource_id}")
343
+ console.print()
344
+
345
+ # Main properties table
346
+ table = Table(show_header=True, header_style="bold")
347
+ table.add_column("Property")
348
+ table.add_column("Value")
349
+
350
+ # State with color
351
+ table.add_row("State", _format_state_color(res["lifecycle_state"]))
352
+
353
+ # Error if failed
354
+ if res.get("error"):
355
+ table.add_row("Error", f"[red]{escape(res['error'])}[/red]")
356
+
357
+ # Timestamps
358
+ if res.get("created_at"):
359
+ table.add_row("Created", res["created_at"])
360
+ if res.get("updated_at"):
361
+ table.add_row("Updated", res["updated_at"])
362
+
363
+ console.print(table)
364
+
365
+ # Config section
366
+ config = res.get("config", {})
367
+ if config:
368
+ console.print()
369
+ console.print("[bold]Config:[/bold]")
370
+ for key, value in config.items():
371
+ formatted = _format_config_value(value)
372
+ console.print(f" {key}: {formatted}")
373
+
374
+ # Outputs section
375
+ outputs = res.get("outputs", {})
376
+ if outputs:
377
+ console.print()
378
+ console.print("[bold]Outputs:[/bold]")
379
+ for key, value in outputs.items():
380
+ console.print(f" {key}: {value}")
381
+
382
+ # Dependencies section
383
+ dependencies = res.get("dependencies", [])
384
+ if dependencies:
385
+ console.print()
386
+ console.print("[bold]Dependencies:[/bold]")
387
+ for dep in dependencies:
388
+ dep_id = f"{dep['provider']}/{dep['resource']}/{dep['name']}"
389
+ console.print(f" - {dep_id}")
390
+
391
+ # Tags section
392
+ tags = res.get("tags", [])
393
+ if tags:
394
+ console.print()
395
+ console.print("[bold]Tags:[/bold]")
396
+ console.print(f" {', '.join(tags)}")
397
+
398
+ console.print()
399
+
400
+
401
+ @app.command()
402
+ def describe(
403
+ resource_id: Annotated[str, typer.Argument(autocompletion=completion_resource_ids)],
404
+ name: Annotated[str, typer.Argument(autocompletion=completion_resource_names)],
405
+ output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE,
406
+ ):
407
+ """Show detailed information about a resource.
408
+
409
+ Displays the resource's config, outputs, dependencies, and error messages.
410
+
411
+ Examples:
412
+ pragma resources describe gcp/secret my-test-secret
413
+ pragma resources describe postgres/database my-db
414
+ pragma resources describe gcp/secret my-secret -o json
415
+
416
+ Raises:
417
+ typer.Exit: If the resource is not found or an error occurs.
418
+ """
419
+ client = get_client()
420
+ provider, resource = parse_resource_id(resource_id)
421
+
422
+ try:
423
+ res = client.get_resource(provider=provider, resource=resource, name=name)
424
+ except httpx.HTTPStatusError as e:
425
+ console.print(f"[red]Error:[/red] {_format_api_error(e)}")
426
+ raise typer.Exit(1)
427
+
428
+ output_data(res, output, table_renderer=_print_resource_details)
120
429
 
121
430
 
122
431
  @app.command()
@@ -134,6 +443,9 @@ def apply(
134
443
  For pragma/secret resources, file references in config.data values
135
444
  are resolved before submission. Use '@path/to/file' syntax to inline
136
445
  file contents.
446
+
447
+ Raises:
448
+ typer.Exit: If the apply operation fails.
137
449
  """
138
450
  client = get_client()
139
451
  for f in file:
@@ -144,9 +456,14 @@ def apply(
144
456
  resource = resolve_file_references(resource, base_dir)
145
457
  if pending:
146
458
  resource["lifecycle_state"] = "pending"
147
- result = client.apply_resource(resource=resource)
148
- res_id = f"{result['provider']}/{result['resource']}/{result['name']}"
149
- print(f"Applied {res_id} {format_state(result['lifecycle_state'])}")
459
+ res_id = f"{resource.get('provider', '?')}/{resource.get('resource', '?')}/{resource.get('name', '?')}"
460
+ try:
461
+ result = client.apply_resource(resource=resource)
462
+ res_id = f"{result['provider']}/{result['resource']}/{result['name']}"
463
+ print(f"Applied {res_id} {format_state(result['lifecycle_state'])}")
464
+ except httpx.HTTPStatusError as e:
465
+ console.print(f"[red]Error applying {res_id}:[/red] {_format_api_error(e)}")
466
+ raise typer.Exit(1)
150
467
 
151
468
 
152
469
  @app.command()
@@ -154,52 +471,16 @@ def delete(
154
471
  resource_id: Annotated[str, typer.Argument(autocompletion=completion_resource_ids)],
155
472
  name: Annotated[str, typer.Argument(autocompletion=completion_resource_names)],
156
473
  ):
157
- """Delete a resource."""
158
- client = get_client()
159
- provider, resource = parse_resource_id(resource_id)
160
- client.delete_resource(provider=provider, resource=resource, name=name)
161
- print(f"Deleted {resource_id}/{name}")
162
-
163
-
164
- @app.command()
165
- def register(
166
- resource_id: Annotated[str, typer.Argument(help="Resource type in provider/resource format")],
167
- description: Annotated[str | None, typer.Option("--description", "-d", help="Resource type description")] = None,
168
- schema_file: Annotated[typer.FileText | None, typer.Option("--schema", "-s", help="JSON schema file")] = None,
169
- tags: Annotated[list[str] | None, typer.Option("--tag", "-t", help="Tags for categorization")] = None,
170
- ):
171
- """Register a new resource type.
172
-
173
- Registers a resource type so that resources of this type can be created.
174
- Providers use this to declare what resources they can manage.
175
- """
176
- client = get_client()
177
- provider, resource = parse_resource_id(resource_id)
178
-
179
- schema = None
180
- if schema_file:
181
- schema = json.load(schema_file)
182
-
183
- client.register_resource(
184
- provider=provider,
185
- resource=resource,
186
- schema=schema,
187
- description=description,
188
- tags=tags,
189
- )
190
- print(f"Registered {resource_id}")
191
-
192
-
193
- @app.command()
194
- def unregister(
195
- resource_id: Annotated[str, typer.Argument(autocompletion=completion_resource_ids)],
196
- ):
197
- """Unregister a resource type.
474
+ """Delete a resource.
198
475
 
199
- Removes a resource type registration. Existing resources of this type
200
- will no longer be manageable.
476
+ Raises:
477
+ typer.Exit: If the resource is not found or deletion fails.
201
478
  """
202
479
  client = get_client()
203
480
  provider, resource = parse_resource_id(resource_id)
204
- client.unregister_resource(provider=provider, resource=resource)
205
- print(f"Unregistered {resource_id}")
481
+ try:
482
+ client.delete_resource(provider=provider, resource=resource, name=name)
483
+ print(f"Deleted {resource_id}/{name}")
484
+ except httpx.HTTPStatusError as e:
485
+ console.print(f"[red]Error deleting {resource_id}/{name}:[/red] {_format_api_error(e)}")
486
+ raise typer.Exit(1)
pragma_cli/config.py CHANGED
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import os
6
6
  from pathlib import Path
7
+ from urllib.parse import urlparse
7
8
 
8
9
  import yaml
9
10
  from pydantic import BaseModel
@@ -30,6 +31,24 @@ class ContextConfig(BaseModel):
30
31
  """Configuration for a single CLI context."""
31
32
 
32
33
  api_url: str
34
+ auth_url: str | None = None
35
+
36
+ def get_auth_url(self) -> str:
37
+ """Get the auth URL, deriving from api_url if not explicitly set.
38
+
39
+ Returns:
40
+ Auth URL for Clerk authentication.
41
+ """
42
+ if self.auth_url:
43
+ return self.auth_url
44
+
45
+ # Handle localhost: default to port 3000 for web app
46
+ parsed = urlparse(self.api_url)
47
+ if parsed.hostname in ("localhost", "127.0.0.1"):
48
+ return "http://localhost:3000"
49
+
50
+ # Derive from api_url: api.pragmatiks.io -> app.pragmatiks.io
51
+ return self.api_url.replace("://api.", "://app.")
33
52
 
34
53
 
35
54
  class PragmaConfig(BaseModel):
pragma_cli/helpers.py CHANGED
@@ -1,7 +1,46 @@
1
- """CLI helper functions for parsing resource identifiers."""
1
+ """CLI helper functions for parsing resource identifiers and output formatting."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import json
6
+ from enum import StrEnum
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ import yaml
10
+
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Callable
14
+
15
+
16
+ class OutputFormat(StrEnum):
17
+ """Output format options for CLI commands."""
18
+
19
+ TABLE = "table"
20
+ JSON = "json"
21
+ YAML = "yaml"
22
+
23
+
24
+ def output_data(
25
+ data: list[dict[str, Any]] | dict[str, Any],
26
+ format: OutputFormat,
27
+ table_renderer: Callable[..., None] | None = None,
28
+ ) -> None:
29
+ """Output data in the specified format.
30
+
31
+ Args:
32
+ data: Data to output (list of dicts or single dict).
33
+ format: Output format (table, json, yaml).
34
+ table_renderer: Function to render table output. Required for TABLE format.
35
+ """
36
+ if format == OutputFormat.TABLE:
37
+ if table_renderer:
38
+ table_renderer(data)
39
+ elif format == OutputFormat.JSON:
40
+ print(json.dumps(data, indent=2, default=str))
41
+ elif format == OutputFormat.YAML:
42
+ print(yaml.dump(data, default_flow_style=False, sort_keys=False))
43
+
5
44
 
6
45
  def parse_resource_id(resource_id: str) -> tuple[str, str]:
7
46
  """Parse resource identifier into provider and resource type.
pragma_cli/main.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """CLI entry point with Typer application setup and command routing."""
2
2
 
3
+ from importlib.metadata import version as get_version
3
4
  from typing import Annotated
4
5
 
5
6
  import typer
@@ -13,9 +14,34 @@ from pragma_cli.config import get_current_context
13
14
  app = typer.Typer()
14
15
 
15
16
 
17
+ def _version_callback(value: bool) -> None:
18
+ """Print version and exit if --version flag is provided.
19
+
20
+ Args:
21
+ value: True if --version flag was provided.
22
+
23
+ Raises:
24
+ typer.Exit: Always exits after displaying version.
25
+ """
26
+ if value:
27
+ package_version = get_version("pragmatiks-cli")
28
+ typer.echo(f"pragma {package_version}")
29
+ raise typer.Exit()
30
+
31
+
16
32
  @app.callback()
17
33
  def main(
18
34
  ctx: typer.Context,
35
+ version: Annotated[
36
+ bool | None,
37
+ typer.Option(
38
+ "--version",
39
+ "-V",
40
+ help="Show version and exit",
41
+ callback=_version_callback,
42
+ is_eager=True,
43
+ ),
44
+ ] = None,
19
45
  context: Annotated[
20
46
  str | None,
21
47
  typer.Option(
@@ -37,14 +63,14 @@ def main(
37
63
  """Pragma CLI - Declarative resource management.
38
64
 
39
65
  Authentication (industry-standard pattern):
40
- - CLI writes credentials: 'pragma login' stores tokens in ~/.config/pragma/credentials
66
+ - CLI writes credentials: 'pragma auth login' stores tokens in ~/.config/pragma/credentials
41
67
  - SDK reads credentials: Automatic token discovery via precedence chain
42
68
 
43
69
  Token Discovery Precedence:
44
70
  1. --token flag (explicit override)
45
71
  2. PRAGMA_AUTH_TOKEN_<CONTEXT> context-specific environment variable
46
72
  3. PRAGMA_AUTH_TOKEN environment variable
47
- 4. ~/.config/pragma/credentials file (from pragma login)
73
+ 4. ~/.config/pragma/credentials file (from pragma auth login)
48
74
  5. No authentication
49
75
  """
50
76
  context_name, context_config = get_current_context(context)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pragmatiks-cli
3
- Version: 0.8.1
3
+ Version: 0.12.5
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
@@ -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=ADpPH8jDbpwhyRzwvG_4PAg2oxVbt2vGx1LrAZNIq-I,9968
4
+ pragma_cli/commands/completions.py,sha256=0SFfq1V0QLjLc4Kr6wcRvQF0UQpB9lZ6V5fU6SVtsbM,3610
5
+ pragma_cli/commands/config.py,sha256=fq4G6DwgTaEZLZIcGdLXzEh4Zrv6M6_ew0IU7WytQk0,2648
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=Ab7Oh-WGYvl8CauGiG3e2bFJVFo4kB4NbHXsolsFNoo,29265
9
+ pragma_cli/commands/resources.py,sha256=HpgxD-Rx8IWk1CBVwRcxuhEYd8xe0vqSrsPwfzotiJU,16533
10
+ pragma_cli/config.py,sha256=2r9kcBrh700AWF0H3gMIpYX8uXFSe2Yk2T8tuXzjCaU,2984
11
+ pragma_cli/helpers.py,sha256=lR-s_Q7YNuWcPeluHZO3RrsdpKq8ndwWYLSlzRvY35w,1681
12
+ pragma_cli/main.py,sha256=_S2X3QLfuGcULBfwezp4RBK1_PFkHY_L_8j0hZbT2gk,2676
13
+ pragma_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ pragmatiks_cli-0.12.5.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
15
+ pragmatiks_cli-0.12.5.dist-info/entry_points.txt,sha256=9xeQQlnHxq94dks6mlJ2I9LuMUKmqxuJzyKSZCb9iJM,48
16
+ pragmatiks_cli-0.12.5.dist-info/METADATA,sha256=E8HFQsJjup_TPTswEsrHq8MFuKm8HpPdt5MoRCZWXF0,4466
17
+ pragmatiks_cli-0.12.5.dist-info/RECORD,,
@@ -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=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,,