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.
- cloudcost_cli-0.1.0/MANIFEST.in +1 -0
- cloudcost_cli-0.1.0/PKG-INFO +340 -0
- cloudcost_cli-0.1.0/README.md +299 -0
- cloudcost_cli-0.1.0/backend/__init__.py +1 -0
- cloudcost_cli-0.1.0/backend/app/__init__.py +1 -0
- cloudcost_cli-0.1.0/backend/app/auth.py +104 -0
- cloudcost_cli-0.1.0/backend/app/cli.py +726 -0
- cloudcost_cli-0.1.0/backend/app/comments.py +94 -0
- cloudcost_cli-0.1.0/backend/app/config.py +191 -0
- cloudcost_cli-0.1.0/backend/app/database.py +470 -0
- cloudcost_cli-0.1.0/backend/app/emailer.py +157 -0
- cloudcost_cli-0.1.0/backend/app/github_client.py +197 -0
- cloudcost_cli-0.1.0/backend/app/infracost.py +129 -0
- cloudcost_cli-0.1.0/backend/app/litellm_admin.py +41 -0
- cloudcost_cli-0.1.0/backend/app/main.py +833 -0
- cloudcost_cli-0.1.0/backend/app/model_pricing.py +80 -0
- cloudcost_cli-0.1.0/backend/app/security.py +15 -0
- cloudcost_cli-0.1.0/backend/app/storage.py +31 -0
- cloudcost_cli-0.1.0/backend/app/usage.py +73 -0
- cloudcost_cli-0.1.0/cloudcost_cli.egg-info/PKG-INFO +340 -0
- cloudcost_cli-0.1.0/cloudcost_cli.egg-info/SOURCES.txt +25 -0
- cloudcost_cli-0.1.0/cloudcost_cli.egg-info/dependency_links.txt +1 -0
- cloudcost_cli-0.1.0/cloudcost_cli.egg-info/entry_points.txt +2 -0
- cloudcost_cli-0.1.0/cloudcost_cli.egg-info/requires.txt +23 -0
- cloudcost_cli-0.1.0/cloudcost_cli.egg-info/top_level.txt +1 -0
- cloudcost_cli-0.1.0/pyproject.toml +68 -0
- cloudcost_cli-0.1.0/setup.cfg +4 -0
|
@@ -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."""
|