argus-cloud-optimizer 0.4.0__tar.gz → 0.4.1__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 (106) hide show
  1. {argus_cloud_optimizer-0.4.0/argus_cloud_optimizer.egg-info → argus_cloud_optimizer-0.4.1}/PKG-INFO +22 -8
  2. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/README.md +20 -6
  3. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/aws/adapter.py +3 -1
  4. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/gcp/adapter.py +5 -1
  5. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/gcp/asset_inventory.py +66 -27
  6. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/gcp/cloud_logging.py +1 -2
  7. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/gcp/cloud_monitoring.py +2 -2
  8. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/ai/azure_openai.py +16 -0
  9. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/ai/vertexai.py +6 -3
  10. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1/argus_cloud_optimizer.egg-info}/PKG-INFO +22 -8
  11. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/argus_cloud_optimizer.egg-info/SOURCES.txt +1 -0
  12. argus_cloud_optimizer-0.4.1/core/__version__.py +1 -0
  13. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/agent/loop.py +6 -0
  14. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/agent/prompts.py +1 -1
  15. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/config.py +1 -1
  16. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/reports/delivery.py +16 -2
  17. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/reports/generator.py +21 -0
  18. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/deploy/aws/multi-account/spoke-role.yaml +1 -0
  19. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/deploy/aws/single-account/template.yaml +1 -0
  20. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/deploy/gcp/deploy.sh +34 -2
  21. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/concepts/ai-providers.md +25 -3
  22. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/getting-started/configuration.md +2 -2
  23. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/getting-started/quickstart.md +54 -2
  24. argus_cloud_optimizer-0.4.1/docs/index.md +441 -0
  25. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/reference/env-vars.md +3 -3
  26. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/reference/troubleshooting.md +25 -4
  27. argus_cloud_optimizer-0.4.1/docs/roadmap.md +80 -0
  28. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/entrypoints/aws_lambda.py +16 -2
  29. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/entrypoints/azure_function.py +17 -2
  30. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/entrypoints/cli.py +3 -1
  31. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/entrypoints/cli_chat.py +2 -4
  32. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/entrypoints/gcp_cloudrun.py +93 -16
  33. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/pyproject.toml +1 -1
  34. argus_cloud_optimizer-0.4.0/core/__version__.py +0 -1
  35. argus_cloud_optimizer-0.4.0/docs/index.md +0 -278
  36. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/.env.example +0 -0
  37. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/LICENSE +0 -0
  38. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/MANIFEST.in +0 -0
  39. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/accounts.yaml.example +0 -0
  40. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/__init__.py +0 -0
  41. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/aws/__init__.py +0 -0
  42. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/aws/auth.py +0 -0
  43. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/aws/cloudtrail.py +0 -0
  44. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/aws/cloudwatch.py +0 -0
  45. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/aws/config.py +0 -0
  46. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/aws/cost_explorer.py +0 -0
  47. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/aws/resource_explorer.py +0 -0
  48. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/aws/retry.py +0 -0
  49. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/azure/__init__.py +0 -0
  50. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/azure/activity_log.py +0 -0
  51. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/azure/adapter.py +0 -0
  52. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/azure/cost_management.py +0 -0
  53. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/azure/monitor.py +0 -0
  54. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/azure/resource_graph.py +0 -0
  55. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/azure/retry.py +0 -0
  56. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/base.py +0 -0
  57. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/gcp/__init__.py +0 -0
  58. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/gcp/billing.py +0 -0
  59. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/adapters/gcp/retry.py +0 -0
  60. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/ai/__init__.py +0 -0
  61. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/ai/anthropic.py +0 -0
  62. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/ai/base.py +0 -0
  63. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/ai/bedrock.py +0 -0
  64. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/argus_cloud_optimizer.egg-info/dependency_links.txt +0 -0
  65. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/argus_cloud_optimizer.egg-info/entry_points.txt +0 -0
  66. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/argus_cloud_optimizer.egg-info/requires.txt +0 -0
  67. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/argus_cloud_optimizer.egg-info/top_level.txt +0 -0
  68. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/__init__.py +0 -0
  69. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/agent/__init__.py +0 -0
  70. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/agent/chat.py +0 -0
  71. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/log.py +0 -0
  72. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/models/__init__.py +0 -0
  73. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/models/finding.py +0 -0
  74. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/py.typed +0 -0
  75. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/reports/__init__.py +0 -0
  76. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/reports/comparison.py +0 -0
  77. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/reports/export.py +0 -0
  78. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/reports/html.py +0 -0
  79. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/reports/multi_cloud.py +0 -0
  80. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/secrets.py +0 -0
  81. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/token_tracker.py +0 -0
  82. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/core/validation.py +0 -0
  83. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/deploy/aws/multi-account/hub/template.yaml +0 -0
  84. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/deploy/azure/function-app.bicep +0 -0
  85. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/concepts/adapters.md +0 -0
  86. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/concepts/ai-reasoning.md +0 -0
  87. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/concepts/how-it-works.md +0 -0
  88. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/concepts/index.md +0 -0
  89. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/contributing/development.md +0 -0
  90. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/contributing/index.md +0 -0
  91. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/contributing/new-adapter.md +0 -0
  92. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/contributing/new-ai-provider.md +0 -0
  93. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/deployment/aws.md +0 -0
  94. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/deployment/azure.md +0 -0
  95. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/deployment/gcp.md +0 -0
  96. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/deployment/index.md +0 -0
  97. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/deployment/multi-account.md +0 -0
  98. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/getting-started/first-scan.md +0 -0
  99. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/getting-started/index.md +0 -0
  100. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/reference/iam-permissions.md +0 -0
  101. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/reference/index.md +0 -0
  102. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/reference/report-schema.md +0 -0
  103. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/reference/resource-types.md +0 -0
  104. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/docs/reference/security.md +0 -0
  105. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/entrypoints/__init__.py +0 -0
  106. {argus_cloud_optimizer-0.4.0 → argus_cloud_optimizer-0.4.1}/setup.cfg +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: argus-cloud-optimizer
3
- Version: 0.4.0
4
- Summary: AI-powered multi-cloud cost optimization agent
3
+ Version: 0.4.1
4
+ Summary: Argus — AI Cloud Detective for AWS, GCP, and Azure
5
5
  Author-email: Vamshi Siddarth Gaddam <vamshisiddarth02@gmail.com>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/vamshisiddarth/argus
@@ -62,12 +62,12 @@ Requires-Dist: mkdocs-minify-plugin>=0.8.0; extra == "dev"
62
62
  Dynamic: license-file
63
63
 
64
64
  <p align="center">
65
- <img src="docs/assets/images/logo-full.svg" alt="Argus" height="72">
65
+ <img src="docs/assets/images/logo.svg" alt="Argus" height="72">
66
66
  </p>
67
67
 
68
- <p align="center"><strong>AI-powered cloud cost optimization agent for AWS, GCP, and Azure.</strong></p>
68
+ <p align="center"><strong>Argus — AI Cloud Detective for AWS, GCP, and Azure.</strong></p>
69
69
 
70
- Argus finds idle and wasted cloud resources — stopped EC2 instances, unattached EBS volumes, orphaned Elastic IPs, underutilized RDS databases — and delivers a prioritized, AI-reasoned report to Slack every week.
70
+ Argus is an open-source AI Cloud Detective — it hunts idle and wasted cloud resources across AWS, GCP, and Azure (stopped EC2 instances, unattached EBS volumes, orphaned Elastic IPs, underutilized RDS databases) and delivers a prioritized, AI-reasoned report to Slack every week. No rules. No thresholds. Just reasoning.
71
71
 
72
72
  [![CI](https://github.com/vamshisiddarth/argus/actions/workflows/ci.yml/badge.svg)](https://github.com/vamshisiddarth/argus/actions/workflows/ci.yml)
73
73
  [![Publish](https://github.com/vamshisiddarth/argus/actions/workflows/publish.yml/badge.svg)](https://github.com/vamshisiddarth/argus/actions/workflows/publish.yml)
@@ -100,6 +100,20 @@ Top findings
100
100
 
101
101
  ---
102
102
 
103
+ ## 🆕 What's new in v0.4.1
104
+
105
+ > **Biggest release yet.** Argus jumps from scheduled reporter to a conversational FinOps companion.
106
+
107
+ - **Interactive chat mode** — `argus chat` lets you ask natural-language questions about your cloud costs and get intelligent, context-aware answers backed by real metrics and cost data.
108
+ - **True multi-cloud, multi-account** — GCP multi-project and Azure multi-subscription support with unified reporting across all accounts in a single run.
109
+ - **Expanded resource coverage** — 31 GCP resource types (up from 22) and 40 Azure resource types (up from 26), all with curated metric mappings.
110
+ - **Chat UX polish** — live spinner updates per tool call, AI-generated context summaries, turn-safe history trimming, and rich terminal formatting via `pip install argus-cloud-optimizer[chat]`.
111
+ - **Docs overhaul** — new architecture diagram, corrected IAM permissions for all three clouds, and accurate env-var reference.
112
+
113
+ [Full changelog →](CHANGELOG.md)
114
+
115
+ ---
116
+
103
117
  ## What it does
104
118
 
105
119
  Every week (or on demand), Argus:
@@ -166,13 +180,13 @@ docker run --rm \
166
180
 
167
181
  ```bash
168
182
  pip install argus-cloud-optimizer
169
- argus --version # argus 0.3.0
183
+ argus --version # argus 0.4.1
170
184
  argus --help
171
185
  ```
172
186
 
173
187
  One package — all three clouds included. No extras needed. `--cloud` auto-detects from your environment (GCP_PROJECT_ID / AZURE_SUBSCRIPTION_IDS / AWS credentials), or specify explicitly.
174
188
 
175
- > **Verified 2026-06-21:** `pip install argus-cloud-optimizer && argus --version` works on a clean Python 3.11/3.12/3.13 venv with no project files.
189
+ > **Verified 2026-06-24:** `pip install argus-cloud-optimizer && argus --version` works on a clean Python 3.11/3.12/3.13 venv with no project files.
176
190
 
177
191
  **AWS:**
178
192
 
@@ -231,7 +245,7 @@ argus chat # auto-detects cloud
231
245
  ```
232
246
 
233
247
  ```
234
- Argus v0.3.0 — Interactive Cloud Cost Assistant
248
+ Argus v0.4.1 — AI Cloud Detective
235
249
  Cloud: AWS | Accounts: prod (123456789012) | Budget: $1.00/session
236
250
  Tip: end a line with \ to continue on the next line.
237
251
  Type your question, or /help for commands.
@@ -1,10 +1,10 @@
1
1
  <p align="center">
2
- <img src="docs/assets/images/logo-full.svg" alt="Argus" height="72">
2
+ <img src="docs/assets/images/logo.svg" alt="Argus" height="72">
3
3
  </p>
4
4
 
5
- <p align="center"><strong>AI-powered cloud cost optimization agent for AWS, GCP, and Azure.</strong></p>
5
+ <p align="center"><strong>Argus — AI Cloud Detective for AWS, GCP, and Azure.</strong></p>
6
6
 
7
- Argus finds idle and wasted cloud resources — stopped EC2 instances, unattached EBS volumes, orphaned Elastic IPs, underutilized RDS databases — and delivers a prioritized, AI-reasoned report to Slack every week.
7
+ Argus is an open-source AI Cloud Detective — it hunts idle and wasted cloud resources across AWS, GCP, and Azure (stopped EC2 instances, unattached EBS volumes, orphaned Elastic IPs, underutilized RDS databases) and delivers a prioritized, AI-reasoned report to Slack every week. No rules. No thresholds. Just reasoning.
8
8
 
9
9
  [![CI](https://github.com/vamshisiddarth/argus/actions/workflows/ci.yml/badge.svg)](https://github.com/vamshisiddarth/argus/actions/workflows/ci.yml)
10
10
  [![Publish](https://github.com/vamshisiddarth/argus/actions/workflows/publish.yml/badge.svg)](https://github.com/vamshisiddarth/argus/actions/workflows/publish.yml)
@@ -37,6 +37,20 @@ Top findings
37
37
 
38
38
  ---
39
39
 
40
+ ## 🆕 What's new in v0.4.1
41
+
42
+ > **Biggest release yet.** Argus jumps from scheduled reporter to a conversational FinOps companion.
43
+
44
+ - **Interactive chat mode** — `argus chat` lets you ask natural-language questions about your cloud costs and get intelligent, context-aware answers backed by real metrics and cost data.
45
+ - **True multi-cloud, multi-account** — GCP multi-project and Azure multi-subscription support with unified reporting across all accounts in a single run.
46
+ - **Expanded resource coverage** — 31 GCP resource types (up from 22) and 40 Azure resource types (up from 26), all with curated metric mappings.
47
+ - **Chat UX polish** — live spinner updates per tool call, AI-generated context summaries, turn-safe history trimming, and rich terminal formatting via `pip install argus-cloud-optimizer[chat]`.
48
+ - **Docs overhaul** — new architecture diagram, corrected IAM permissions for all three clouds, and accurate env-var reference.
49
+
50
+ [Full changelog →](CHANGELOG.md)
51
+
52
+ ---
53
+
40
54
  ## What it does
41
55
 
42
56
  Every week (or on demand), Argus:
@@ -103,13 +117,13 @@ docker run --rm \
103
117
 
104
118
  ```bash
105
119
  pip install argus-cloud-optimizer
106
- argus --version # argus 0.3.0
120
+ argus --version # argus 0.4.1
107
121
  argus --help
108
122
  ```
109
123
 
110
124
  One package — all three clouds included. No extras needed. `--cloud` auto-detects from your environment (GCP_PROJECT_ID / AZURE_SUBSCRIPTION_IDS / AWS credentials), or specify explicitly.
111
125
 
112
- > **Verified 2026-06-21:** `pip install argus-cloud-optimizer && argus --version` works on a clean Python 3.11/3.12/3.13 venv with no project files.
126
+ > **Verified 2026-06-24:** `pip install argus-cloud-optimizer && argus --version` works on a clean Python 3.11/3.12/3.13 venv with no project files.
113
127
 
114
128
  **AWS:**
115
129
 
@@ -168,7 +182,7 @@ argus chat # auto-detects cloud
168
182
  ```
169
183
 
170
184
  ```
171
- Argus v0.3.0 — Interactive Cloud Cost Assistant
185
+ Argus v0.4.1 — AI Cloud Detective
172
186
  Cloud: AWS | Accounts: prod (123456789012) | Budget: $1.00/session
173
187
  Tip: end a line with \ to continue on the next line.
174
188
  Type your question, or /help for commands.
@@ -82,4 +82,6 @@ class AWSAdapter(CloudAdapter):
82
82
  ) -> "AWSAdapter":
83
83
  """Convenience constructor — resolves auth and returns a ready adapter."""
84
84
  session = auth.get_session(account=account, region=region)
85
- return cls(session=session, aggregator_region=region)
85
+ # Pass aggregator_region=None so __init__ honours RESOURCE_EXPLORER_REGION
86
+ # env var before falling back to the default "us-east-1".
87
+ return cls(session=session, aggregator_region=None)
@@ -33,12 +33,16 @@ class GCPAdapter(CloudAdapter):
33
33
  )
34
34
  self._project_id: str = resolved
35
35
  self._bq_billing_table = bq_billing_table or os.environ.get("BILLING_BQ_TABLE")
36
+ # Populated after list_resources() — asset types skipped due to disabled APIs
37
+ self.skipped_asset_types: list[str] = []
36
38
 
37
39
  def list_resources(self, ignore_regions: list[str] | None = None) -> list[Resource]:
38
- return asset_inventory.list_resources(
40
+ resources, skipped = asset_inventory.list_resources(
39
41
  project_id=self._project_id,
40
42
  ignore_regions=ignore_regions,
41
43
  )
44
+ self.skipped_asset_types = skipped
45
+ return resources
42
46
 
43
47
  def get_metrics(
44
48
  self,
@@ -3,7 +3,12 @@ from __future__ import annotations
3
3
  from typing import Any
4
4
 
5
5
  import structlog
6
- from google.api_core.exceptions import GoogleAPICallError, PermissionDenied
6
+ from google.api_core.exceptions import (
7
+ GoogleAPICallError,
8
+ InvalidArgument,
9
+ NotFound,
10
+ PermissionDenied,
11
+ )
7
12
  from google.cloud import asset_v1
8
13
 
9
14
  from adapters.base import Resource
@@ -32,7 +37,7 @@ SCANNED_ASSET_TYPES: list[str] = [
32
37
  "compute.googleapis.com/VpnTunnel",
33
38
  "vpcaccess.googleapis.com/Connector", # Serverless VPC
34
39
  # --- Databases ---
35
- "sql.googleapis.com/Instance", # Cloud SQL
40
+ "sqladmin.googleapis.com/Instance", # Cloud SQL
36
41
  "spanner.googleapis.com/Instance",
37
42
  "bigtable.googleapis.com/Instance",
38
43
  "alloydb.googleapis.com/Cluster", # AlloyDB (managed Postgres)
@@ -55,42 +60,76 @@ SCANNED_ASSET_TYPES: list[str] = [
55
60
  def list_resources(
56
61
  project_id: str,
57
62
  ignore_regions: list[str] | None = None,
58
- ) -> list[Resource]:
63
+ ) -> tuple[list[Resource], list[str]]:
59
64
  """
60
- Return all billable GCP resources in a project using Cloud Asset Inventory.
61
- Uses a single paginated API call — no per-resource-type enumeration needed.
65
+ Return (resources, skipped_asset_types) for a GCP project.
66
+
67
+ Uses a single paginated Cloud Asset Inventory call. If any asset type's API
68
+ is not enabled in the project, INVALID_ARGUMENT is returned for that type.
69
+ We strip it and retry so a project without Bigtable/Spanner/etc. enabled
70
+ doesn't block the whole scan. Callers receive the list of skipped types so
71
+ they can surface a warning in the Slack digest.
62
72
  """
63
73
  client = asset_v1.AssetServiceClient()
64
74
  parent = f"projects/{project_id}"
65
75
  ignore_set = set(ignore_regions or [])
66
76
  resources: list[Resource] = []
67
-
68
- request = asset_v1.ListAssetsRequest(
69
- parent=parent,
70
- asset_types=SCANNED_ASSET_TYPES,
71
- content_type=asset_v1.ContentType.RESOURCE,
72
- )
73
-
74
- try:
75
- for asset in retry_on_transient(
76
- client.list_assets, request=request, timeout=60
77
- ):
78
- parsed = _parse_asset(asset, ignore_set)
79
- if parsed:
80
- resources.append(parsed)
81
- except PermissionDenied as exc:
82
- raise PermissionError(
83
- f"Argus service account is missing cloudasset.assets.listAssets "
84
- f"permission on project {project_id}."
85
- ) from exc
86
- except GoogleAPICallError as exc:
87
- raise RuntimeError(f"Cloud Asset Inventory API error: {exc}") from exc
77
+ asset_types = list(SCANNED_ASSET_TYPES) # mutable — bad types get stripped
78
+ skipped_types: list[str] = []
79
+
80
+ while True:
81
+ request = asset_v1.ListAssetsRequest(
82
+ parent=parent,
83
+ asset_types=asset_types,
84
+ content_type=asset_v1.ContentType.RESOURCE,
85
+ )
86
+ try:
87
+ for asset in retry_on_transient(
88
+ client.list_assets, request=request, timeout=60
89
+ ):
90
+ parsed = _parse_asset(asset, ignore_set)
91
+ if parsed:
92
+ resources.append(parsed)
93
+ break # success — exit retry loop
94
+ except InvalidArgument as exc:
95
+ # An asset type whose API is not enabled in this project causes
96
+ # INVALID_ARGUMENT. Strip it and retry with the remaining types.
97
+ bad_type = _extract_bad_asset_type(str(exc), asset_types)
98
+ if bad_type:
99
+ logger.warning(
100
+ "asset_type_api_not_enabled_skipping",
101
+ asset_type=bad_type,
102
+ project_id=project_id,
103
+ )
104
+ skipped_types.append(bad_type)
105
+ asset_types.remove(bad_type)
106
+ resources = [] # reset — avoid duplicates from partial page
107
+ else:
108
+ raise RuntimeError(
109
+ f"Cloud Asset Inventory INVALID_ARGUMENT (unknown type): {exc}"
110
+ ) from exc
111
+ except (PermissionDenied, NotFound) as exc:
112
+ raise PermissionError(
113
+ f"Project '{project_id}' not found or Argus lacks permission. "
114
+ f"Check the project ID and ensure cloudasset.assets.listAssets "
115
+ f"is granted to your credentials."
116
+ ) from exc
117
+ except GoogleAPICallError as exc:
118
+ raise RuntimeError(f"Cloud Asset Inventory API error: {exc}") from exc
88
119
 
89
120
  logger.info(
90
121
  "asset_inventory_complete",
91
122
  extra={"project_id": project_id, "total": len(resources)},
92
123
  )
93
- return resources
124
+ return resources, skipped_types
125
+
126
+
127
+ def _extract_bad_asset_type(error_msg: str, asset_types: list[str]) -> str | None:
128
+ """Return the first asset type string found in an INVALID_ARGUMENT error."""
129
+ for asset_type in asset_types:
130
+ if asset_type in error_msg:
131
+ return asset_type
132
+ return None
94
133
 
95
134
 
96
135
  def _parse_asset(asset: Any, ignore_set: set[str]) -> Resource | None:
@@ -52,7 +52,6 @@ def get_last_activity(
52
52
  filter_=log_filter,
53
53
  order_by=gcp_logging.DESCENDING,
54
54
  page_size=1,
55
- timeout=60,
56
55
  )
57
56
  )
58
57
  except GoogleAPICallError as exc:
@@ -76,7 +75,7 @@ def _service_from_resource_type(resource_type: str) -> str | None:
76
75
  mapping: dict[str, str] = {
77
76
  "compute.googleapis.com/Instance": "compute.googleapis.com",
78
77
  "compute.googleapis.com/Disk": "compute.googleapis.com",
79
- "sql.googleapis.com/Instance": "cloudsql.googleapis.com",
78
+ "sqladmin.googleapis.com/Instance": "cloudsql.googleapis.com",
80
79
  "container.googleapis.com/Cluster": "container.googleapis.com",
81
80
  "storage.googleapis.com/Bucket": "storage.googleapis.com",
82
81
  "bigquery.googleapis.com/Dataset": "bigquery.googleapis.com",
@@ -26,7 +26,7 @@ _METRICS: dict[str, list[tuple[str, str]]] = {
26
26
  ("compute.googleapis.com/instance/disk/read_ops_count", "sum"),
27
27
  ("compute.googleapis.com/instance/disk/write_ops_count", "sum"),
28
28
  ],
29
- "sql.googleapis.com/Instance": [
29
+ "sqladmin.googleapis.com/Instance": [
30
30
  ("cloudsql.googleapis.com/database/cpu/utilization", "mean"),
31
31
  ("cloudsql.googleapis.com/database/network/connections", "mean"),
32
32
  ("cloudsql.googleapis.com/database/network/received_bytes_count", "sum"),
@@ -394,7 +394,7 @@ def _resource_filter(resource_id: str, resource_type: str) -> str:
394
394
  match resource_type:
395
395
  case "compute.googleapis.com/Instance":
396
396
  return f'resource.labels.instance_id="{name}"'
397
- case "sql.googleapis.com/Instance":
397
+ case "sqladmin.googleapis.com/Instance":
398
398
  return f'resource.labels.database_id="{name}"'
399
399
  case "container.googleapis.com/Cluster":
400
400
  return f'resource.labels.cluster_name="{name}"'
@@ -128,6 +128,22 @@ class AzureOpenAIProvider(AIProvider):
128
128
  for attempt in range(MAX_RETRIES):
129
129
  try:
130
130
  return self._client.chat.completions.create(**kwargs)
131
+ except openai.BadRequestError as exc:
132
+ # Reasoning models (o1/o3/o4) have two restrictions vs standard models:
133
+ # 1. Require max_completion_tokens instead of max_tokens.
134
+ # 2. Do not accept temperature (fixed at 1).
135
+ # Catch both in one retry so we don't need two round-trips.
136
+ err_msg = str(exc)
137
+ changed = False
138
+ if "max_completion_tokens" in err_msg and "max_tokens" in kwargs:
139
+ kwargs["max_completion_tokens"] = kwargs.pop("max_tokens")
140
+ changed = True
141
+ if "temperature" in err_msg and "temperature" in kwargs:
142
+ kwargs.pop("temperature")
143
+ changed = True
144
+ if changed:
145
+ continue
146
+ raise
131
147
  except openai.RateLimitError:
132
148
  if attempt < MAX_RETRIES - 1:
133
149
  logger.warning(
@@ -9,7 +9,9 @@ No API key needed when running on Cloud Run / GCE with the right service account
9
9
  Environment variables:
10
10
  VERTEXAI_PROJECT GCP project ID (required)
11
11
  VERTEXAI_LOCATION GCP region (default: us-central1)
12
- VERTEXAI_MODEL Model name (default: gemini-1.5-pro-002)
12
+ VERTEXAI_MODEL Model name (default: google/gemini-1.5-pro-002)
13
+ Must include publisher prefix for the OpenAI-compat endpoint
14
+ (e.g. google/gemini-1.5-pro-002, google/gemini-2.0-flash-001)
13
15
  """
14
16
 
15
17
  from __future__ import annotations
@@ -38,10 +40,11 @@ class VertexAIProvider(AIProvider):
38
40
  or directly via vertexai package. Falls back to the OpenAI-compatible
39
41
  Vertex AI endpoint so we can reuse the openai SDK already in requirements.txt.
40
42
 
41
- Model: gemini-1.5-pro-002 (default) — supports function calling + large context.
43
+ Model: google/gemini-1.5-pro-002 (default) — the publisher prefix is required
44
+ by the OpenAI-compatible Vertex AI endpoint (/endpoints/openapi).
42
45
  """
43
46
 
44
- DEFAULT_MODEL = "gemini-1.5-pro-002"
47
+ DEFAULT_MODEL = "google/gemini-1.5-pro-002"
45
48
  DEFAULT_LOCATION = "us-central1"
46
49
  DEFAULT_MAX_TOKENS = 4096
47
50
  DEFAULT_TEMPERATURE = 0.0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: argus-cloud-optimizer
3
- Version: 0.4.0
4
- Summary: AI-powered multi-cloud cost optimization agent
3
+ Version: 0.4.1
4
+ Summary: Argus — AI Cloud Detective for AWS, GCP, and Azure
5
5
  Author-email: Vamshi Siddarth Gaddam <vamshisiddarth02@gmail.com>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/vamshisiddarth/argus
@@ -62,12 +62,12 @@ Requires-Dist: mkdocs-minify-plugin>=0.8.0; extra == "dev"
62
62
  Dynamic: license-file
63
63
 
64
64
  <p align="center">
65
- <img src="docs/assets/images/logo-full.svg" alt="Argus" height="72">
65
+ <img src="docs/assets/images/logo.svg" alt="Argus" height="72">
66
66
  </p>
67
67
 
68
- <p align="center"><strong>AI-powered cloud cost optimization agent for AWS, GCP, and Azure.</strong></p>
68
+ <p align="center"><strong>Argus — AI Cloud Detective for AWS, GCP, and Azure.</strong></p>
69
69
 
70
- Argus finds idle and wasted cloud resources — stopped EC2 instances, unattached EBS volumes, orphaned Elastic IPs, underutilized RDS databases — and delivers a prioritized, AI-reasoned report to Slack every week.
70
+ Argus is an open-source AI Cloud Detective — it hunts idle and wasted cloud resources across AWS, GCP, and Azure (stopped EC2 instances, unattached EBS volumes, orphaned Elastic IPs, underutilized RDS databases) and delivers a prioritized, AI-reasoned report to Slack every week. No rules. No thresholds. Just reasoning.
71
71
 
72
72
  [![CI](https://github.com/vamshisiddarth/argus/actions/workflows/ci.yml/badge.svg)](https://github.com/vamshisiddarth/argus/actions/workflows/ci.yml)
73
73
  [![Publish](https://github.com/vamshisiddarth/argus/actions/workflows/publish.yml/badge.svg)](https://github.com/vamshisiddarth/argus/actions/workflows/publish.yml)
@@ -100,6 +100,20 @@ Top findings
100
100
 
101
101
  ---
102
102
 
103
+ ## 🆕 What's new in v0.4.1
104
+
105
+ > **Biggest release yet.** Argus jumps from scheduled reporter to a conversational FinOps companion.
106
+
107
+ - **Interactive chat mode** — `argus chat` lets you ask natural-language questions about your cloud costs and get intelligent, context-aware answers backed by real metrics and cost data.
108
+ - **True multi-cloud, multi-account** — GCP multi-project and Azure multi-subscription support with unified reporting across all accounts in a single run.
109
+ - **Expanded resource coverage** — 31 GCP resource types (up from 22) and 40 Azure resource types (up from 26), all with curated metric mappings.
110
+ - **Chat UX polish** — live spinner updates per tool call, AI-generated context summaries, turn-safe history trimming, and rich terminal formatting via `pip install argus-cloud-optimizer[chat]`.
111
+ - **Docs overhaul** — new architecture diagram, corrected IAM permissions for all three clouds, and accurate env-var reference.
112
+
113
+ [Full changelog →](CHANGELOG.md)
114
+
115
+ ---
116
+
103
117
  ## What it does
104
118
 
105
119
  Every week (or on demand), Argus:
@@ -166,13 +180,13 @@ docker run --rm \
166
180
 
167
181
  ```bash
168
182
  pip install argus-cloud-optimizer
169
- argus --version # argus 0.3.0
183
+ argus --version # argus 0.4.1
170
184
  argus --help
171
185
  ```
172
186
 
173
187
  One package — all three clouds included. No extras needed. `--cloud` auto-detects from your environment (GCP_PROJECT_ID / AZURE_SUBSCRIPTION_IDS / AWS credentials), or specify explicitly.
174
188
 
175
- > **Verified 2026-06-21:** `pip install argus-cloud-optimizer && argus --version` works on a clean Python 3.11/3.12/3.13 venv with no project files.
189
+ > **Verified 2026-06-24:** `pip install argus-cloud-optimizer && argus --version` works on a clean Python 3.11/3.12/3.13 venv with no project files.
176
190
 
177
191
  **AWS:**
178
192
 
@@ -231,7 +245,7 @@ argus chat # auto-detects cloud
231
245
  ```
232
246
 
233
247
  ```
234
- Argus v0.3.0 — Interactive Cloud Cost Assistant
248
+ Argus v0.4.1 — AI Cloud Detective
235
249
  Cloud: AWS | Accounts: prod (123456789012) | Budget: $1.00/session
236
250
  Tip: end a line with \ to continue on the next line.
237
251
  Type your question, or /help for commands.
@@ -68,6 +68,7 @@ deploy/aws/single-account/template.yaml
68
68
  deploy/azure/function-app.bicep
69
69
  deploy/gcp/deploy.sh
70
70
  docs/index.md
71
+ docs/roadmap.md
71
72
  docs/concepts/adapters.md
72
73
  docs/concepts/ai-providers.md
73
74
  docs/concepts/ai-reasoning.md
@@ -0,0 +1 @@
1
+ __version__ = "0.4.1"
@@ -195,6 +195,7 @@ class AgentLoop:
195
195
  resources = self._adapter.list_resources(ignore_regions=ignore_regions or None)
196
196
  resources = _apply_exclusion_filters(resources)
197
197
  total_discovered = len(resources)
198
+ logger.info("resources_discovered", count=total_discovered)
198
199
 
199
200
  if not resources:
200
201
  self._prefiltered_payload = []
@@ -242,6 +243,11 @@ class AgentLoop:
242
243
  discovered=total_discovered,
243
244
  sent_to_ai=len(payload),
244
245
  )
246
+ logger.info(
247
+ "ai_analysis_starting",
248
+ resources=len(payload),
249
+ msg=f"Analyzing {len(payload)} resources with AI (this takes 2-5 minutes)…",
250
+ )
245
251
  return payload
246
252
 
247
253
  def _execute(self, tc: ToolCall) -> tuple[str, bool]:
@@ -84,7 +84,7 @@ def build_system_prompt(
84
84
  """
85
85
  context = _build_context_header(cloud, ignore_regions, accounts)
86
86
 
87
- return f"""You are Argus, an intelligent cloud cost optimization agent.
87
+ return f"""You are Argus, an AI Cloud Detective.
88
88
 
89
89
  MISSION
90
90
  ───────
@@ -48,7 +48,7 @@ class AISettings(BaseSettings):
48
48
  # Vertex AI
49
49
  vertexai_project: str | None = Field(None, alias="VERTEXAI_PROJECT")
50
50
  vertexai_location: str = Field("us-central1", alias="VERTEXAI_LOCATION")
51
- vertexai_model: str = Field("gemini-1.5-pro-002", alias="VERTEXAI_MODEL")
51
+ vertexai_model: str = Field("google/gemini-1.5-pro-002", alias="VERTEXAI_MODEL")
52
52
 
53
53
  # Azure OpenAI
54
54
  azure_openai_endpoint: str | None = Field(None, alias="AZURE_OPENAI_ENDPOINT")
@@ -219,7 +219,14 @@ def build_notification_providers() -> list[NotificationProvider]:
219
219
  return providers
220
220
 
221
221
 
222
- def notify_all(payload: dict[str, Any], dry_run: bool | None = None) -> None:
222
+ def notify_all(payload: dict[str, Any], dry_run: bool | None = None) -> bool:
223
+ """
224
+ Send payload to all configured notification providers.
225
+
226
+ Returns True if at least one provider delivered successfully, False if
227
+ every provider failed. Callers should treat False as a delivery failure
228
+ and exit non-zero so the scheduler / CloudWatch alarm fires.
229
+ """
223
230
  from core.config import get_settings
224
231
 
225
232
  resolved_dry_run = dry_run if dry_run is not None else get_settings().report.dry_run
@@ -229,12 +236,14 @@ def notify_all(payload: dict[str, Any], dry_run: bool | None = None) -> None:
229
236
  "dry_run_notification_skipped",
230
237
  payload_preview=json.dumps(payload, indent=2)[:500],
231
238
  )
232
- return
239
+ return True
233
240
 
234
241
  providers = build_notification_providers()
242
+ any_succeeded = False
235
243
  for provider in providers:
236
244
  try:
237
245
  provider.notify(payload)
246
+ any_succeeded = True
238
247
  except (NotificationDeliveryError, OSError) as exc:
239
248
  logger.error(
240
249
  "notification_delivery_failed",
@@ -242,6 +251,11 @@ def notify_all(payload: dict[str, Any], dry_run: bool | None = None) -> None:
242
251
  error=str(exc),
243
252
  )
244
253
 
254
+ if not any_succeeded and providers:
255
+ logger.error("all_notification_providers_failed", provider_count=len(providers))
256
+
257
+ return any_succeeded or not providers
258
+
245
259
 
246
260
  # ---------------------------------------------------------------------------
247
261
  # Legacy function — delegates to notify_all for backward compatibility
@@ -23,6 +23,7 @@ def build_report(
23
23
  agent_output_tokens: int = 0,
24
24
  scan_diff: dict[str, Any] | None = None,
25
25
  scan_errors: list[dict[str, str]] | None = None,
26
+ skipped_resource_types: list[str] | None = None,
26
27
  ) -> dict[str, Any]:
27
28
  """
28
29
  Convert a list of ResourceFinding objects into the canonical JSON report.
@@ -53,6 +54,7 @@ def build_report(
53
54
  ),
54
55
  "scan_diff": scan_diff,
55
56
  "scan_errors": scan_errors or [],
57
+ "skipped_resource_types": skipped_resource_types or [],
56
58
  }
57
59
 
58
60
 
@@ -207,6 +209,25 @@ def build_slack_payload(
207
209
  {"type": "divider"},
208
210
  ]
209
211
 
212
+ skipped_types = report.get("skipped_resource_types") or []
213
+ if skipped_types:
214
+ short_names = [t.split("/")[-1] for t in skipped_types]
215
+ blocks.append(
216
+ {
217
+ "type": "section",
218
+ "text": {
219
+ "type": "mrkdwn",
220
+ "text": (
221
+ f":information_source: *Skipped {len(skipped_types)} resource "
222
+ f"type{'s' if len(skipped_types) != 1 else ''} "
223
+ f"(API not enabled in project):* "
224
+ + ", ".join(f"`{n}`" for n in short_names)
225
+ ),
226
+ },
227
+ }
228
+ )
229
+ blocks.append({"type": "divider"})
230
+
210
231
  scan_errors = report.get("scan_errors") or []
211
232
  if scan_errors:
212
233
  total_attempted = accounts + len(scan_errors)
@@ -66,6 +66,7 @@ Resources:
66
66
  Effect: Allow
67
67
  Action:
68
68
  - cloudwatch:GetMetricData
69
+ - cloudwatch:ListMetrics
69
70
  Resource: "*"
70
71
 
71
72
  # Cost Explorer — cost data (read-only, per-resource billing breakdown)
@@ -150,6 +150,7 @@ Resources:
150
150
  Effect: Allow
151
151
  Action:
152
152
  - cloudwatch:GetMetricData
153
+ - cloudwatch:ListMetrics
153
154
  Resource: "*"
154
155
 
155
156
  - Sid: CostExplorer