Best use case
cloud-cost-estimator is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Teams using cloud-cost-estimator should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/cloud-cost-estimator/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How cloud-cost-estimator Compares
| Feature / Agent | cloud-cost-estimator | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
This skill provides specific capabilities for your AI agent. See the About section for full details.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# cloud-cost-estimator Skill
## Overview
cloud-cost-estimator is a pure client-side React SPA that builds multi-provider cloud cost estimates. There is no backend server, no API, and no network requests at runtime. All pricing data is bundled into the compiled JavaScript at build time. Estimates are persisted in `localStorage`.
## Architecture: Client-only SPA
```
src/
data/
aws.ts -- AWS service prices (ServicePrice[])
gcp.ts -- GCP service prices (ServicePrice[])
azure.ts -- Azure service prices (ServicePrice[])
index.ts -- re-exports + metadata (DATA_DATE, version)
stores/
estimateStore.ts -- Zustand: wizard state (step, providers, lineItems)
savedStore.ts -- Zustand: persisted saved estimates (localStorage)
lib/
calculator.ts -- computeLineItem, computeTotal, exportCSV, exportJSON
ulid.ts -- client-side ULID generation (no dependency)
shareLink.ts -- encode/decode estimate to URL hash
components/
ProgressSteps.tsx
ProviderCard.tsx
ServiceRow.tsx
LineItemRow.tsx
CostBar.tsx
ProviderCompareTable.tsx
pages/
EstimatePage.tsx -- 4-step wizard
ComparePage.tsx
SavedPage.tsx
SettingsPage.tsx
```
## Core Data Types
```typescript
// src/data/types.ts
export interface ServicePrice {
id: string; // e.g. "ec2-t3.medium"
provider: 'aws' | 'gcp' | 'azure';
name: string; // display name
category: 'compute' | 'storage' | 'database' | 'network' | 'other';
unit: string; // e.g. "instance", "GB", "GB-month"
pricePerUnit: number; // always in USD cents (integer)
billingModel: 'hourly' | 'monthly' | 'per-gb' | 'per-request' | 'per-gb-month';
specs?: string; // e.g. "2 vCPU, 4 GB"
region?: string; // default region for this price
dataDate: string; // "YYYY-MM-DD"
}
export interface LineItem {
serviceId: string;
provider: 'aws' | 'gcp' | 'azure';
quantity: number;
monthlyCents: number; // computed: quantity * normalised monthly price
}
export interface Estimate {
id: string; // ULID
name: string;
providers: Array<'aws' | 'gcp' | 'azure'>;
lineItems: LineItem[];
totalCents: number;
createdAt: string; // ISO-8601
dataDate: string; // pricing data date used
}
```
## Calculator Logic
```typescript
// src/lib/calculator.ts
export const HOURS_PER_MONTH = 730;
export function computeLineItem(price: ServicePrice, quantity: number): number {
// returns monthly cost in cents
switch (price.billingModel) {
case 'hourly':
return Math.round(price.pricePerUnit * HOURS_PER_MONTH * quantity);
case 'monthly':
return Math.round(price.pricePerUnit * quantity);
case 'per-gb':
case 'per-gb-month':
return Math.round(price.pricePerUnit * quantity);
case 'per-request':
return Math.round(price.pricePerUnit * quantity);
default:
return Math.round(price.pricePerUnit * quantity);
}
}
export function computeTotal(lineItems: LineItem[]): number {
return lineItems.reduce((sum, li) => sum + li.monthlyCents, 0);
}
// Format: 12345 cents -> "$123.45"
export function formatCents(cents: number): string {
return '$' + (cents / 100).toFixed(2);
}
```
## Zustand Store Shapes
### estimateStore
```typescript
interface EstimateStore {
step: 1 | 2 | 3 | 4;
name: string;
providers: Set<'aws' | 'gcp' | 'azure'>;
selectedServices: Set<string>; // serviceId[]
quantities: Record<string, number>; // serviceId -> quantity
lineItems: LineItem[]; // computed on step 3 changes
// actions
setStep: (s: 1|2|3|4) => void;
toggleProvider: (p: string) => void;
toggleService: (id: string) => void;
setQuantity: (id: string, qty: number) => void;
reset: () => void;
}
```
### savedStore
```typescript
interface SavedStore {
estimates: Estimate[]; // persisted via zustand/middleware/persist
// actions
save: (e: Estimate) => void;
remove: (id: string) => void;
load: (e: Estimate) => void; // loads into estimateStore
exportAll: () => void; // triggers JSON download
}
// localStorage key: "cce_saved_estimates"
```
## Share Link Encoding
Estimates can be encoded in the URL hash. This allows sharing without a server.
```typescript
// src/lib/shareLink.ts
// Encode: JSON stringify -> btoa -> set window.location.hash
export function encodeEstimate(estimate: Estimate): string;
// Decode: read hash -> atob -> JSON parse
export function decodeEstimate(hash: string): Estimate | null;
```
URL format: `https://host/#share=<base64url-encoded-json>`
No server is contacted. Recipients decode the estimate in the browser.
## Export Formats
### CSV
```
Provider,Service,Category,Quantity,Unit,Unit Price (USD),Monthly (USD)
AWS,EC2 t3.medium,compute,3,instance,$30.37,$91.10
AWS,EC2 t3.large,compute,1,instance,$60.74,$60.74
...
,,,,,,Total,$303.16
```
### JSON
```json
{
"id": "01HXYZ...",
"name": "Staging Environment",
"dataDate": "2026-03-01",
"exportedAt": "2026-03-20T10:42:00Z",
"totalMonthlyCents": 30316,
"totalMonthlyUSD": 303.16,
"lineItems": [
{
"provider": "aws",
"serviceId": "ec2-t3.medium",
"name": "EC2 t3.medium",
"category": "compute",
"quantity": 3,
"unit": "instance",
"unitPriceCents": 3037,
"monthlyCents": 9110
}
]
}
```
## localStorage Keys
| Key | Contents |
|-----|----------|
| `cce_saved_estimates` | JSON array of `Estimate[]` |
| `cce_settings` | User settings (region, currency, display prefs) |
## Settings
| Setting | Default | Type | Notes |
|---------|---------|------|-------|
| `awsRegion` | `us-east-1` | string | Displayed label only; pricing data is region-specific files |
| `gcpRegion` | `us-central1` | string | As above |
| `azureRegion` | `East US` | string | As above |
| `currency` | `USD` | string | USD only in v1; conversion stubs ready |
| `showAnnual` | `true` | boolean | Show annual projection alongside monthly |
| `showReserved` | `false` | boolean | Show reserved pricing column in catalog |
| `decimalPrecision` | `4` | number | Decimal places for per-unit pricing |
| `warnUnsaved` | `true` | boolean | Confirm dialog when loading over unsaved estimate |
## Provider Compare Logic
`ComparePage` loads all three provider price lists and maps services to equivalents using a `comparisons[]` array in `src/data/comparisons.ts`. Each entry names the equivalent service IDs per provider and a configured quantity.
```typescript
interface Comparison {
spec: string; // human label e.g. "2 vCPU / 4 GB general purpose"
unit: string; // "per instance, monthly"
category: ServiceCategory;
aws?: string; // serviceId
gcp?: string;
azure?: string;
}
```
The cheapest provider per row is highlighted with `.cheapest` CSS class.
## Build and Development
```bash
# install
pnpm install
# dev server (Vite, port 5173)
pnpm dev
# production build
pnpm build
# preview production build
pnpm preview
# type check
pnpm typecheck
# lint
pnpm lint
```
## Vite Config
```typescript
// vite.config.ts
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist',
sourcemap: true,
},
});
```
No environment variables are needed at runtime. The app is fully self-contained after `pnpm build`.
## Deployment
The `dist/` directory produced by `pnpm build` is a static site. Deploy to any static host:
```bash
# Netlify
netlify deploy --prod --dir dist
# Vercel
vercel --prod
# S3 + CloudFront
aws s3 sync dist/ s3://my-bucket/ --delete
aws cloudfront create-invalidation --distribution-id XXXXX --paths "/*"
# nginx
server {
root /var/www/cloud-cost-estimator/dist;
try_files $uri $uri/ /index.html;
}
```
## Adding New Pricing Data
Pricing data lives in `src/data/{provider}.ts` as a TypeScript array. To update:
1. Edit the array in the relevant provider file.
2. Update `DATA_DATE` in `src/data/index.ts` to reflect the new pricing date.
3. Run `pnpm build` to bundle updated prices into the JS.
Prices are stored as **integer cents** to avoid floating-point rounding issues. `$0.023/GB` is stored as `pricePerUnit: 2.3` (fractional cents allowed for sub-cent prices; the calculator rounds to integer cents at line-item level).
## Testing Price Accuracy
```bash
# run unit tests for calculator
pnpm test
# Example test assertion (vitest)
expect(computeLineItem({ pricePerUnit: 416, billingModel: 'hourly' }, 3))
.toBe(910680); // 3 * $0.0416 * 730hr = $91.07 -> 9107 cents
```
Note: `HOURS_PER_MONTH = 730` is the AWS standard for on-demand monthly estimates.
## Troubleshooting
| Issue | Cause | Fix |
|-------|-------|-----|
| Saved estimates lost | Browser storage cleared | Use Export before clearing |
| Share link too long | Large estimate | URL hash is base64-encoded JSON; works for up to ~50 services |
| Prices seem wrong | Bundled data is outdated | Check `DATA_DATE` in src/data/index.ts; update if needed |
| App blank on load | JS bundle error | Check browser console; run `pnpm build` again |
| Currency not converting | v1 limitation | Only USD supported in v1; conversion planned for v2 |Related Skills
Skill: Cost Reporting
## Overview
Skill: Uptime Monitoring
## Overview
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.
email-digest
Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.
websocket-realtime
Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".
poll-builder
Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.
Skill: personal-finance
## Overview
Skill: csv-import
## Overview
Skill: Syntax Highlighting
## Purpose