cloudwright-ai-cli 1.2.2__tar.gz → 1.4.0__tar.gz
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.
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/PKG-INFO +1 -1
- cloudwright_ai_cli-1.4.0/cloudwright_cli/__init__.py +1 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/chat.py +26 -12
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/chat_ui.py +1 -1
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/export.py +32 -6
- cloudwright_ai_cli-1.4.0/cloudwright_cli/commands/import_live_cmd.py +136 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/completions.py +2 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/main.py +2 -0
- cloudwright_ai_cli-1.4.0/tests/test_chat_debug.py +74 -0
- cloudwright_ai_cli-1.2.2/cloudwright_cli/__init__.py +0 -1
- cloudwright_ai_cli-1.2.2/tests/test_chat_debug.py +0 -55
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/.gitignore +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/README.md +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/__main__.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/__init__.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/adr.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/analyze_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/catalog_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/chat_session.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/chat_streaming.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/compare.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/cost.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/databricks_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/design.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/diff.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/drift_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/import_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/init_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/lint_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/mcp_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/modify_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/policy.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/refresh_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/schema_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/score_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/security_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/validate.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/decorators.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/output.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/project.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/py.typed +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/utils.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/pyproject.toml +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/tests/__init__.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/tests/test_chat_commands.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/tests/test_chat_persistence.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/tests/test_chat_streaming.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/tests/test_cli.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/tests/test_drift_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/tests/test_init.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/tests/test_modify_cmd.py +0 -0
- {cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/tests/test_project.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudwright-ai-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: CLI for Cloudwright architecture intelligence
|
|
5
5
|
Project-URL: Homepage, https://github.com/xmpuspus/cloudwright
|
|
6
6
|
Project-URL: Repository, https://github.com/xmpuspus/cloudwright
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.4.0"
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
import
|
|
4
|
+
import os
|
|
5
5
|
import time
|
|
6
6
|
from typing import Annotated
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
9
|
from cloudwright import ArchSpec, ConversationSession
|
|
10
10
|
from cloudwright.ascii_diagram import render_ascii
|
|
11
|
+
from cloudwright.logging import configure_logging
|
|
11
12
|
from cloudwright.session_store import SessionStore
|
|
12
13
|
from rich.console import Console
|
|
13
14
|
from rich.live import Live
|
|
@@ -23,21 +24,27 @@ from .chat_ui import _HELP, print_cost_summary, print_diff, run_validate
|
|
|
23
24
|
|
|
24
25
|
console = Console()
|
|
25
26
|
|
|
27
|
+
DEFAULT_WEB_PORT = 8765
|
|
28
|
+
|
|
26
29
|
|
|
27
30
|
def chat(
|
|
28
31
|
web: Annotated[bool, typer.Option("--web", help="Launch web UI instead of terminal chat")] = False,
|
|
29
32
|
resume: Annotated[str | None, typer.Option("--resume", help="Resume a saved session by ID")] = None,
|
|
30
33
|
debug: Annotated[bool, typer.Option("--debug", help="Log LLM requests/responses to stderr")] = False,
|
|
34
|
+
port: Annotated[
|
|
35
|
+
int,
|
|
36
|
+
typer.Option("--port", help=f"Port for --web (default: {DEFAULT_WEB_PORT})"),
|
|
37
|
+
] = DEFAULT_WEB_PORT,
|
|
31
38
|
) -> None:
|
|
32
39
|
"""Interactive architecture design chat."""
|
|
33
40
|
if web:
|
|
34
|
-
_launch_web()
|
|
41
|
+
_launch_web(port=port)
|
|
35
42
|
return
|
|
36
43
|
|
|
37
44
|
_run_terminal_chat(resume=resume, debug=debug)
|
|
38
45
|
|
|
39
46
|
|
|
40
|
-
def _launch_web() -> None:
|
|
47
|
+
def _launch_web(port: int = DEFAULT_WEB_PORT) -> None:
|
|
41
48
|
try:
|
|
42
49
|
import cloudwright_web # type: ignore
|
|
43
50
|
import uvicorn
|
|
@@ -49,18 +56,19 @@ def _launch_web() -> None:
|
|
|
49
56
|
|
|
50
57
|
import socket
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
port
|
|
57
|
-
|
|
59
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
60
|
+
if s.connect_ex(("127.0.0.1", port)) == 0:
|
|
61
|
+
console.print(
|
|
62
|
+
f"[red]Error:[/red] port {port} is already in use. "
|
|
63
|
+
f"Pass --port to choose another (e.g. --port {port + 1})."
|
|
64
|
+
)
|
|
65
|
+
raise typer.Exit(1)
|
|
58
66
|
|
|
59
67
|
import threading
|
|
60
68
|
import webbrowser
|
|
61
69
|
|
|
62
70
|
url = f"http://127.0.0.1:{port}"
|
|
63
|
-
console.print(f"[cyan]
|
|
71
|
+
console.print(f"\n[bold cyan]Cloudwright web UI:[/bold cyan] {url}\n")
|
|
64
72
|
|
|
65
73
|
def _open_browser():
|
|
66
74
|
time.sleep(1.5)
|
|
@@ -71,8 +79,14 @@ def _launch_web() -> None:
|
|
|
71
79
|
|
|
72
80
|
|
|
73
81
|
def _run_terminal_chat(resume: str | None = None, debug: bool = False) -> None:
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
configure_logging()
|
|
83
|
+
env_level = os.environ.get("CLOUDWRIGHT_LOG_LEVEL", "").upper()
|
|
84
|
+
if debug or env_level == "DEBUG":
|
|
85
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
86
|
+
logging.getLogger("cloudwright").setLevel(logging.DEBUG)
|
|
87
|
+
elif env_level in {"INFO", "WARNING", "ERROR", "CRITICAL"}:
|
|
88
|
+
logging.getLogger().setLevel(getattr(logging, env_level))
|
|
89
|
+
logging.getLogger("cloudwright").setLevel(getattr(logging, env_level))
|
|
76
90
|
|
|
77
91
|
console.print(
|
|
78
92
|
Panel(
|
|
@@ -16,7 +16,7 @@ Commands:
|
|
|
16
16
|
/yaml Show YAML for last architecture
|
|
17
17
|
/cost Show cost estimate for last architecture
|
|
18
18
|
/validate [fw] Run compliance check (hipaa, pci-dss, soc2, fedramp, gdpr)
|
|
19
|
-
/export <fmt> Export last architecture (terraform, mermaid, d2, cloudformation, sbom, aibom)
|
|
19
|
+
/export <fmt> Export last architecture (terraform, pulumi-ts, pulumi-python, mermaid, d2, cloudformation, sbom, aibom)
|
|
20
20
|
/terraform Export last architecture as Terraform
|
|
21
21
|
/new Start a new architecture from scratch
|
|
22
22
|
/help, /? Show this help
|
|
@@ -15,6 +15,10 @@ console = Console()
|
|
|
15
15
|
|
|
16
16
|
_SYNTAX_MAP = {
|
|
17
17
|
"terraform": "hcl",
|
|
18
|
+
"pulumi-ts": "typescript",
|
|
19
|
+
"pulumi-typescript": "typescript",
|
|
20
|
+
"pulumi-python": "python",
|
|
21
|
+
"pulumi-py": "python",
|
|
18
22
|
"cloudformation": "yaml",
|
|
19
23
|
"mermaid": "text",
|
|
20
24
|
"d2": "text",
|
|
@@ -25,6 +29,26 @@ _SYNTAX_MAP = {
|
|
|
25
29
|
"aibom": "json",
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
# Formats that produce a multi-file project layout when --output points at a
|
|
33
|
+
# directory (or any extensionless path). Keep in sync with the dispatch in
|
|
34
|
+
# cloudwright.exporter.export_spec.
|
|
35
|
+
_DIRECTORY_FORMATS = {
|
|
36
|
+
"terraform",
|
|
37
|
+
"pulumi-ts",
|
|
38
|
+
"pulumi-typescript",
|
|
39
|
+
"pulumi-python",
|
|
40
|
+
"pulumi-py",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Primary entry filename per directory-format, used only for status messages.
|
|
44
|
+
_DIRECTORY_ENTRY = {
|
|
45
|
+
"terraform": "main.tf",
|
|
46
|
+
"pulumi-ts": "index.ts",
|
|
47
|
+
"pulumi-typescript": "index.ts",
|
|
48
|
+
"pulumi-python": "__main__.py",
|
|
49
|
+
"pulumi-py": "__main__.py",
|
|
50
|
+
}
|
|
51
|
+
|
|
28
52
|
|
|
29
53
|
def export(
|
|
30
54
|
ctx: typer.Context,
|
|
@@ -39,9 +63,10 @@ def export(
|
|
|
39
63
|
],
|
|
40
64
|
output: Annotated[Path | None, typer.Option("--output", "-o", help="Output file or directory")] = None,
|
|
41
65
|
) -> None:
|
|
42
|
-
"""Export an architecture spec to Terraform, CloudFormation, Mermaid, SVG, PNG, SBOM, or AIBOM."""
|
|
66
|
+
"""Export an architecture spec to Terraform, Pulumi (TS/Python), CloudFormation, Mermaid, SVG, PNG, SBOM, or AIBOM."""
|
|
43
67
|
fmt = format.lower().strip()
|
|
44
|
-
|
|
68
|
+
_aliases = {"cfn", "pulumi-typescript", "pulumi-py"}
|
|
69
|
+
if fmt not in FORMATS and fmt not in _aliases:
|
|
45
70
|
emit_error(ctx, ValueError(f"Unknown format {fmt!r}"), action=f"Use one of: {', '.join(FORMATS)}")
|
|
46
71
|
|
|
47
72
|
if output:
|
|
@@ -55,11 +80,11 @@ def export(
|
|
|
55
80
|
output_str = str(output) if output else None
|
|
56
81
|
output_dir_str = None
|
|
57
82
|
|
|
58
|
-
# Terraform with a directory target writes
|
|
59
|
-
if fmt
|
|
83
|
+
# Terraform / Pulumi with a directory target writes a project layout in the dir
|
|
84
|
+
if fmt in _DIRECTORY_FORMATS and output and output.is_dir():
|
|
60
85
|
output_dir_str = output_str
|
|
61
86
|
output_str = None
|
|
62
|
-
elif fmt
|
|
87
|
+
elif fmt in _DIRECTORY_FORMATS and output and not output.suffix:
|
|
63
88
|
# Treat extensionless output as a directory path
|
|
64
89
|
output_dir_str = output_str
|
|
65
90
|
output_str = None
|
|
@@ -104,7 +129,8 @@ def export(
|
|
|
104
129
|
|
|
105
130
|
if output:
|
|
106
131
|
if output_dir_str:
|
|
107
|
-
|
|
132
|
+
entry = _DIRECTORY_ENTRY.get(fmt, "main.tf")
|
|
133
|
+
console.print(f"[green]Written to {output_dir_str}/{entry}[/green]")
|
|
108
134
|
else:
|
|
109
135
|
console.print(f"[green]Written to {output}[/green]")
|
|
110
136
|
else:
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Import live cloud infrastructure into an ArchSpec via provider APIs.
|
|
2
|
+
|
|
3
|
+
Currently supports AWS via boto3 (``cloudwright import-live --provider aws``).
|
|
4
|
+
GCP and Azure surface a clear ``not yet implemented`` error.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Annotated
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
|
|
17
|
+
from cloudwright_cli.output import emit_success, err_console, is_json_mode, validate_output_path
|
|
18
|
+
from cloudwright_cli.utils import handle_error
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def import_live(
|
|
24
|
+
ctx: typer.Context,
|
|
25
|
+
provider: Annotated[
|
|
26
|
+
str,
|
|
27
|
+
typer.Option("--provider", help="Cloud provider to scan: aws (gcp, azure not yet implemented)"),
|
|
28
|
+
] = "aws",
|
|
29
|
+
region: Annotated[
|
|
30
|
+
str,
|
|
31
|
+
typer.Option("--region", help="Cloud region (e.g. us-east-1)"),
|
|
32
|
+
] = "us-east-1",
|
|
33
|
+
profile: Annotated[
|
|
34
|
+
str | None,
|
|
35
|
+
typer.Option("--profile", help="AWS named profile (~/.aws/credentials)"),
|
|
36
|
+
] = None,
|
|
37
|
+
services: Annotated[
|
|
38
|
+
str | None,
|
|
39
|
+
typer.Option(
|
|
40
|
+
"--services",
|
|
41
|
+
help="Comma-separated subset of services to scan (e.g. ec2,rds,s3). Default: all.",
|
|
42
|
+
),
|
|
43
|
+
] = None,
|
|
44
|
+
output: Annotated[
|
|
45
|
+
str | None,
|
|
46
|
+
typer.Option("--output", "-o", help="Write ArchSpec YAML to this file instead of stdout"),
|
|
47
|
+
] = None,
|
|
48
|
+
name: Annotated[
|
|
49
|
+
str | None,
|
|
50
|
+
typer.Option("--name", help="Override the architecture name"),
|
|
51
|
+
] = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Walk live AWS APIs and produce an ArchSpec from running infrastructure."""
|
|
54
|
+
try:
|
|
55
|
+
provider_norm = (provider or "aws").lower()
|
|
56
|
+
if provider_norm in {"gcp", "google", "azure"}:
|
|
57
|
+
raise typer.BadParameter(
|
|
58
|
+
f"--provider {provider!r} is not yet implemented. Only --provider aws is supported in v1.4."
|
|
59
|
+
)
|
|
60
|
+
if provider_norm != "aws":
|
|
61
|
+
raise typer.BadParameter(f"Unknown --provider {provider!r}. Supported: aws.")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
from cloudwright.importer.live_aws import (
|
|
65
|
+
SUPPORTED_SERVICES,
|
|
66
|
+
LiveImportError,
|
|
67
|
+
import_live_aws,
|
|
68
|
+
)
|
|
69
|
+
except ImportError as exc:
|
|
70
|
+
# boto3 not installed at all (cloudwright.importer.live_aws import boto3 lazily,
|
|
71
|
+
# but a missing core import surfaces here as a final fallback).
|
|
72
|
+
err_console.print(
|
|
73
|
+
"[red]boto3 is required for live AWS import.[/red] "
|
|
74
|
+
"Install with: pip install 'cloudwright-ai[live-import]'"
|
|
75
|
+
)
|
|
76
|
+
raise typer.Exit(code=1) from exc
|
|
77
|
+
|
|
78
|
+
services_list: list[str] | None = None
|
|
79
|
+
if services:
|
|
80
|
+
services_list = [s.strip().lower() for s in services.split(",") if s.strip()]
|
|
81
|
+
unknown = [s for s in services_list if s not in SUPPORTED_SERVICES]
|
|
82
|
+
if unknown:
|
|
83
|
+
raise typer.BadParameter(
|
|
84
|
+
f"Unknown service(s): {sorted(set(unknown))}. Supported: {list(SUPPORTED_SERVICES)}"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
json_mode = is_json_mode(ctx)
|
|
88
|
+
|
|
89
|
+
def _progress(msg: str) -> None:
|
|
90
|
+
if not json_mode:
|
|
91
|
+
err_console.print(msg)
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
spec = import_live_aws(
|
|
95
|
+
region=region,
|
|
96
|
+
profile=profile,
|
|
97
|
+
services=services_list,
|
|
98
|
+
progress=_progress,
|
|
99
|
+
name=name,
|
|
100
|
+
)
|
|
101
|
+
except LiveImportError as exc:
|
|
102
|
+
# Clean error path — credentials missing, profile not found, etc.
|
|
103
|
+
err_console.print(f"[red]error:[/red] {exc}")
|
|
104
|
+
raise typer.Exit(code=1) from exc
|
|
105
|
+
|
|
106
|
+
if name:
|
|
107
|
+
spec = spec.model_copy(update={"name": name})
|
|
108
|
+
|
|
109
|
+
if json_mode:
|
|
110
|
+
emit_success(ctx, {"spec": json.loads(spec.to_json())})
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
content = spec.to_yaml()
|
|
114
|
+
n_comps = len(spec.components)
|
|
115
|
+
n_bounds = len(spec.boundaries)
|
|
116
|
+
|
|
117
|
+
if output:
|
|
118
|
+
validate_output_path(output)
|
|
119
|
+
Path(output).write_text(content)
|
|
120
|
+
err_console.print()
|
|
121
|
+
err_console.print(
|
|
122
|
+
f"[green]Imported[/green] {n_comps} component(s), {n_bounds} boundary(ies) "
|
|
123
|
+
f"from {region} -> [bold]{output}[/bold]"
|
|
124
|
+
)
|
|
125
|
+
err_console.print(f"Run [bold]cloudwright cost {output}[/bold] to estimate.")
|
|
126
|
+
else:
|
|
127
|
+
sys.stdout.write(content)
|
|
128
|
+
err_console.print()
|
|
129
|
+
err_console.print(f"[green]Imported[/green] {n_comps} component(s), {n_bounds} boundary(ies) from {region}")
|
|
130
|
+
|
|
131
|
+
except typer.Exit:
|
|
132
|
+
raise
|
|
133
|
+
except typer.BadParameter:
|
|
134
|
+
raise
|
|
135
|
+
except Exception as e:
|
|
136
|
+
handle_error(ctx, e)
|
|
@@ -47,6 +47,8 @@ def complete_workload_profile(incomplete: str) -> list[tuple[str, str]]:
|
|
|
47
47
|
def complete_export_format(incomplete: str) -> list[tuple[str, str]]:
|
|
48
48
|
formats = [
|
|
49
49
|
("terraform", "HashiCorp Terraform HCL"),
|
|
50
|
+
("pulumi-ts", "Pulumi TypeScript (@pulumi/aws, @pulumi/gcp, @pulumi/azure-native)"),
|
|
51
|
+
("pulumi-python", "Pulumi Python (pulumi_aws, pulumi_gcp, pulumi_azure_native)"),
|
|
50
52
|
("cloudformation", "AWS CloudFormation YAML"),
|
|
51
53
|
("mermaid", "Mermaid diagram syntax"),
|
|
52
54
|
("d2", "D2 diagram language"),
|
|
@@ -13,6 +13,7 @@ from cloudwright_cli.commands.diff import diff
|
|
|
13
13
|
from cloudwright_cli.commands.drift_cmd import drift
|
|
14
14
|
from cloudwright_cli.commands.export import export
|
|
15
15
|
from cloudwright_cli.commands.import_cmd import import_infra
|
|
16
|
+
from cloudwright_cli.commands.import_live_cmd import import_live
|
|
16
17
|
from cloudwright_cli.commands.init_cmd import init
|
|
17
18
|
from cloudwright_cli.commands.lint_cmd import lint
|
|
18
19
|
from cloudwright_cli.commands.mcp_cmd import mcp_serve
|
|
@@ -68,6 +69,7 @@ app.command()(diff)
|
|
|
68
69
|
app.command()(drift)
|
|
69
70
|
app.command()(modify)
|
|
70
71
|
app.command(name="import")(import_infra)
|
|
72
|
+
app.command(name="import-live")(import_live)
|
|
71
73
|
app.command()(chat)
|
|
72
74
|
app.command()(init)
|
|
73
75
|
app.command()(policy)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestDebugMode:
|
|
8
|
+
def test_debug_enables_logging(self):
|
|
9
|
+
"""--debug flag should set the cloudwright logger to DEBUG level.
|
|
10
|
+
|
|
11
|
+
Audit fix v1.3: previously called logging.basicConfig() which is a
|
|
12
|
+
no-op against the structlog-configured root logger. Now it sets
|
|
13
|
+
DEBUG on the root + cloudwright logger directly.
|
|
14
|
+
"""
|
|
15
|
+
cloudwright_logger = logging.getLogger("cloudwright")
|
|
16
|
+
prior_level = cloudwright_logger.level
|
|
17
|
+
try:
|
|
18
|
+
with (
|
|
19
|
+
patch("cloudwright_cli.commands.chat.ConversationSession"),
|
|
20
|
+
patch("cloudwright_cli.commands.chat.SessionStore"),
|
|
21
|
+
patch("cloudwright_cli.commands.chat.Prompt.ask", side_effect=[KeyboardInterrupt]),
|
|
22
|
+
):
|
|
23
|
+
from cloudwright_cli.commands.chat import _run_terminal_chat
|
|
24
|
+
|
|
25
|
+
_run_terminal_chat(debug=True)
|
|
26
|
+
|
|
27
|
+
assert cloudwright_logger.level == logging.DEBUG
|
|
28
|
+
finally:
|
|
29
|
+
cloudwright_logger.setLevel(prior_level)
|
|
30
|
+
|
|
31
|
+
def test_debug_flag_via_chat_entrypoint(self):
|
|
32
|
+
with (
|
|
33
|
+
patch("cloudwright_cli.commands.chat._run_terminal_chat") as mock_run,
|
|
34
|
+
patch("cloudwright_cli.commands.chat._launch_web"),
|
|
35
|
+
):
|
|
36
|
+
from cloudwright_cli.commands.chat import chat
|
|
37
|
+
|
|
38
|
+
chat(web=False, resume=None, debug=True)
|
|
39
|
+
|
|
40
|
+
# The chat() entrypoint may pass extra args (port). Just assert debug=True.
|
|
41
|
+
assert mock_run.call_count == 1
|
|
42
|
+
kwargs = mock_run.call_args.kwargs
|
|
43
|
+
assert kwargs.get("debug") is True
|
|
44
|
+
|
|
45
|
+
def test_no_debug_flag_via_chat_entrypoint(self):
|
|
46
|
+
with (
|
|
47
|
+
patch("cloudwright_cli.commands.chat._run_terminal_chat") as mock_run,
|
|
48
|
+
patch("cloudwright_cli.commands.chat._launch_web"),
|
|
49
|
+
):
|
|
50
|
+
from cloudwright_cli.commands.chat import chat
|
|
51
|
+
|
|
52
|
+
chat(web=False, resume=None, debug=False)
|
|
53
|
+
|
|
54
|
+
assert mock_run.call_count == 1
|
|
55
|
+
assert mock_run.call_args.kwargs.get("debug") is False
|
|
56
|
+
|
|
57
|
+
def test_log_level_env_var_debug(self, monkeypatch):
|
|
58
|
+
"""CLOUDWRIGHT_LOG_LEVEL=DEBUG should also set DEBUG even without --debug."""
|
|
59
|
+
monkeypatch.setenv("CLOUDWRIGHT_LOG_LEVEL", "DEBUG")
|
|
60
|
+
cloudwright_logger = logging.getLogger("cloudwright")
|
|
61
|
+
prior_level = cloudwright_logger.level
|
|
62
|
+
try:
|
|
63
|
+
with (
|
|
64
|
+
patch("cloudwright_cli.commands.chat.ConversationSession"),
|
|
65
|
+
patch("cloudwright_cli.commands.chat.SessionStore"),
|
|
66
|
+
patch("cloudwright_cli.commands.chat.Prompt.ask", side_effect=[KeyboardInterrupt]),
|
|
67
|
+
):
|
|
68
|
+
from cloudwright_cli.commands.chat import _run_terminal_chat
|
|
69
|
+
|
|
70
|
+
_run_terminal_chat(debug=False)
|
|
71
|
+
|
|
72
|
+
assert cloudwright_logger.level == logging.DEBUG
|
|
73
|
+
finally:
|
|
74
|
+
cloudwright_logger.setLevel(prior_level)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.2.2"
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import sys
|
|
5
|
-
from unittest.mock import patch
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TestDebugMode:
|
|
9
|
-
def test_debug_enables_logging(self):
|
|
10
|
-
with (
|
|
11
|
-
patch("cloudwright_cli.commands.chat.ConversationSession"),
|
|
12
|
-
patch("cloudwright_cli.commands.chat.SessionStore"),
|
|
13
|
-
patch("cloudwright_cli.commands.chat.Prompt.ask", side_effect=[KeyboardInterrupt]),
|
|
14
|
-
patch("logging.basicConfig") as mock_basic,
|
|
15
|
-
):
|
|
16
|
-
from cloudwright_cli.commands.chat import _run_terminal_chat
|
|
17
|
-
|
|
18
|
-
_run_terminal_chat(debug=True)
|
|
19
|
-
|
|
20
|
-
mock_basic.assert_called_once_with(stream=sys.stderr, level=logging.DEBUG)
|
|
21
|
-
|
|
22
|
-
def test_no_debug_by_default(self):
|
|
23
|
-
with (
|
|
24
|
-
patch("cloudwright_cli.commands.chat.ConversationSession"),
|
|
25
|
-
patch("cloudwright_cli.commands.chat.SessionStore"),
|
|
26
|
-
patch("cloudwright_cli.commands.chat.Prompt.ask", side_effect=[KeyboardInterrupt]),
|
|
27
|
-
patch("logging.basicConfig") as mock_basic,
|
|
28
|
-
):
|
|
29
|
-
from cloudwright_cli.commands.chat import _run_terminal_chat
|
|
30
|
-
|
|
31
|
-
_run_terminal_chat(debug=False)
|
|
32
|
-
|
|
33
|
-
mock_basic.assert_not_called()
|
|
34
|
-
|
|
35
|
-
def test_debug_flag_via_chat_entrypoint(self):
|
|
36
|
-
with (
|
|
37
|
-
patch("cloudwright_cli.commands.chat._run_terminal_chat") as mock_run,
|
|
38
|
-
patch("cloudwright_cli.commands.chat._launch_web"),
|
|
39
|
-
):
|
|
40
|
-
from cloudwright_cli.commands.chat import chat
|
|
41
|
-
|
|
42
|
-
chat(web=False, resume=None, debug=True)
|
|
43
|
-
|
|
44
|
-
mock_run.assert_called_once_with(resume=None, debug=True)
|
|
45
|
-
|
|
46
|
-
def test_no_debug_flag_via_chat_entrypoint(self):
|
|
47
|
-
with (
|
|
48
|
-
patch("cloudwright_cli.commands.chat._run_terminal_chat") as mock_run,
|
|
49
|
-
patch("cloudwright_cli.commands.chat._launch_web"),
|
|
50
|
-
):
|
|
51
|
-
from cloudwright_cli.commands.chat import chat
|
|
52
|
-
|
|
53
|
-
chat(web=False, resume=None, debug=False)
|
|
54
|
-
|
|
55
|
-
mock_run.assert_called_once_with(resume=None, debug=False)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/analyze_cmd.py
RENAMED
|
File without changes
|
{cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/catalog_cmd.py
RENAMED
|
File without changes
|
{cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/chat_session.py
RENAMED
|
File without changes
|
{cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/chat_streaming.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/databricks_cmd.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/import_cmd.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/modify_cmd.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/refresh_cmd.py
RENAMED
|
File without changes
|
{cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/schema_cmd.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudwright_ai_cli-1.2.2 → cloudwright_ai_cli-1.4.0}/cloudwright_cli/commands/security_cmd.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|