cloudcost-cli 0.1.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.
@@ -0,0 +1 @@
1
+ prune backend/tests
@@ -0,0 +1,340 @@
1
+ Metadata-Version: 2.4
2
+ Name: cloudcost-cli
3
+ Version: 0.1.0
4
+ Summary: CloudCost CLI for Terraform cost estimates and GitHub pull request cost automation.
5
+ Author: CloudCost AI
6
+ Project-URL: Homepage, https://cloudcost.live
7
+ Project-URL: Documentation, https://cloudcost.live/docs/cloudcost-cli.html
8
+ Project-URL: Source, https://github.com/zedxod/cloudcost
9
+ Keywords: finops,terraform,infracost,github,cloud-cost,llm-cost
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Build Tools
17
+ Classifier: Topic :: System :: Systems Administration
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: httpx>=0.27.0
21
+ Requires-Dist: pydantic-settings>=2.5.0
22
+ Provides-Extra: server
23
+ Requires-Dist: fastapi>=0.115.0; extra == "server"
24
+ Requires-Dist: litellm[proxy]>=1.85.0; extra == "server"
25
+ Requires-Dist: prisma>=0.15.0; extra == "server"
26
+ Requires-Dist: psycopg[binary]>=3.2.0; extra == "server"
27
+ Requires-Dist: PyJWT[crypto]>=2.9.0; extra == "server"
28
+ Requires-Dist: python-multipart>=0.0.12; extra == "server"
29
+ Requires-Dist: uvicorn[standard]>=0.30.0; extra == "server"
30
+ Provides-Extra: dev
31
+ Requires-Dist: build>=1.2.0; extra == "dev"
32
+ Requires-Dist: fastapi>=0.115.0; extra == "dev"
33
+ Requires-Dist: litellm[proxy]>=1.85.0; extra == "dev"
34
+ Requires-Dist: prisma>=0.15.0; extra == "dev"
35
+ Requires-Dist: psycopg[binary]>=3.2.0; extra == "dev"
36
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
37
+ Requires-Dist: PyJWT[crypto]>=2.9.0; extra == "dev"
38
+ Requires-Dist: python-multipart>=0.0.12; extra == "dev"
39
+ Requires-Dist: twine>=5.0.0; extra == "dev"
40
+ Requires-Dist: uvicorn[standard]>=0.30.0; extra == "dev"
41
+
42
+ # CloudCost AI
43
+
44
+ CloudCost AI is a developer-first FinOps MVP. It has two mechanisms:
45
+
46
+ 1. An AI spend proxy powered by LiteLLM.
47
+ 2. A GitHub App webhook that estimates Terraform cost deltas in pull requests with Infracost.
48
+
49
+ The landing page is served from `index.html`; the working backend is in `backend/app`.
50
+
51
+ ## Get Started
52
+
53
+ The first-run path is modeled around a local CLI estimate, then GitHub automation:
54
+
55
+ ```powershell
56
+ .\.venv\Scripts\python -m pip install -e .
57
+ cloudcost go
58
+ ```
59
+
60
+ Run `cloudcost go` from a Terraform project directory. It runs the local estimate, then opens the GitHub App manifest install flow with the required webhook, events, and permissions already filled in. If a team only wants the local estimate, `cloudcost` still works by itself. Power users can also run `cloudcost setup`, `cloudcost doctor`, `cloudcost connect-github`, or `cloudcost analyze --plan tfplan.json` directly.
61
+
62
+ When the package is published, users should install it with:
63
+
64
+ ```bash
65
+ pipx install cloudcost-cli
66
+ cloudcost go
67
+ ```
68
+
69
+ Publishing notes are in `docs/publishing-pypi.md`.
70
+
71
+ The signed-in dashboard sidebar links to `/get-started`, a docs-style first-run guide with macOS, Windows, and Linux command tabs that update the CLI install, setup, Terraform, CI, and GitHub steps. The left docs rail links to styled HTML docs for CloudCost CLI, VPS install, and pricing API reference.
72
+
73
+ ## Cloudflare Pages Landing Page
74
+
75
+ For a free public early-access page, deploy the static pages plus the Pages Functions:
76
+
77
+ - `functions/api/waitlist.js`
78
+ - `functions/api/auth/*`
79
+ - `functions/api/dashboard/summary.js`
80
+ - `package.json`
81
+
82
+ Use `npm run build` and set the Cloudflare Pages output directory to `dist`; the build script copies `index.html`, `assets/`, `docs/`, `/login`, `/signup`, `/dashboard`, `/get-started`, and `/tester-dashboard`.
83
+
84
+ Set `DATABASE_URL`, `AUTH_SECRET`, `RESEND_API_KEY`, and `FROM_EMAIL` in Cloudflare Pages environment variables. The Functions store early-access emails, users, OTPs, and browser sessions in Neon, and send Resend confirmations without running the Python backend.
85
+
86
+ See `docs/cloudflare-pages-waitlist.md`.
87
+
88
+ ## One-Click VPS Path
89
+
90
+ For a customer-facing install, use the VPS bundle instead of the local debug path.
91
+
92
+ Prerequisites:
93
+
94
+ - Ubuntu VPS with ports `80` and `443` open
95
+ - A domain pointed at the VPS
96
+ - Docker, or approval for the bootstrap script to install Docker
97
+ - A pricing mode:
98
+ - Recommended: CloudCost hosted pricing key
99
+ - Advanced: Infracost API key for syncing a self-hosted pricing database
100
+
101
+ Run:
102
+
103
+ ```bash
104
+ git clone YOUR_REPO_URL cloudcost-ai
105
+ cd cloudcost-ai
106
+ bash scripts/bootstrap-vps.sh
107
+ ```
108
+
109
+ The script generates local secrets, creates `.env`, starts Caddy HTTPS, backend, LiteLLM, and Postgres. If the customer chooses strict self-hosted pricing, it also initializes and runs the local Infracost pricing API. It then prints:
110
+
111
+ - Dashboard: `https://YOUR_DOMAIN/dashboard`
112
+ - Tester runbook: `https://YOUR_DOMAIN/tester-dashboard`
113
+ - GitHub install page: `https://YOUR_DOMAIN/install/github`
114
+ - Webhook URL: `https://YOUR_DOMAIN/api/github/webhook`
115
+
116
+ Open the GitHub install page and click **Install on GitHub**. GitHub creates a customer-owned GitHub App from CloudCost's manifest and redirects back with the App ID, webhook secret, and private key. CloudCost stores those on the VPS automatically.
117
+
118
+ After setup, run:
119
+
120
+ ```bash
121
+ bash scripts/doctor-vps.sh
122
+ ```
123
+
124
+ This path is the intended customer install path. The longer local setup below remains for development and debugging.
125
+
126
+ ## Architecture
127
+
128
+ ```text
129
+ Browser
130
+ -> FastAPI backend
131
+ -> static landing page
132
+ -> waitlist endpoint
133
+ -> GitHub webhook endpoint
134
+ -> LiteLLM admin wrapper
135
+ -> metadata-only usage ingest
136
+
137
+ LLM clients
138
+ -> LiteLLM proxy :4000
139
+ -> OpenAI / Anthropic / other providers
140
+ -> metadata-only callback
141
+ -> FastAPI /api/llm/usage
142
+
143
+ GitHub pull_request webhook
144
+ -> HMAC validation
145
+ -> installation access token
146
+ -> locate Terraform plan JSON
147
+ -> Infracost Plan JSON API
148
+ -> upsert PR comment
149
+ ```
150
+
151
+ ## Quick Start
152
+
153
+ 1. Create local environment config:
154
+
155
+ ```powershell
156
+ Copy-Item .env.example .env
157
+ ```
158
+
159
+ 2. Fill `.env` with:
160
+
161
+ - `DATABASE_URL`, preferably a pooled Neon Postgres URL with `sslmode=require`
162
+ - `GITHUB_APP_ID` or `GITHUB_CLIENT_ID`
163
+ - `GITHUB_PRIVATE_KEY_PATH`
164
+ - `GITHUB_WEBHOOK_SECRET`
165
+ - `INFRACOST_API_KEY`
166
+ - `LITELLM_MASTER_KEY`
167
+ - at least one provider key, such as `OPENAI_API_KEY`
168
+
169
+ Put the downloaded GitHub App private key at `secrets/github-app-private-key.pem`.
170
+ Docker Compose mounts `./secrets` into the backend container as `/app/secrets`.
171
+
172
+ `DATABASE_URL` is shared by LiteLLM and the backend. LiteLLM uses it for virtual keys and spend/auth state; the backend creates `cloudcost_waitlist`, `cloudcost_usage_events`, `cloudcost_users`, `cloudcost_email_otps`, and `cloudcost_sessions` tables for app data. Set `APP_DATABASE_URL` if you want those app tables in a separate database.
173
+
174
+ The waitlist endpoint accepts only an email address, stores one row per email, and uses `data/waitlist.jsonl` only when no Postgres URL is configured. Set `SMTP_HOST` and `SMTP_FROM_EMAIL` if you want waitlist signups to receive a confirmation email.
175
+
176
+ For app signups, set `AUTH_SECRET` to a long random value. New accounts verify a mail OTP before `/dashboard` creates a session.
177
+
178
+ 3. Start the stack:
179
+
180
+ ```powershell
181
+ docker compose up --build
182
+ ```
183
+
184
+ If Docker Desktop is not installed, run the backend and LiteLLM locally instead:
185
+
186
+ ```powershell
187
+ .\.venv\Scripts\python -m pip install -e ".[dev]"
188
+ .\.venv\Scripts\python -m pip install "litellm[proxy]" prisma
189
+ .\scripts\start-litellm-local.ps1
190
+ ```
191
+
192
+ In a second terminal:
193
+
194
+ ```powershell
195
+ .\.venv\Scripts\python -m uvicorn backend.app.main:app --reload --port 8000
196
+ ```
197
+
198
+ For the local no-Docker path, set `LITELLM_BASE_URL=http://127.0.0.1:4000` in `.env`.
199
+
200
+ 4. Open:
201
+
202
+ - Landing page: `http://localhost:8000`
203
+ - Sign in: `http://localhost:8000/login`
204
+ - Sign up: `http://localhost:8000/signup`
205
+ - Dashboard: `http://localhost:8000/dashboard`
206
+ - Get started: `http://localhost:8000/get-started`
207
+ - Tester runbook: `http://localhost:8000/tester-dashboard`
208
+ - Backend health: `http://localhost:8000/healthz`
209
+ - Mechanism map: `http://localhost:8000/api/mechanism`
210
+ - LiteLLM proxy: `http://localhost:4000`
211
+
212
+ ## GitHub App Setup
213
+
214
+ Register a GitHub App with:
215
+
216
+ - Webhook URL: `https://YOUR_DOMAIN/api/github/webhook`
217
+ - Webhook secret: same value as `GITHUB_WEBHOOK_SECRET`
218
+ - Subscribe to only `pull_request`
219
+ - Repository permissions:
220
+ - Contents: read
221
+ - Pull requests: read and write
222
+ - Issues: write
223
+ - Metadata: read
224
+
225
+ The backend handles `opened`, `reopened`, `synchronize`, and `ready_for_review` pull request actions.
226
+
227
+ ## Terraform Plan JSON
228
+
229
+ The MVP looks for one of these files in the pull request files first, then in the repository tree at the PR head SHA:
230
+
231
+ - `plan.json`
232
+ - `tfplan.json`
233
+ - `terraform-plan.json`
234
+ - `infracost-plan.json`
235
+ - files ending in `.tfplan.json` or `.plan.json`
236
+
237
+ Generate a compatible file with Terraform:
238
+
239
+ ```powershell
240
+ terraform init
241
+ terraform plan -out tfplan.binary
242
+ terraform show -json tfplan.binary > plan.json
243
+ ```
244
+
245
+ When found, the backend runs the local Infracost CLI against the plan JSON, parses `diffTotalMonthlyCost`, and creates or updates one CloudCost AI pull request comment. In the default self-hosted setup, the plan JSON stays inside your backend runtime; the CLI only talks to your Cloud Pricing API for price lookups.
246
+
247
+ ## Self-Hosted Infracost
248
+
249
+ The default project mode is:
250
+
251
+ ```env
252
+ INFRACOST_MODE=cli
253
+ INFRACOST_CLI_PATH=infracost
254
+ INFRACOST_PRICING_API_ENDPOINT=http://127.0.0.1:4001
255
+ ```
256
+
257
+ That means CloudCost AI does not call the hosted Infracost Plan JSON API. Instead:
258
+
259
+ 1. You self-host the Infracost Cloud Pricing API.
260
+ 2. The backend runs `infracost breakdown --path plan.json --format json`.
261
+ 3. The Infracost CLI requests prices from `INFRACOST_PRICING_API_ENDPOINT`.
262
+ 4. The backend comments the result on the pull request.
263
+
264
+ For local Windows development, install the Infracost CLI and make sure `infracost` is on PATH. For Docker builds, the backend image installs the CLI automatically.
265
+
266
+ See `/docs/pricing-api.html` for the full setup path.
267
+
268
+ ## LiteLLM Spend Proxy
269
+
270
+ LiteLLM requires `DATABASE_URL` for persistent virtual keys, budgets, spend logs, and auth state. With Neon, use the pooled connection string from the Neon dashboard:
271
+
272
+ ```env
273
+ DATABASE_URL=postgresql://USER:PASSWORD@HOST/neondb?sslmode=require&channel_binding=require
274
+ ```
275
+
276
+ Create a virtual key through CloudCost AI:
277
+
278
+ ```powershell
279
+ Invoke-RestMethod -Method Post http://localhost:8000/api/llm/keys `
280
+ -ContentType 'application/json' `
281
+ -Body '{
282
+ "user_id": "ada@example.com",
283
+ "team_id": "platform",
284
+ "models": ["gpt-5-mini"],
285
+ "max_budget": 10,
286
+ "budget_duration": "30d",
287
+ "metadata": {"service": "billing"}
288
+ }'
289
+ ```
290
+
291
+ Use the returned key against LiteLLM:
292
+
293
+ ```powershell
294
+ $env:OPENAI_API_KEY = "sk-returned-virtual-key"
295
+ ```
296
+
297
+ Point OpenAI-compatible clients at:
298
+
299
+ ```text
300
+ http://localhost:4000/v1
301
+ ```
302
+
303
+ On the VPS bundle, Caddy exposes LiteLLM through the same HTTPS origin:
304
+
305
+ ```text
306
+ https://YOUR_DOMAIN/llm/v1
307
+ ```
308
+
309
+ LiteLLM enforces the budgets and forwards metadata-only usage records to `/api/llm/usage`. The custom callback deliberately omits prompts, messages, responses, choices, and completions.
310
+
311
+ ## Local Development
312
+
313
+ Install dependencies:
314
+
315
+ ```powershell
316
+ python -m venv .venv
317
+ .\.venv\Scripts\Activate.ps1
318
+ python -m pip install -e ".[dev]"
319
+ ```
320
+
321
+ Run tests:
322
+
323
+ ```powershell
324
+ pytest
325
+ ```
326
+
327
+ Run the backend without Docker:
328
+
329
+ ```powershell
330
+ uvicorn backend.app.main:app --reload --port 8000
331
+ ```
332
+
333
+ ## Important Limits
334
+
335
+ This is an MVP scaffold, not a production billing system yet.
336
+
337
+ - Real GitHub, Infracost, and LLM provider credentials are required for end-to-end live calls.
338
+ - The JSONL stores in `data/` are for local validation; replace them with a database before production.
339
+ - For production GitHub traffic, put the backend behind HTTPS and keep the webhook secret/private key out of the repo.
340
+ - For production LiteLLM, pin image versions, rotate master keys, and use a managed Postgres database.
@@ -0,0 +1,299 @@
1
+ # CloudCost AI
2
+
3
+ CloudCost AI is a developer-first FinOps MVP. It has two mechanisms:
4
+
5
+ 1. An AI spend proxy powered by LiteLLM.
6
+ 2. A GitHub App webhook that estimates Terraform cost deltas in pull requests with Infracost.
7
+
8
+ The landing page is served from `index.html`; the working backend is in `backend/app`.
9
+
10
+ ## Get Started
11
+
12
+ The first-run path is modeled around a local CLI estimate, then GitHub automation:
13
+
14
+ ```powershell
15
+ .\.venv\Scripts\python -m pip install -e .
16
+ cloudcost go
17
+ ```
18
+
19
+ Run `cloudcost go` from a Terraform project directory. It runs the local estimate, then opens the GitHub App manifest install flow with the required webhook, events, and permissions already filled in. If a team only wants the local estimate, `cloudcost` still works by itself. Power users can also run `cloudcost setup`, `cloudcost doctor`, `cloudcost connect-github`, or `cloudcost analyze --plan tfplan.json` directly.
20
+
21
+ When the package is published, users should install it with:
22
+
23
+ ```bash
24
+ pipx install cloudcost-cli
25
+ cloudcost go
26
+ ```
27
+
28
+ Publishing notes are in `docs/publishing-pypi.md`.
29
+
30
+ The signed-in dashboard sidebar links to `/get-started`, a docs-style first-run guide with macOS, Windows, and Linux command tabs that update the CLI install, setup, Terraform, CI, and GitHub steps. The left docs rail links to styled HTML docs for CloudCost CLI, VPS install, and pricing API reference.
31
+
32
+ ## Cloudflare Pages Landing Page
33
+
34
+ For a free public early-access page, deploy the static pages plus the Pages Functions:
35
+
36
+ - `functions/api/waitlist.js`
37
+ - `functions/api/auth/*`
38
+ - `functions/api/dashboard/summary.js`
39
+ - `package.json`
40
+
41
+ Use `npm run build` and set the Cloudflare Pages output directory to `dist`; the build script copies `index.html`, `assets/`, `docs/`, `/login`, `/signup`, `/dashboard`, `/get-started`, and `/tester-dashboard`.
42
+
43
+ Set `DATABASE_URL`, `AUTH_SECRET`, `RESEND_API_KEY`, and `FROM_EMAIL` in Cloudflare Pages environment variables. The Functions store early-access emails, users, OTPs, and browser sessions in Neon, and send Resend confirmations without running the Python backend.
44
+
45
+ See `docs/cloudflare-pages-waitlist.md`.
46
+
47
+ ## One-Click VPS Path
48
+
49
+ For a customer-facing install, use the VPS bundle instead of the local debug path.
50
+
51
+ Prerequisites:
52
+
53
+ - Ubuntu VPS with ports `80` and `443` open
54
+ - A domain pointed at the VPS
55
+ - Docker, or approval for the bootstrap script to install Docker
56
+ - A pricing mode:
57
+ - Recommended: CloudCost hosted pricing key
58
+ - Advanced: Infracost API key for syncing a self-hosted pricing database
59
+
60
+ Run:
61
+
62
+ ```bash
63
+ git clone YOUR_REPO_URL cloudcost-ai
64
+ cd cloudcost-ai
65
+ bash scripts/bootstrap-vps.sh
66
+ ```
67
+
68
+ The script generates local secrets, creates `.env`, starts Caddy HTTPS, backend, LiteLLM, and Postgres. If the customer chooses strict self-hosted pricing, it also initializes and runs the local Infracost pricing API. It then prints:
69
+
70
+ - Dashboard: `https://YOUR_DOMAIN/dashboard`
71
+ - Tester runbook: `https://YOUR_DOMAIN/tester-dashboard`
72
+ - GitHub install page: `https://YOUR_DOMAIN/install/github`
73
+ - Webhook URL: `https://YOUR_DOMAIN/api/github/webhook`
74
+
75
+ Open the GitHub install page and click **Install on GitHub**. GitHub creates a customer-owned GitHub App from CloudCost's manifest and redirects back with the App ID, webhook secret, and private key. CloudCost stores those on the VPS automatically.
76
+
77
+ After setup, run:
78
+
79
+ ```bash
80
+ bash scripts/doctor-vps.sh
81
+ ```
82
+
83
+ This path is the intended customer install path. The longer local setup below remains for development and debugging.
84
+
85
+ ## Architecture
86
+
87
+ ```text
88
+ Browser
89
+ -> FastAPI backend
90
+ -> static landing page
91
+ -> waitlist endpoint
92
+ -> GitHub webhook endpoint
93
+ -> LiteLLM admin wrapper
94
+ -> metadata-only usage ingest
95
+
96
+ LLM clients
97
+ -> LiteLLM proxy :4000
98
+ -> OpenAI / Anthropic / other providers
99
+ -> metadata-only callback
100
+ -> FastAPI /api/llm/usage
101
+
102
+ GitHub pull_request webhook
103
+ -> HMAC validation
104
+ -> installation access token
105
+ -> locate Terraform plan JSON
106
+ -> Infracost Plan JSON API
107
+ -> upsert PR comment
108
+ ```
109
+
110
+ ## Quick Start
111
+
112
+ 1. Create local environment config:
113
+
114
+ ```powershell
115
+ Copy-Item .env.example .env
116
+ ```
117
+
118
+ 2. Fill `.env` with:
119
+
120
+ - `DATABASE_URL`, preferably a pooled Neon Postgres URL with `sslmode=require`
121
+ - `GITHUB_APP_ID` or `GITHUB_CLIENT_ID`
122
+ - `GITHUB_PRIVATE_KEY_PATH`
123
+ - `GITHUB_WEBHOOK_SECRET`
124
+ - `INFRACOST_API_KEY`
125
+ - `LITELLM_MASTER_KEY`
126
+ - at least one provider key, such as `OPENAI_API_KEY`
127
+
128
+ Put the downloaded GitHub App private key at `secrets/github-app-private-key.pem`.
129
+ Docker Compose mounts `./secrets` into the backend container as `/app/secrets`.
130
+
131
+ `DATABASE_URL` is shared by LiteLLM and the backend. LiteLLM uses it for virtual keys and spend/auth state; the backend creates `cloudcost_waitlist`, `cloudcost_usage_events`, `cloudcost_users`, `cloudcost_email_otps`, and `cloudcost_sessions` tables for app data. Set `APP_DATABASE_URL` if you want those app tables in a separate database.
132
+
133
+ The waitlist endpoint accepts only an email address, stores one row per email, and uses `data/waitlist.jsonl` only when no Postgres URL is configured. Set `SMTP_HOST` and `SMTP_FROM_EMAIL` if you want waitlist signups to receive a confirmation email.
134
+
135
+ For app signups, set `AUTH_SECRET` to a long random value. New accounts verify a mail OTP before `/dashboard` creates a session.
136
+
137
+ 3. Start the stack:
138
+
139
+ ```powershell
140
+ docker compose up --build
141
+ ```
142
+
143
+ If Docker Desktop is not installed, run the backend and LiteLLM locally instead:
144
+
145
+ ```powershell
146
+ .\.venv\Scripts\python -m pip install -e ".[dev]"
147
+ .\.venv\Scripts\python -m pip install "litellm[proxy]" prisma
148
+ .\scripts\start-litellm-local.ps1
149
+ ```
150
+
151
+ In a second terminal:
152
+
153
+ ```powershell
154
+ .\.venv\Scripts\python -m uvicorn backend.app.main:app --reload --port 8000
155
+ ```
156
+
157
+ For the local no-Docker path, set `LITELLM_BASE_URL=http://127.0.0.1:4000` in `.env`.
158
+
159
+ 4. Open:
160
+
161
+ - Landing page: `http://localhost:8000`
162
+ - Sign in: `http://localhost:8000/login`
163
+ - Sign up: `http://localhost:8000/signup`
164
+ - Dashboard: `http://localhost:8000/dashboard`
165
+ - Get started: `http://localhost:8000/get-started`
166
+ - Tester runbook: `http://localhost:8000/tester-dashboard`
167
+ - Backend health: `http://localhost:8000/healthz`
168
+ - Mechanism map: `http://localhost:8000/api/mechanism`
169
+ - LiteLLM proxy: `http://localhost:4000`
170
+
171
+ ## GitHub App Setup
172
+
173
+ Register a GitHub App with:
174
+
175
+ - Webhook URL: `https://YOUR_DOMAIN/api/github/webhook`
176
+ - Webhook secret: same value as `GITHUB_WEBHOOK_SECRET`
177
+ - Subscribe to only `pull_request`
178
+ - Repository permissions:
179
+ - Contents: read
180
+ - Pull requests: read and write
181
+ - Issues: write
182
+ - Metadata: read
183
+
184
+ The backend handles `opened`, `reopened`, `synchronize`, and `ready_for_review` pull request actions.
185
+
186
+ ## Terraform Plan JSON
187
+
188
+ The MVP looks for one of these files in the pull request files first, then in the repository tree at the PR head SHA:
189
+
190
+ - `plan.json`
191
+ - `tfplan.json`
192
+ - `terraform-plan.json`
193
+ - `infracost-plan.json`
194
+ - files ending in `.tfplan.json` or `.plan.json`
195
+
196
+ Generate a compatible file with Terraform:
197
+
198
+ ```powershell
199
+ terraform init
200
+ terraform plan -out tfplan.binary
201
+ terraform show -json tfplan.binary > plan.json
202
+ ```
203
+
204
+ When found, the backend runs the local Infracost CLI against the plan JSON, parses `diffTotalMonthlyCost`, and creates or updates one CloudCost AI pull request comment. In the default self-hosted setup, the plan JSON stays inside your backend runtime; the CLI only talks to your Cloud Pricing API for price lookups.
205
+
206
+ ## Self-Hosted Infracost
207
+
208
+ The default project mode is:
209
+
210
+ ```env
211
+ INFRACOST_MODE=cli
212
+ INFRACOST_CLI_PATH=infracost
213
+ INFRACOST_PRICING_API_ENDPOINT=http://127.0.0.1:4001
214
+ ```
215
+
216
+ That means CloudCost AI does not call the hosted Infracost Plan JSON API. Instead:
217
+
218
+ 1. You self-host the Infracost Cloud Pricing API.
219
+ 2. The backend runs `infracost breakdown --path plan.json --format json`.
220
+ 3. The Infracost CLI requests prices from `INFRACOST_PRICING_API_ENDPOINT`.
221
+ 4. The backend comments the result on the pull request.
222
+
223
+ For local Windows development, install the Infracost CLI and make sure `infracost` is on PATH. For Docker builds, the backend image installs the CLI automatically.
224
+
225
+ See `/docs/pricing-api.html` for the full setup path.
226
+
227
+ ## LiteLLM Spend Proxy
228
+
229
+ LiteLLM requires `DATABASE_URL` for persistent virtual keys, budgets, spend logs, and auth state. With Neon, use the pooled connection string from the Neon dashboard:
230
+
231
+ ```env
232
+ DATABASE_URL=postgresql://USER:PASSWORD@HOST/neondb?sslmode=require&channel_binding=require
233
+ ```
234
+
235
+ Create a virtual key through CloudCost AI:
236
+
237
+ ```powershell
238
+ Invoke-RestMethod -Method Post http://localhost:8000/api/llm/keys `
239
+ -ContentType 'application/json' `
240
+ -Body '{
241
+ "user_id": "ada@example.com",
242
+ "team_id": "platform",
243
+ "models": ["gpt-5-mini"],
244
+ "max_budget": 10,
245
+ "budget_duration": "30d",
246
+ "metadata": {"service": "billing"}
247
+ }'
248
+ ```
249
+
250
+ Use the returned key against LiteLLM:
251
+
252
+ ```powershell
253
+ $env:OPENAI_API_KEY = "sk-returned-virtual-key"
254
+ ```
255
+
256
+ Point OpenAI-compatible clients at:
257
+
258
+ ```text
259
+ http://localhost:4000/v1
260
+ ```
261
+
262
+ On the VPS bundle, Caddy exposes LiteLLM through the same HTTPS origin:
263
+
264
+ ```text
265
+ https://YOUR_DOMAIN/llm/v1
266
+ ```
267
+
268
+ LiteLLM enforces the budgets and forwards metadata-only usage records to `/api/llm/usage`. The custom callback deliberately omits prompts, messages, responses, choices, and completions.
269
+
270
+ ## Local Development
271
+
272
+ Install dependencies:
273
+
274
+ ```powershell
275
+ python -m venv .venv
276
+ .\.venv\Scripts\Activate.ps1
277
+ python -m pip install -e ".[dev]"
278
+ ```
279
+
280
+ Run tests:
281
+
282
+ ```powershell
283
+ pytest
284
+ ```
285
+
286
+ Run the backend without Docker:
287
+
288
+ ```powershell
289
+ uvicorn backend.app.main:app --reload --port 8000
290
+ ```
291
+
292
+ ## Important Limits
293
+
294
+ This is an MVP scaffold, not a production billing system yet.
295
+
296
+ - Real GitHub, Infracost, and LLM provider credentials are required for end-to-end live calls.
297
+ - The JSONL stores in `data/` are for local validation; replace them with a database before production.
298
+ - For production GitHub traffic, put the backend behind HTTPS and keep the webhook secret/private key out of the repo.
299
+ - For production LiteLLM, pin image versions, rotate master keys, and use a managed Postgres database.
@@ -0,0 +1 @@
1
+ """CloudCost AI backend package."""
@@ -0,0 +1 @@
1
+ """Application modules for CloudCost AI."""