cloudwright-ai-cli 0.3.1__tar.gz → 0.3.3__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.
Files changed (42) hide show
  1. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/PKG-INFO +1 -1
  2. cloudwright_ai_cli-0.3.3/cloudwright_cli/__init__.py +1 -0
  3. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/adr.py +10 -7
  4. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/cost.py +20 -1
  5. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/design.py +11 -8
  6. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/lint_cmd.py +9 -7
  7. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/mcp_cmd.py +1 -2
  8. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/modify_cmd.py +18 -12
  9. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/schema_cmd.py +5 -7
  10. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/security_cmd.py +9 -7
  11. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/validate.py +9 -7
  12. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/completions.py +20 -0
  13. cloudwright_ai_cli-0.3.1/cloudwright_cli/__init__.py +0 -1
  14. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/.gitignore +0 -0
  15. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/README.md +0 -0
  16. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/__main__.py +0 -0
  17. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/__init__.py +0 -0
  18. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/analyze_cmd.py +0 -0
  19. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/catalog_cmd.py +0 -0
  20. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/chat.py +0 -0
  21. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/compare.py +0 -0
  22. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/databricks_cmd.py +0 -0
  23. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/diff.py +0 -0
  24. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/drift_cmd.py +0 -0
  25. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/export.py +0 -0
  26. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/import_cmd.py +0 -0
  27. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/init_cmd.py +0 -0
  28. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/policy.py +0 -0
  29. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/refresh_cmd.py +0 -0
  30. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/commands/score_cmd.py +0 -0
  31. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/main.py +0 -0
  32. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/output.py +0 -0
  33. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/project.py +0 -0
  34. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/py.typed +0 -0
  35. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/cloudwright_cli/utils.py +0 -0
  36. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/pyproject.toml +0 -0
  37. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/tests/__init__.py +0 -0
  38. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/tests/test_cli.py +0 -0
  39. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/tests/test_drift_cmd.py +0 -0
  40. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/tests/test_init.py +0 -0
  41. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/tests/test_modify_cmd.py +0 -0
  42. {cloudwright_ai_cli-0.3.1 → cloudwright_ai_cli-0.3.3}/tests/test_project.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudwright-ai-cli
3
- Version: 0.3.1
3
+ Version: 0.3.3
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__ = "0.3.3"
@@ -71,13 +71,16 @@ def adr(
71
71
  from cloudwright.llm.anthropic import GENERATE_MODEL
72
72
 
73
73
  spec_json = spec.model_dump_json(indent=2, exclude_none=True)
74
- emit_dry_run(ctx, {
75
- "model": GENERATE_MODEL,
76
- "estimated_tokens": len(spec_json + _ADR_SYSTEM) // 4,
77
- "max_tokens": 2000,
78
- "system_prompt_preview": _ADR_SYSTEM,
79
- "user_prompt_preview": f"Generate ADR for: {spec.name}",
80
- })
74
+ emit_dry_run(
75
+ ctx,
76
+ {
77
+ "model": GENERATE_MODEL,
78
+ "estimated_tokens": len(spec_json + _ADR_SYSTEM) // 4,
79
+ "max_tokens": 2000,
80
+ "system_prompt_preview": _ADR_SYSTEM,
81
+ "user_prompt_preview": f"Generate ADR for: {spec.name}",
82
+ },
83
+ )
81
84
 
82
85
  text = _generate_adr(spec, title=title, decision=decision)
83
86
 
@@ -21,14 +21,33 @@ def cost(
21
21
  pricing_tier: Annotated[
22
22
  str | None, typer.Option(help="Pricing tier (on_demand, reserved_1yr, reserved_3yr, spot)")
23
23
  ] = None,
24
+ workload_profile: Annotated[
25
+ str | None,
26
+ typer.Option(
27
+ "--workload-profile",
28
+ "-w",
29
+ help="Workload sizing profile (small, medium, large, enterprise). "
30
+ "Sets realistic defaults for request volumes, storage, node counts, and data transfer.",
31
+ ),
32
+ ] = None,
24
33
  ) -> None:
25
34
  """Show cost breakdown for an architecture spec."""
35
+ if workload_profile:
36
+ from cloudwright.cost import VALID_WORKLOAD_PROFILES
37
+
38
+ if workload_profile not in VALID_WORKLOAD_PROFILES:
39
+ console.print(
40
+ f"[red]Invalid workload profile:[/red] {workload_profile!r}. "
41
+ f"Choose from: {', '.join(sorted(VALID_WORKLOAD_PROFILES))}"
42
+ )
43
+ raise typer.Exit(1)
44
+
26
45
  spec = ArchSpec.from_file(spec_file)
27
46
 
28
47
  # Compute cost estimate if not present
29
48
  if not spec.cost_estimate:
30
49
  engine = CostEngine()
31
- spec.cost_estimate = engine.estimate(spec)
50
+ spec.cost_estimate = engine.estimate(spec, workload_profile=workload_profile)
32
51
 
33
52
  if is_json_mode(ctx):
34
53
  emit_success(ctx, {"estimate": spec.cost_estimate.model_dump(exclude_none=True)})
@@ -45,14 +45,17 @@ def design(
45
45
  system = Architect._select_system_prompt(description)
46
46
  if constraints:
47
47
  system += _build_constraint_prompt(constraints)
48
- emit_dry_run(ctx, {
49
- "model": GENERATE_MODEL,
50
- "estimated_tokens": len(system + description) // 4,
51
- "max_tokens": 10000,
52
- "system_prompt_preview": system[:200],
53
- "user_prompt_preview": description,
54
- "constraints": constraints.model_dump(exclude_none=True),
55
- })
48
+ emit_dry_run(
49
+ ctx,
50
+ {
51
+ "model": GENERATE_MODEL,
52
+ "estimated_tokens": len(system + description) // 4,
53
+ "max_tokens": 10000,
54
+ "system_prompt_preview": system[:200],
55
+ "user_prompt_preview": description,
56
+ "constraints": constraints.model_dump(exclude_none=True),
57
+ },
58
+ )
56
59
 
57
60
  try:
58
61
  architect = Architect()
@@ -32,13 +32,15 @@ def lint(
32
32
  if is_json_mode(ctx):
33
33
  if should_stream(ctx):
34
34
  for w in warnings:
35
- emit_stream({
36
- "rule": w.rule,
37
- "severity": w.severity,
38
- "component": w.component,
39
- "message": w.message,
40
- "recommendation": w.recommendation,
41
- })
35
+ emit_stream(
36
+ {
37
+ "rule": w.rule,
38
+ "severity": w.severity,
39
+ "component": w.component,
40
+ "message": w.message,
41
+ "recommendation": w.recommendation,
42
+ }
43
+ )
42
44
  return
43
45
  result = [
44
46
  {
@@ -21,8 +21,7 @@ def mcp_serve(
21
21
  from rich.console import Console
22
22
 
23
23
  Console(stderr=True).print(
24
- "[red]Error:[/red] cloudwright-ai-mcp not installed.\n"
25
- " Install: pip install cloudwright-ai-mcp"
24
+ "[red]Error:[/red] cloudwright-ai-mcp not installed.\n Install: pip install cloudwright-ai-mcp"
26
25
  )
27
26
  raise typer.Exit(1) from None
28
27
 
@@ -50,13 +50,16 @@ def modify(
50
50
  from cloudwright.llm.anthropic import GENERATE_MODEL
51
51
 
52
52
  spec_text = original.to_yaml()
53
- emit_dry_run(ctx, {
54
- "model": GENERATE_MODEL,
55
- "estimated_tokens": len(spec_text + instruction) // 4,
56
- "max_tokens": 8000,
57
- "user_prompt_preview": f"Modify: {instruction}",
58
- "constraints": {"spec_file": spec_file, "instruction": instruction},
59
- })
53
+ emit_dry_run(
54
+ ctx,
55
+ {
56
+ "model": GENERATE_MODEL,
57
+ "estimated_tokens": len(spec_text + instruction) // 4,
58
+ "max_tokens": 8000,
59
+ "user_prompt_preview": f"Modify: {instruction}",
60
+ "constraints": {"spec_file": spec_file, "instruction": instruction},
61
+ },
62
+ )
60
63
 
61
64
  with console.status("Applying modification..."):
62
65
  modified = architect.modify(original, instruction)
@@ -73,11 +76,14 @@ def modify(
73
76
  diff_result = Differ().diff(original_costed, modified_costed)
74
77
 
75
78
  if is_json_mode(ctx):
76
- emit_success(ctx, {
77
- "original": original.model_dump(),
78
- "modified": modified.model_dump(),
79
- "diff": diff_result.model_dump(),
80
- })
79
+ emit_success(
80
+ ctx,
81
+ {
82
+ "original": original.model_dump(),
83
+ "modified": modified.model_dump(),
84
+ "diff": diff_result.model_dump(),
85
+ },
86
+ )
81
87
  return
82
88
 
83
89
  console.print(Rule("[bold]Changes[/bold]"))
@@ -47,8 +47,7 @@ def _show_service(ctx: typer.Context, query: str) -> None:
47
47
  if not svc_def:
48
48
  available = [s.service_key for s in registry.list_services(provider)[:15]]
49
49
  raise ValueError(
50
- f"Service '{service_key}' not found for provider '{provider}'. "
51
- f"Available: {', '.join(available)}"
50
+ f"Service '{service_key}' not found for provider '{provider}'. Available: {', '.join(available)}"
52
51
  )
53
52
 
54
53
  equivalents = {}
@@ -80,7 +79,9 @@ def _show_service(ctx: typer.Context, query: str) -> None:
80
79
  Panel(
81
80
  f"[bold]{svc_def.name}[/bold] ({provider}.{service_key})\n"
82
81
  f"Category: {svc_def.category} | Pricing: {svc_def.pricing_formula}\n"
83
- f"{svc_def.description}" if svc_def.description else "",
82
+ f"{svc_def.description}"
83
+ if svc_def.description
84
+ else "",
84
85
  title="Service Schema",
85
86
  )
86
87
  )
@@ -147,10 +148,7 @@ def _show_compliance(ctx: typer.Context, framework: str) -> None:
147
148
  data = {
148
149
  "framework": result.framework,
149
150
  "total_checks": len(result.checks),
150
- "checks": [
151
- {"name": c.name, "category": c.category, "severity": c.severity}
152
- for c in result.checks
153
- ],
151
+ "checks": [{"name": c.name, "category": c.category, "severity": c.severity} for c in result.checks],
154
152
  }
155
153
 
156
154
  if is_json_mode(ctx):
@@ -32,13 +32,15 @@ def security_scan(
32
32
  if is_json_mode(ctx):
33
33
  if should_stream(ctx):
34
34
  for f in report.findings:
35
- emit_stream({
36
- "severity": f.severity,
37
- "rule": f.rule,
38
- "component_id": f.component_id,
39
- "message": f.message,
40
- "remediation": f.remediation,
41
- })
35
+ emit_stream(
36
+ {
37
+ "severity": f.severity,
38
+ "rule": f.rule,
39
+ "component_id": f.component_id,
40
+ "message": f.message,
41
+ "remediation": f.remediation,
42
+ }
43
+ )
42
44
  else:
43
45
  result = {
44
46
  "passed": report.passed,
@@ -68,13 +68,15 @@ def validate(
68
68
  if should_stream(ctx):
69
69
  for result in results:
70
70
  for check in result.checks:
71
- emit_stream({
72
- "framework": result.framework,
73
- "check": check.name,
74
- "passed": check.passed,
75
- "detail": check.detail,
76
- "recommendation": check.recommendation,
77
- })
71
+ emit_stream(
72
+ {
73
+ "framework": result.framework,
74
+ "check": check.name,
75
+ "passed": check.passed,
76
+ "detail": check.detail,
77
+ "recommendation": check.recommendation,
78
+ }
79
+ )
78
80
  else:
79
81
  emit_success(ctx, {"results": [r.model_dump(exclude_none=True) for r in results]})
80
82
  return
@@ -24,6 +24,26 @@ def complete_compliance(incomplete: str) -> list[tuple[str, str]]:
24
24
  return [(f, h) for f, h in frameworks if f.startswith(incomplete)]
25
25
 
26
26
 
27
+ def complete_pricing_tier(incomplete: str) -> list[tuple[str, str]]:
28
+ tiers = [
29
+ ("on_demand", "Standard on-demand pricing"),
30
+ ("reserved_1yr", "1-year reserved (40% savings)"),
31
+ ("reserved_3yr", "3-year reserved (60% savings)"),
32
+ ("spot", "Spot/preemptible (70% savings)"),
33
+ ]
34
+ return [(t, h) for t, h in tiers if t.startswith(incomplete)]
35
+
36
+
37
+ def complete_workload_profile(incomplete: str) -> list[tuple[str, str]]:
38
+ profiles = [
39
+ ("small", "Startup/dev — low traffic, minimal redundancy"),
40
+ ("medium", "Production — moderate traffic, multi-AZ databases"),
41
+ ("large", "Scale — high traffic, large clusters and storage"),
42
+ ("enterprise", "Enterprise — very high traffic, full redundancy"),
43
+ ]
44
+ return [(p, h) for p, h in profiles if p.startswith(incomplete)]
45
+
46
+
27
47
  def complete_export_format(incomplete: str) -> list[tuple[str, str]]:
28
48
  formats = [
29
49
  ("terraform", "HashiCorp Terraform HCL"),
@@ -1 +0,0 @@
1
- __version__ = "0.3.1"