@bhaskarauthor/llm-spend 0.1.0-alpha.1
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.
- package/LICENSE +17 -0
- package/PRODUCT_CYCLE.md +38 -0
- package/README.md +35 -0
- package/demo/DEMO.md +24 -0
- package/demo/run-demo.mjs +12 -0
- package/fixtures/sample.json +9 -0
- package/package.json +39 -0
- package/src/cli.js +30 -0
- package/src/core.js +28 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bhaskar Pandey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
package/PRODUCT_CYCLE.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Product cycle — llm-spend
|
|
2
|
+
|
|
3
|
+
| Field | Value |
|
|
4
|
+
|-------|--------|
|
|
5
|
+
| **Track** | Alpha (`alpha`) |
|
|
6
|
+
| **Version** | `0.1.0-alpha.1` |
|
|
7
|
+
| **Cycle** | **CLOSED** (Alpha) |
|
|
8
|
+
| **Closed at** | 2026-07-12T04:12:35.713Z |
|
|
9
|
+
| **Publish** | **Not published** — local workspace only until explicit go-ahead |
|
|
10
|
+
|
|
11
|
+
## Closed-cycle checklist
|
|
12
|
+
|
|
13
|
+
- [x] `package.json` name + version set
|
|
14
|
+
- [x] MIT LICENSE
|
|
15
|
+
- [x] README documents scope + limitations
|
|
16
|
+
- [x] Automated tests exist and pass under suite litmus
|
|
17
|
+
- [x] Cycle smoke tests (`tests/cycle.test.mjs`) for core entrypoints
|
|
18
|
+
- [x] Demo or usage path documented
|
|
19
|
+
- [x] Zero runtime dependencies (suite convention)
|
|
20
|
+
- [x] Known limitations listed (below)
|
|
21
|
+
- [ ] npm publish (blocked until owner says go)
|
|
22
|
+
- [ ] git remote push (blocked until owner says go)
|
|
23
|
+
|
|
24
|
+
## Known limitations (Alpha)
|
|
25
|
+
|
|
26
|
+
- Offline domain MVP — no live cloud / WhatsApp / payments integrations.
|
|
27
|
+
- Litmus + cycle smoke tests, not full E2E or load tests.
|
|
28
|
+
- Commercial hosted layers are future work; OSS core stays free.
|
|
29
|
+
|
|
30
|
+
## How to verify
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd llm-spend
|
|
34
|
+
npm test
|
|
35
|
+
npm run cycle:check 2>/dev/null || node --test tests/*.test.mjs tests/**/*.test.mjs
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Suite: `node scripts/suite-litmus.mjs` from workspace root.
|
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# llm-spend
|
|
2
|
+
|
|
3
|
+
> P05 · AI Tooling / FinOps
|
|
4
|
+
|
|
5
|
+
**Unified LLM cost, token, and budget observability across OpenAI, Anthropic, Google, Azure, open models**
|
|
6
|
+
|
|
7
|
+
## Status
|
|
8
|
+
|
|
9
|
+
Offline **MVP core** (v0.1.0-beta.0) — zero runtime dependencies, litmus-tested, demo-ready.
|
|
10
|
+
Part of the Bhaskar Pandey product portfolio under `Documents/development`.
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd llm-spend
|
|
16
|
+
npm test
|
|
17
|
+
npm run demo
|
|
18
|
+
node src/cli.js --help
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## MVP scope
|
|
22
|
+
|
|
23
|
+
Proxy + per-key budgets + simple dashboard + alerts
|
|
24
|
+
|
|
25
|
+
## Open source → commercial
|
|
26
|
+
|
|
27
|
+
| Layer | Now | Later |
|
|
28
|
+
|-------|-----|-------|
|
|
29
|
+
| Core algorithms / CLI | OSS MIT | remains OSS |
|
|
30
|
+
| Hosted / team / SSO | — | commercial |
|
|
31
|
+
| Support / SLAs | community | paid |
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
MIT
|
package/demo/DEMO.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# llm-spend (P05) — Demo
|
|
2
|
+
|
|
3
|
+
Unified LLM cost, token, and budget observability across OpenAI, Anthropic, Google, Azure, open models
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd llm-spend
|
|
9
|
+
node demo/run-demo.mjs
|
|
10
|
+
node src/cli.js --json fixtures/sample.json
|
|
11
|
+
npm test
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## MVP
|
|
15
|
+
|
|
16
|
+
Proxy + per-key budgets + simple dashboard + alerts
|
|
17
|
+
|
|
18
|
+
## Platforms
|
|
19
|
+
|
|
20
|
+
Web,API,CLI
|
|
21
|
+
|
|
22
|
+
## Commercial path (later)
|
|
23
|
+
|
|
24
|
+
OSS core stays free; paid layer = team dashboard / hosted / compliance export.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
6
|
+
console.log("=== DEMO P05 llm-spend ===");
|
|
7
|
+
console.log("Unified LLM cost, token, and budget observability across OpenAI, Anthropic, Google, Azure, open models");
|
|
8
|
+
const r = spawnSync(process.execPath, [path.join(root, "src/cli.js"), "--json", path.join(root, "fixtures/sample.json")], {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
});
|
|
11
|
+
console.log(r.stdout || r.stderr);
|
|
12
|
+
process.exit(r.status ?? 0);
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bhaskarauthor/llm-spend",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Unified LLM cost, token, and budget observability across OpenAI, Anthropic, Google, Azure, open models",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"llm-spend": "src/cli.js",
|
|
9
|
+
"ai-llm-spend": "src/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "node --test tests/litmus.test.mjs tests/cycle.test.mjs",
|
|
13
|
+
"demo": "node demo/run-demo.mjs",
|
|
14
|
+
"litmus": "node --test tests/litmus.test.mjs",
|
|
15
|
+
"cycle:check": "node --test tests/litmus.test.mjs tests/cycle.test.mjs"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=20"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"AI",
|
|
22
|
+
"FinOps",
|
|
23
|
+
"observability",
|
|
24
|
+
"cost"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": "Bhaskar Pandey <bhaskarauthor@gmail.com>",
|
|
28
|
+
"files": [
|
|
29
|
+
"src",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"PRODUCT_CYCLE.md",
|
|
33
|
+
"demo",
|
|
34
|
+
"fixtures"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import * as core from "./core.js";
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
8
|
+
console.log(`llm-spend (P05) — Unified LLM cost, token, and budget observability across OpenAI, Anthropic, Google, Azure, open models
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
llm-spend [--json] [fixture.json]
|
|
12
|
+
llm-spend --help
|
|
13
|
+
|
|
14
|
+
Offline MVP core for OSS shipping; commercial layers later.
|
|
15
|
+
`);
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
const json = args.includes("--json");
|
|
19
|
+
const file = args.find((a) => !a.startsWith("-"));
|
|
20
|
+
let input = {};
|
|
21
|
+
if (file && existsSync(file)) input = JSON.parse(readFileSync(resolve(file), "utf8"));
|
|
22
|
+
else if (existsSync(new URL("../fixtures/sample.json", import.meta.url))) {
|
|
23
|
+
input = JSON.parse(readFileSync(new URL("../fixtures/sample.json", import.meta.url), "utf8"));
|
|
24
|
+
}
|
|
25
|
+
const result = typeof core.main === "function" ? core.main(input) : { ok: true };
|
|
26
|
+
if (json) console.log(JSON.stringify({ product: "llm-spend", id: "P05", result }, null, 2));
|
|
27
|
+
else {
|
|
28
|
+
console.log("llm-spend · P05");
|
|
29
|
+
console.log(JSON.stringify(result, null, 2));
|
|
30
|
+
}
|
package/src/core.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** P05 llm-spend — offline MVP core (zero deps) */
|
|
2
|
+
export function main(input) {
|
|
3
|
+
const events = (Array.isArray(input.events) ? input.events : Array.isArray(input) ? input : []);
|
|
4
|
+
const usage = parseUsageEvents(Array.isArray(events) ? events : []);
|
|
5
|
+
const budget = input.budgetUsd || 100;
|
|
6
|
+
return { usage, budget: budgetStatus(usage.totalCostUsd, budget) };
|
|
7
|
+
}
|
|
8
|
+
export function parseUsageEvents(events) {
|
|
9
|
+
const byProvider = {};
|
|
10
|
+
let totalTokens = 0, totalCost = 0;
|
|
11
|
+
for (const e of (Array.isArray(events) ? events : [])) {
|
|
12
|
+
const p = e.provider || "unknown";
|
|
13
|
+
const tokens = Number(e.tokens || 0);
|
|
14
|
+
const cost = Number(e.costUsd ?? tokens * (e.pricePer1k || 0.002) / 1000);
|
|
15
|
+
byProvider[p] = byProvider[p] || { tokens: 0, costUsd: 0, calls: 0 };
|
|
16
|
+
byProvider[p].tokens += tokens;
|
|
17
|
+
byProvider[p].costUsd += cost;
|
|
18
|
+
byProvider[p].calls += 1;
|
|
19
|
+
totalTokens += tokens;
|
|
20
|
+
totalCost += cost;
|
|
21
|
+
}
|
|
22
|
+
return { byProvider, totalTokens, totalCostUsd: round(totalCost), eventCount: events.length };
|
|
23
|
+
}
|
|
24
|
+
export function budgetStatus(totalCostUsd, budgetUsd) {
|
|
25
|
+
const used = totalCostUsd / budgetUsd;
|
|
26
|
+
return { budgetUsd, totalCostUsd, usedPct: round(used * 100), over: totalCostUsd > budgetUsd };
|
|
27
|
+
}
|
|
28
|
+
function round(n) { return Math.round(n * 10000) / 10000; }
|