cloudwright-ai-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ """Lint an architecture spec for anti-patterns."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Annotated
8
+
9
+ import typer
10
+ from rich.console import Console
11
+ from rich.table import Table
12
+ from rich.text import Text
13
+
14
+ from cloudwright_cli.utils import handle_error
15
+
16
+ console = Console()
17
+
18
+
19
+ def lint(
20
+ ctx: typer.Context,
21
+ spec_file: Annotated[Path, typer.Argument(help="Architecture spec YAML file", exists=True)],
22
+ output: Annotated[str, typer.Option(help="Output format: text, json")] = "text",
23
+ strict: Annotated[bool, typer.Option(help="Fail on warnings too")] = False,
24
+ ) -> None:
25
+ """Detect architecture anti-patterns in a spec file."""
26
+ try:
27
+ from cloudwright import ArchSpec
28
+ from cloudwright.linter import lint as run_lint
29
+
30
+ spec = ArchSpec.from_file(spec_file)
31
+ warnings = run_lint(spec)
32
+
33
+ if output == "json":
34
+ result = [
35
+ {
36
+ "rule": w.rule,
37
+ "severity": w.severity,
38
+ "component": w.component,
39
+ "message": w.message,
40
+ "recommendation": w.recommendation,
41
+ }
42
+ for w in warnings
43
+ ]
44
+ print(json.dumps(result, indent=2))
45
+ else:
46
+ if not warnings:
47
+ console.print(f"[green][PASS][/green] No anti-patterns detected in {spec.name}")
48
+ else:
49
+ table = Table(title=f"Lint Results: {spec.name}", show_lines=True)
50
+ table.add_column("Severity", width=9)
51
+ table.add_column("Rule", style="cyan")
52
+ table.add_column("Component")
53
+ table.add_column("Message")
54
+ table.add_column("Recommendation")
55
+
56
+ for w in warnings:
57
+ if w.severity == "error":
58
+ sev = Text("error", style="bold red")
59
+ elif w.severity == "warning":
60
+ sev = Text("warning", style="yellow")
61
+ else:
62
+ sev = Text("info", style="blue")
63
+
64
+ table.add_row(
65
+ sev,
66
+ w.rule,
67
+ w.component or "—",
68
+ w.message,
69
+ w.recommendation,
70
+ )
71
+
72
+ console.print(table)
73
+
74
+ errors = [w for w in warnings if w.severity == "error"]
75
+ warns = [w for w in warnings if w.severity == "warning"]
76
+ console.print(
77
+ f"\n[bold]{len(warnings)} finding(s)[/bold]: "
78
+ f"[red]{len(errors)} error(s)[/red], "
79
+ f"[yellow]{len(warns)} warning(s)[/yellow]"
80
+ )
81
+
82
+ has_errors = any(w.severity == "error" for w in warnings)
83
+ has_warnings = any(w.severity == "warning" for w in warnings)
84
+
85
+ if has_errors or (strict and has_warnings):
86
+ raise typer.Exit(1)
87
+
88
+ except typer.Exit:
89
+ raise
90
+ except Exception as e:
91
+ handle_error(ctx, e)
@@ -0,0 +1,125 @@
1
+ """Structured modify — modify a spec with natural language and show the diff."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Annotated
7
+
8
+ import typer
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+ from rich.rule import Rule
12
+ from rich.syntax import Syntax
13
+ from rich.table import Table
14
+
15
+ from cloudwright_cli.utils import handle_error
16
+
17
+ console = Console()
18
+
19
+
20
+ def modify(
21
+ ctx: typer.Context,
22
+ spec_file: Annotated[str, typer.Argument(help="Path to the ArchSpec YAML to modify")],
23
+ instruction: Annotated[str, typer.Argument(help="Natural language modification instruction")],
24
+ output: Annotated[str | None, typer.Option("--output", "-o", help="Output file (default: overwrite input)")] = None,
25
+ dry_run: Annotated[bool, typer.Option("--dry-run", help="Show changes without writing")] = False,
26
+ json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
27
+ ) -> None:
28
+ """Modify an ArchSpec with natural language and show the diff."""
29
+ try:
30
+ from cloudwright import ArchSpec, Differ
31
+ from cloudwright.architect import Architect
32
+ from cloudwright.cost import CostEngine
33
+
34
+ spec_path = Path(spec_file)
35
+ if not spec_path.exists():
36
+ console.print(f"[red]Error:[/red] Spec file not found: {spec_file}")
37
+ raise typer.Exit(1)
38
+
39
+ original = ArchSpec.from_file(spec_path)
40
+
41
+ console.print(f"Modifying [cyan]{spec_file}[/cyan]: [yellow]{instruction}[/yellow]\n")
42
+
43
+ with console.status("Applying modification..."):
44
+ architect = Architect()
45
+ modified = architect.modify(original, instruction)
46
+
47
+ # Price both versions; ignore errors (catalog may not have all services)
48
+ cost_engine = CostEngine()
49
+ try:
50
+ original_costed = cost_engine.price(original)
51
+ modified_costed = cost_engine.price(modified)
52
+ except Exception:
53
+ original_costed = original
54
+ modified_costed = modified
55
+
56
+ diff_result = Differ().diff(original_costed, modified_costed)
57
+
58
+ if json_output:
59
+ import json
60
+
61
+ result = {
62
+ "original": original.model_dump(),
63
+ "modified": modified.model_dump(),
64
+ "diff": diff_result.model_dump(),
65
+ }
66
+ console.print_json(json.dumps(result, default=str))
67
+ return
68
+
69
+ console.print(Rule("[bold]Changes[/bold]"))
70
+
71
+ if not diff_result.added and not diff_result.removed and not diff_result.changed:
72
+ console.print("[dim]No structural changes detected.[/dim]")
73
+ else:
74
+ if diff_result.added:
75
+ console.print(f"[bold green]Added ({len(diff_result.added)})[/bold green]")
76
+ for comp in diff_result.added:
77
+ console.print(f" [green]+[/green] {comp.id} ({comp.service}) — {comp.label}")
78
+ console.print()
79
+
80
+ if diff_result.removed:
81
+ console.print(f"[bold red]Removed ({len(diff_result.removed)})[/bold red]")
82
+ for comp in diff_result.removed:
83
+ console.print(f" [red]-[/red] {comp.id} ({comp.service}) — {comp.label}")
84
+ console.print()
85
+
86
+ if diff_result.changed:
87
+ table = Table(title="Changed")
88
+ table.add_column("Component", style="cyan")
89
+ table.add_column("Field")
90
+ table.add_column("Before", style="red")
91
+ table.add_column("After", style="green")
92
+ for change in diff_result.changed:
93
+ table.add_row(change.component_id, change.field, change.old_value, change.new_value)
94
+ console.print(table)
95
+ console.print()
96
+
97
+ if diff_result.cost_delta != 0.0:
98
+ sign = "+" if diff_result.cost_delta > 0 else ""
99
+ color = "red" if diff_result.cost_delta > 0 else "green"
100
+ console.print(f"Cost delta: [{color}]{sign}${diff_result.cost_delta:,.2f}/mo[/{color}]")
101
+
102
+ if diff_result.compliance_impact:
103
+ console.print("\n[bold red]Compliance Impact[/bold red]")
104
+ for impact in diff_result.compliance_impact:
105
+ console.print(f" [red]![/red] {impact}")
106
+
107
+ console.print()
108
+ console.print(
109
+ Panel(
110
+ Syntax(modified.to_yaml(), "yaml", theme="monokai", word_wrap=True),
111
+ title="[bold cyan]Modified Spec[/bold cyan]",
112
+ )
113
+ )
114
+
115
+ if not dry_run:
116
+ out_path = Path(output) if output else spec_path
117
+ out_path.write_text(modified.to_yaml())
118
+ console.print(f"\n[green]Written to {out_path}[/green]")
119
+ else:
120
+ console.print("\n[dim]Dry run — no files written.[/dim]")
121
+
122
+ except typer.Exit:
123
+ raise
124
+ except Exception as e:
125
+ handle_error(ctx, e)
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Annotated
6
+
7
+ import typer
8
+ from cloudwright import ArchSpec
9
+ from cloudwright.cost import CostEngine
10
+ from cloudwright.policy import PolicyEngine
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+
14
+ console = Console()
15
+
16
+
17
+ def policy(
18
+ ctx: typer.Context,
19
+ spec_file: Annotated[Path, typer.Argument(help="Path to spec YAML file", exists=True)],
20
+ rules: Annotated[Path, typer.Option("--rules", "-r", help="Path to policy rules YAML file", exists=True)],
21
+ ) -> None:
22
+ """Evaluate an architecture spec against policy rules."""
23
+ try:
24
+ spec = ArchSpec.from_file(spec_file)
25
+ engine = PolicyEngine()
26
+
27
+ cost_estimate = spec.cost_estimate
28
+ if not cost_estimate:
29
+ cost_engine = CostEngine()
30
+ cost_estimate = cost_engine.estimate(spec)
31
+
32
+ result = engine.evaluate_from_file(spec, rules, cost_estimate=cost_estimate)
33
+
34
+ json_mode = ctx.obj.get("json", False) if ctx.obj else False
35
+ if json_mode:
36
+ print(json.dumps(result.model_dump(), default=str))
37
+ if not result.passed:
38
+ raise typer.Exit(1)
39
+ return
40
+
41
+ table = Table(title="Policy Evaluation")
42
+ table.add_column("Status", width=6)
43
+ table.add_column("Rule", style="cyan")
44
+ table.add_column("Severity")
45
+ table.add_column("Message", style="dim")
46
+
47
+ for check in result.results:
48
+ if check.passed:
49
+ status = "[green]PASS[/green]"
50
+ elif check.severity == "deny":
51
+ status = "[red]DENY[/red]"
52
+ elif check.severity == "warn":
53
+ status = "[yellow]WARN[/yellow]"
54
+ else:
55
+ status = "[blue]INFO[/blue]"
56
+
57
+ sev_colors = {"deny": "red", "warn": "yellow", "info": "blue"}
58
+ color = sev_colors.get(check.severity, "")
59
+ severity_display = f"[{color}]{check.severity.upper()}[/{color}]" if color else check.severity.upper()
60
+
61
+ table.add_row(status, check.rule, severity_display, check.message)
62
+
63
+ console.print(table)
64
+ console.print()
65
+
66
+ if result.passed:
67
+ console.print("[green]All policies passed.[/green]")
68
+ else:
69
+ parts = []
70
+ if result.deny_count:
71
+ parts.append(f"[red]{result.deny_count} denied[/red]")
72
+ if result.warn_count:
73
+ parts.append(f"[yellow]{result.warn_count} warnings[/yellow]")
74
+ console.print(f"Policy evaluation: {', '.join(parts)}")
75
+ if result.deny_count > 0:
76
+ raise typer.Exit(1)
77
+
78
+ except typer.Exit:
79
+ raise
80
+ except FileNotFoundError as e:
81
+ console.print(f"[red]Error:[/red] File not found: {e}")
82
+ raise typer.Exit(1)
83
+ except Exception as e:
84
+ verbose = ctx.obj.get("verbose", False) if ctx.obj else False
85
+ console.print(f"[red]Error:[/red] {e}")
86
+ if verbose:
87
+ console.print_exception()
88
+ raise typer.Exit(1)
@@ -0,0 +1,104 @@
1
+ """Refresh live pricing data from cloud provider APIs into the catalog."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Annotated
7
+
8
+ import typer
9
+ from rich.console import Console
10
+ from rich.table import Table
11
+
12
+ from cloudwright_cli.utils import handle_error
13
+
14
+ console = Console()
15
+
16
+
17
+ def refresh(
18
+ ctx: typer.Context,
19
+ provider: Annotated[
20
+ str | None,
21
+ typer.Option("--provider", "-p", help="Provider to refresh: aws, gcp, azure (default: all)"),
22
+ ] = None,
23
+ category: Annotated[
24
+ str | None,
25
+ typer.Option("--category", "-c", help="Filter to a category: compute or a service name"),
26
+ ] = None,
27
+ region: Annotated[
28
+ str | None,
29
+ typer.Option("--region", "-r", help="Override the default region for pricing lookups"),
30
+ ] = None,
31
+ dry_run: Annotated[
32
+ bool,
33
+ typer.Option("--dry-run", help="Fetch but don't write to catalog DB"),
34
+ ] = False,
35
+ ) -> None:
36
+ """Refresh live pricing data from cloud provider APIs into the local catalog."""
37
+ try:
38
+ from cloudwright.catalog.refresh import refresh_catalog
39
+
40
+ providers_label = provider or "aws, gcp, azure"
41
+ with console.status(f"Fetching pricing for {providers_label}..."):
42
+ summary = refresh_catalog(
43
+ provider=provider,
44
+ category=category,
45
+ region=region,
46
+ dry_run=dry_run,
47
+ )
48
+
49
+ json_mode = ctx.obj and ctx.obj.get("json")
50
+
51
+ if json_mode:
52
+ data = {
53
+ "total_fetched": summary.total_fetched,
54
+ "total_errors": summary.total_errors,
55
+ "results": [
56
+ {
57
+ "provider": r.provider,
58
+ "category": r.category,
59
+ "instances_fetched": r.instances_fetched,
60
+ "managed_services_fetched": r.managed_services_fetched,
61
+ "errors": r.errors,
62
+ "dry_run": r.dry_run,
63
+ }
64
+ for r in summary.results
65
+ ],
66
+ }
67
+ print(json.dumps(data, indent=2))
68
+ return
69
+
70
+ table = Table(show_header=True, header_style="bold")
71
+ table.add_column("Provider")
72
+ table.add_column("Category")
73
+ table.add_column("Instances", justify="right")
74
+ table.add_column("Managed", justify="right")
75
+ table.add_column("Errors", justify="right")
76
+
77
+ for r in summary.results:
78
+ error_str = str(len(r.errors)) if r.errors else "[green]0[/green]"
79
+ table.add_row(
80
+ r.provider,
81
+ r.category or "all",
82
+ str(r.instances_fetched),
83
+ str(r.managed_services_fetched),
84
+ error_str,
85
+ )
86
+
87
+ console.print(table)
88
+
89
+ suffix = " [dim](dry run)[/dim]" if dry_run else ""
90
+ if summary.total_errors == 0:
91
+ console.print(f"[green]Done.[/green] {summary.total_fetched} pricing records fetched{suffix}")
92
+ else:
93
+ console.print(
94
+ f"[yellow]Done with errors.[/yellow] "
95
+ f"{summary.total_fetched} fetched, {summary.total_errors} error(s){suffix}"
96
+ )
97
+ for r in summary.results:
98
+ for err in r.errors:
99
+ console.print(f" [red]{r.provider}:[/red] {err}")
100
+
101
+ except typer.Exit:
102
+ raise
103
+ except Exception as e:
104
+ handle_error(ctx, e)
@@ -0,0 +1,80 @@
1
+ """Score an architecture's quality."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Annotated
7
+
8
+ import typer
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+ from rich.table import Table
12
+
13
+ from cloudwright_cli.utils import handle_error
14
+
15
+ console = Console()
16
+
17
+
18
+ def score(
19
+ ctx: typer.Context,
20
+ spec_file: Annotated[str, typer.Argument(help="Path to ArchSpec YAML/JSON file")],
21
+ with_cost: Annotated[bool, typer.Option("--with-cost", help="Run cost analysis before scoring")] = False,
22
+ ) -> None:
23
+ """Score an architecture's quality on 5 dimensions (0-100)."""
24
+ try:
25
+ from cloudwright import ArchSpec
26
+ from cloudwright.scorer import Scorer
27
+
28
+ spec = ArchSpec.from_file(spec_file)
29
+
30
+ if with_cost:
31
+ from cloudwright.cost import CostEngine
32
+
33
+ engine = CostEngine()
34
+ spec = engine.price(spec)
35
+
36
+ scorer = Scorer()
37
+ result = scorer.score(spec)
38
+
39
+ if ctx.obj and ctx.obj.get("json"):
40
+ print(json.dumps(result.to_dict(), indent=2))
41
+ return
42
+
43
+ border = "green" if result.overall >= 70 else "yellow" if result.overall >= 50 else "red"
44
+ console.print(
45
+ Panel(
46
+ f"[bold]Overall Score: {result.overall:.0f}/100[/bold] Grade: [bold]{result.grade}[/bold]",
47
+ title=f"Architecture Quality: {spec.name}",
48
+ border_style=border,
49
+ )
50
+ )
51
+
52
+ table = Table(title="Dimension Breakdown")
53
+ table.add_column("Dimension", style="cyan")
54
+ table.add_column("Score", justify="right")
55
+ table.add_column("Weight", justify="right")
56
+ table.add_column("Weighted", justify="right")
57
+ table.add_column("Details")
58
+
59
+ for d in result.dimensions:
60
+ weighted = d.score * d.weight
61
+ color = "green" if d.score >= 70 else "yellow" if d.score >= 50 else "red"
62
+ table.add_row(
63
+ d.name,
64
+ f"[{color}]{d.score:.0f}[/{color}]",
65
+ f"{d.weight:.0%}",
66
+ f"{weighted:.1f}",
67
+ "; ".join(d.details[:2]) if d.details else "",
68
+ )
69
+
70
+ console.print(table)
71
+
72
+ if result.recommendations:
73
+ console.print("\n[bold]Top Recommendations:[/bold]")
74
+ for i, rec in enumerate(result.recommendations, 1):
75
+ console.print(f" {i}. {rec}")
76
+
77
+ except typer.Exit:
78
+ raise
79
+ except Exception as e:
80
+ handle_error(ctx, e)
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Annotated
5
+
6
+ import typer
7
+ from cloudwright import ArchSpec, Validator
8
+ from rich.console import Console
9
+ from rich.rule import Rule
10
+ from rich.text import Text
11
+
12
+ console = Console()
13
+
14
+
15
+ def validate(
16
+ ctx: typer.Context,
17
+ spec_file: Annotated[Path, typer.Argument(help="Path to spec YAML file", exists=True)],
18
+ compliance: Annotated[
19
+ str | None, typer.Option(help="Comma-separated compliance frameworks (hipaa, pci-dss, soc2)")
20
+ ] = None,
21
+ well_architected: Annotated[bool, typer.Option("--well-architected", help="Run well-architected review")] = False,
22
+ report: Annotated[
23
+ Path | None, typer.Option("--report", help="Write a markdown compliance report to this path")
24
+ ] = None,
25
+ pdf_report: Annotated[Path | None, typer.Option("--pdf", help="Write a PDF compliance report to this path")] = None,
26
+ ) -> None:
27
+ """Validate an architecture spec against compliance frameworks or well-architected principles."""
28
+ if not compliance and not well_architected:
29
+ console.print("[yellow]Specify --compliance and/or --well-architected.[/yellow]")
30
+ raise typer.Exit(1)
31
+
32
+ spec = ArchSpec.from_file(spec_file)
33
+ frameworks: list[str] = []
34
+
35
+ if compliance:
36
+ frameworks.extend(f.strip() for f in compliance.split(",") if f.strip())
37
+ if well_architected:
38
+ frameworks.append("well-architected")
39
+
40
+ wa = "well-architected" in frameworks
41
+ compliance_only = [f for f in frameworks if f != "well-architected"]
42
+
43
+ with console.status("Running validation..."):
44
+ results = Validator().validate(spec, compliance_only, well_architected=wa)
45
+
46
+ if report and results:
47
+ from cloudwright.exporter.compliance_report import render as render_report
48
+
49
+ # Generate one report per framework; if multiple, use the first or merge
50
+ report_text = "\n\n---\n\n".join(render_report(spec, r) for r in results)
51
+ report.write_text(report_text)
52
+ console.print(f"[green]Compliance report written to {report}[/green]")
53
+
54
+ if pdf_report and results:
55
+ from cloudwright.exporter.compliance_report import render_pdf
56
+
57
+ for r in results:
58
+ if len(results) > 1:
59
+ out_path = pdf_report.parent / f"{pdf_report.stem}_{r.framework}{pdf_report.suffix}"
60
+ else:
61
+ out_path = pdf_report
62
+ render_pdf(spec, r, str(out_path))
63
+ console.print(f"[green]PDF compliance report written to {out_path}[/green]")
64
+
65
+ if ctx.obj and ctx.obj.get("json"):
66
+ import json
67
+
68
+ print(json.dumps({"results": [r.model_dump() for r in results]}, default=str))
69
+ return
70
+
71
+ any_failed = False
72
+ for result in results:
73
+ title = _framework_title(result.framework)
74
+ console.print(Rule(f"[bold]{title}[/bold]"))
75
+
76
+ passed_count = sum(1 for c in result.checks if c.passed)
77
+ total = len(result.checks)
78
+
79
+ for check in result.checks:
80
+ status = Text("[PASS]", style="green") if check.passed else Text("[FAIL]", style="red")
81
+ line = Text()
82
+ line.append_text(status)
83
+ line.append(f" {check.name}")
84
+ if check.detail:
85
+ line.append(f" — {check.detail}", style="dim")
86
+ console.print(line)
87
+
88
+ if not check.passed and check.recommendation:
89
+ console.print(f" [dim]Recommendation: {check.recommendation}[/dim]")
90
+ any_failed = True
91
+
92
+ pct = int(result.score * 100) if result.score <= 1 else int(result.score)
93
+ console.print(f"Score: {passed_count}/{total} ({pct}%)\n")
94
+
95
+ if any_failed:
96
+ raise typer.Exit(1)
97
+
98
+
99
+ def _framework_title(framework: str) -> str:
100
+ titles = {
101
+ "hipaa": "HIPAA Compliance Review",
102
+ "pci-dss": "PCI-DSS Compliance Review",
103
+ "soc2": "SOC 2 Compliance Review",
104
+ "well-architected": "Well-Architected Review",
105
+ }
106
+ return titles.get(framework.lower(), f"{framework.upper()} Review")
@@ -0,0 +1,66 @@
1
+ import typer
2
+
3
+ from cloudwright_cli import __version__
4
+ from cloudwright_cli.commands.analyze_cmd import analyze
5
+ from cloudwright_cli.commands.catalog_cmd import catalog_app
6
+ from cloudwright_cli.commands.chat import chat
7
+ from cloudwright_cli.commands.compare import compare
8
+ from cloudwright_cli.commands.cost import cost
9
+ from cloudwright_cli.commands.design import design
10
+ from cloudwright_cli.commands.diff import diff
11
+ from cloudwright_cli.commands.drift_cmd import drift
12
+ from cloudwright_cli.commands.export import export
13
+ from cloudwright_cli.commands.import_cmd import import_infra
14
+ from cloudwright_cli.commands.init_cmd import init
15
+ from cloudwright_cli.commands.lint_cmd import lint
16
+ from cloudwright_cli.commands.modify_cmd import modify
17
+ from cloudwright_cli.commands.policy import policy
18
+ from cloudwright_cli.commands.refresh_cmd import refresh
19
+ from cloudwright_cli.commands.score_cmd import score
20
+ from cloudwright_cli.commands.validate import validate
21
+
22
+
23
+ def _version_callback(value: bool) -> None:
24
+ if value:
25
+ print(f"cloudwright {__version__}")
26
+ raise typer.Exit()
27
+
28
+
29
+ app = typer.Typer(
30
+ name="cloudwright",
31
+ help="Architecture intelligence for cloud engineers",
32
+ no_args_is_help=True,
33
+ )
34
+
35
+
36
+ @app.callback()
37
+ def main(
38
+ ctx: typer.Context,
39
+ version: bool = typer.Option(
40
+ False, "--version", "-V", help="Show version", callback=_version_callback, is_eager=True
41
+ ),
42
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
43
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
44
+ ) -> None:
45
+ ctx.ensure_object(dict)
46
+ ctx.obj["verbose"] = verbose
47
+ ctx.obj["json"] = json_output
48
+
49
+
50
+ app.command()(design)
51
+ app.command()(cost)
52
+ app.command()(compare)
53
+ app.command()(validate)
54
+ app.command()(export)
55
+ app.command()(diff)
56
+ app.command()(drift)
57
+ app.command()(modify)
58
+ app.command(name="import")(import_infra)
59
+ app.command()(chat)
60
+ app.command()(init)
61
+ app.command()(policy)
62
+ app.command()(score)
63
+ app.command()(analyze)
64
+ app.command()(refresh)
65
+ app.command()(lint)
66
+ app.add_typer(catalog_app, name="catalog")